@ory/argus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/assets/commands/local-down.md +19 -0
- package/assets/commands/local-up.md +27 -0
- package/assets/skills/auth-setup/SKILL.md +279 -0
- package/assets/skills/local-dev/SKILL.md +206 -0
- package/assets/skills/login-flow/SKILL.md +383 -0
- package/assets/skills/social-login/SKILL.md +312 -0
- package/dist/agent-auth.d.ts +204 -0
- package/dist/agent-auth.js +553 -0
- package/dist/auth-gate.d.ts +71 -0
- package/dist/auth-gate.js +308 -0
- package/dist/auth-store.d.ts +75 -0
- package/dist/auth-store.js +261 -0
- package/dist/auth.d.ts +93 -0
- package/dist/auth.js +323 -0
- package/dist/cli.d.ts +73 -0
- package/dist/cli.js +484 -0
- package/dist/client.d.ts +158 -0
- package/dist/client.js +679 -0
- package/dist/config.d.ts +135 -0
- package/dist/config.js +344 -0
- package/dist/denial.d.ts +79 -0
- package/dist/denial.js +103 -0
- package/dist/dev.d.ts +95 -0
- package/dist/dev.js +514 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +137 -0
- package/dist/local/cli.d.ts +12 -0
- package/dist/local/cli.js +95 -0
- package/dist/local/configs.d.ts +89 -0
- package/dist/local/configs.js +634 -0
- package/dist/local/health.d.ts +32 -0
- package/dist/local/health.js +65 -0
- package/dist/local/index.d.ts +6 -0
- package/dist/local/index.js +38 -0
- package/dist/local/jaeger-main.d.ts +13 -0
- package/dist/local/jaeger-main.js +85 -0
- package/dist/local/jaeger.d.ts +50 -0
- package/dist/local/jaeger.js +162 -0
- package/dist/local/main.d.ts +7 -0
- package/dist/local/main.js +14 -0
- package/dist/local/manager.d.ts +45 -0
- package/dist/local/manager.js +676 -0
- package/dist/local/seed.d.ts +71 -0
- package/dist/local/seed.js +237 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.js +139 -0
- package/dist/mcp.d.ts +76 -0
- package/dist/mcp.js +122 -0
- package/dist/otel/exporter.d.ts +17 -0
- package/dist/otel/exporter.js +12 -0
- package/dist/otel/index.d.ts +2 -0
- package/dist/otel/index.js +8 -0
- package/dist/otel/otlp-http.d.ts +116 -0
- package/dist/otel/otlp-http.js +322 -0
- package/dist/registry/cli.d.ts +12 -0
- package/dist/registry/cli.js +76 -0
- package/dist/registry/config.d.ts +23 -0
- package/dist/registry/config.js +80 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +21 -0
- package/dist/registry/main.d.ts +7 -0
- package/dist/registry/main.js +14 -0
- package/dist/registry/manager.d.ts +38 -0
- package/dist/registry/manager.js +674 -0
- package/dist/setup.d.ts +118 -0
- package/dist/setup.js +398 -0
- package/dist/skills.d.ts +78 -0
- package/dist/skills.js +264 -0
- package/dist/subject.d.ts +43 -0
- package/dist/subject.js +55 -0
- package/dist/tool-metadata.d.ts +41 -0
- package/dist/tool-metadata.js +127 -0
- package/dist/tracer.d.ts +172 -0
- package/dist/tracer.js +452 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +3 -0
- package/dist/watch-sandbox.d.ts +9 -0
- package/dist/watch-sandbox.js +81 -0
- package/package.json +79 -0
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based OAuth2 PKCE login for Ory agent plugins.
|
|
3
|
+
*
|
|
4
|
+
* Spins up a transient localhost callback server on a fixed-port range
|
|
5
|
+
* (option (a) — operators register the same loopback URIs once with the
|
|
6
|
+
* Ory OAuth2 client), generates a PKCE verifier/challenge, launches the
|
|
7
|
+
* user's browser at the project's authorize URL, and exchanges the
|
|
8
|
+
* resulting code for a token set via /oauth2/token.
|
|
9
|
+
*
|
|
10
|
+
* This module never writes to stdout. Browser launching uses the `open`
|
|
11
|
+
* package via dynamic import so the CJS-emitted core stays compatible
|
|
12
|
+
* with its ESM-only export shape.
|
|
13
|
+
*/
|
|
14
|
+
import { OryOAuth2Tokens } from "./config.js";
|
|
15
|
+
/** Loopback ports tried in order. All must be registered as redirect URIs on the Ory OAuth2 client. */
|
|
16
|
+
export declare const LOOPBACK_PORTS: readonly [47823, 47824, 47825, 47826];
|
|
17
|
+
/** Hard timeout if the user never returns to the browser. */
|
|
18
|
+
export declare const DEFAULT_LOGIN_TIMEOUT_MS = 120000;
|
|
19
|
+
export interface PkceLoginOptions {
|
|
20
|
+
/** Ory project URL (e.g. https://your-project.projects.oryapis.com). */
|
|
21
|
+
projectUrl: string;
|
|
22
|
+
/** Public OAuth2 client id registered with the loopback redirect URIs. */
|
|
23
|
+
clientId: string;
|
|
24
|
+
/** Scopes to request. Defaults to `openid offline_access`. */
|
|
25
|
+
scope?: string;
|
|
26
|
+
/** Audience to request, if any. */
|
|
27
|
+
audience?: string;
|
|
28
|
+
/** Login timeout in ms. Defaults to {@link DEFAULT_LOGIN_TIMEOUT_MS}. */
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
/** Override the loopback port list (mostly for testing). */
|
|
31
|
+
ports?: ReadonlyArray<number>;
|
|
32
|
+
/** Hook for opening the browser. Defaults to the `open` npm package. */
|
|
33
|
+
openBrowser?: (url: string) => Promise<void>;
|
|
34
|
+
/** Override headless detection (mostly for testing). */
|
|
35
|
+
isHeadless?: () => boolean;
|
|
36
|
+
/** Optional AbortSignal to cancel the login (e.g. on shutdown). */
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
export type PkceLoginOutcome = {
|
|
40
|
+
kind: "ok";
|
|
41
|
+
tokens: OryOAuth2Tokens;
|
|
42
|
+
} | {
|
|
43
|
+
kind: "declined";
|
|
44
|
+
reason: PkceDeclineReason;
|
|
45
|
+
};
|
|
46
|
+
export type PkceDeclineReason = "headless" | "timeout" | "user_denied" | "state_mismatch" | "no_port" | "browser_launch_failed" | "aborted" | "token_exchange_failed";
|
|
47
|
+
/**
|
|
48
|
+
* Default headless heuristic. Returns true only for unattended runs
|
|
49
|
+
* (CI=true / CI=1) so {@link pkceLogin} can short-circuit instead of
|
|
50
|
+
* waiting on a callback that will never arrive.
|
|
51
|
+
*
|
|
52
|
+
* Heuristics like `SSH_TTY` and "Linux without DISPLAY" used to live here
|
|
53
|
+
* but turned out to be wrong in practice: an SSH user can paste the
|
|
54
|
+
* printed authorize URL into their workstation browser, and a Linux box
|
|
55
|
+
* without a display still works as long as the operator can reach the
|
|
56
|
+
* loopback callback port (e.g. via `ssh -L`). The PKCE flow always
|
|
57
|
+
* prints the URL to stderr, so anything short of "no human at all" is
|
|
58
|
+
* recoverable.
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectHeadless(env?: NodeJS.ProcessEnv): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Run a PKCE login and return either a token set or the reason the flow
|
|
63
|
+
* could not complete. The function never throws on user-driven failures
|
|
64
|
+
* (timeout, decline, headless); it throws only on programmer error or
|
|
65
|
+
* unrecoverable I/O failures.
|
|
66
|
+
*/
|
|
67
|
+
export declare function pkceLogin(options: PkceLoginOptions): Promise<PkceLoginOutcome>;
|
|
68
|
+
/** Generate a 64-byte (≈86-char base64url) code verifier per RFC 7636. */
|
|
69
|
+
export declare function generateCodeVerifier(): string;
|
|
70
|
+
/** SHA-256 + base64url, the only supported PKCE challenge method (`S256`). */
|
|
71
|
+
export declare function sha256Base64Url(input: string): string;
|
|
72
|
+
export declare function buildAuthorizeUrl(params: {
|
|
73
|
+
projectUrl: string;
|
|
74
|
+
clientId: string;
|
|
75
|
+
redirectUri: string;
|
|
76
|
+
state: string;
|
|
77
|
+
codeChallenge: string;
|
|
78
|
+
scope: string;
|
|
79
|
+
audience?: string;
|
|
80
|
+
}): string;
|
|
81
|
+
/**
|
|
82
|
+
* Refresh an access token using a refresh token. Throws on any non-2xx
|
|
83
|
+
* response so callers can decide whether to fall back to a fresh login.
|
|
84
|
+
*
|
|
85
|
+
* Kept as raw fetch for parity with the other two /oauth2/token flows;
|
|
86
|
+
* see `exchangeCodeForTokens` and `fetchClientCredentialsToken` for why
|
|
87
|
+
* @ory/client's OAuth2Api.oauth2TokenExchange isn't used.
|
|
88
|
+
*/
|
|
89
|
+
export declare function refreshAccessToken(args: {
|
|
90
|
+
projectUrl: string;
|
|
91
|
+
clientId: string;
|
|
92
|
+
refreshToken: string;
|
|
93
|
+
}): Promise<OryOAuth2Tokens>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser-based OAuth2 PKCE login for Ory agent plugins.
|
|
4
|
+
*
|
|
5
|
+
* Spins up a transient localhost callback server on a fixed-port range
|
|
6
|
+
* (option (a) — operators register the same loopback URIs once with the
|
|
7
|
+
* Ory OAuth2 client), generates a PKCE verifier/challenge, launches the
|
|
8
|
+
* user's browser at the project's authorize URL, and exchanges the
|
|
9
|
+
* resulting code for a token set via /oauth2/token.
|
|
10
|
+
*
|
|
11
|
+
* This module never writes to stdout. Browser launching uses the `open`
|
|
12
|
+
* package via dynamic import so the CJS-emitted core stays compatible
|
|
13
|
+
* with its ESM-only export shape.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = void 0;
|
|
17
|
+
exports.detectHeadless = detectHeadless;
|
|
18
|
+
exports.pkceLogin = pkceLogin;
|
|
19
|
+
exports.generateCodeVerifier = generateCodeVerifier;
|
|
20
|
+
exports.sha256Base64Url = sha256Base64Url;
|
|
21
|
+
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
22
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
23
|
+
const node_crypto_1 = require("node:crypto");
|
|
24
|
+
const node_http_1 = require("node:http");
|
|
25
|
+
const node_url_1 = require("node:url");
|
|
26
|
+
/** Loopback ports tried in order. All must be registered as redirect URIs on the Ory OAuth2 client. */
|
|
27
|
+
exports.LOOPBACK_PORTS = [47823, 47824, 47825, 47826];
|
|
28
|
+
/** Hard timeout if the user never returns to the browser. */
|
|
29
|
+
exports.DEFAULT_LOGIN_TIMEOUT_MS = 120_000;
|
|
30
|
+
/**
|
|
31
|
+
* Default headless heuristic. Returns true only for unattended runs
|
|
32
|
+
* (CI=true / CI=1) so {@link pkceLogin} can short-circuit instead of
|
|
33
|
+
* waiting on a callback that will never arrive.
|
|
34
|
+
*
|
|
35
|
+
* Heuristics like `SSH_TTY` and "Linux without DISPLAY" used to live here
|
|
36
|
+
* but turned out to be wrong in practice: an SSH user can paste the
|
|
37
|
+
* printed authorize URL into their workstation browser, and a Linux box
|
|
38
|
+
* without a display still works as long as the operator can reach the
|
|
39
|
+
* loopback callback port (e.g. via `ssh -L`). The PKCE flow always
|
|
40
|
+
* prints the URL to stderr, so anything short of "no human at all" is
|
|
41
|
+
* recoverable.
|
|
42
|
+
*/
|
|
43
|
+
function detectHeadless(env = process.env) {
|
|
44
|
+
return env.CI === "true" || env.CI === "1";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run a PKCE login and return either a token set or the reason the flow
|
|
48
|
+
* could not complete. The function never throws on user-driven failures
|
|
49
|
+
* (timeout, decline, headless); it throws only on programmer error or
|
|
50
|
+
* unrecoverable I/O failures.
|
|
51
|
+
*/
|
|
52
|
+
async function pkceLogin(options) {
|
|
53
|
+
const isHeadless = options.isHeadless ?? detectHeadless;
|
|
54
|
+
const headless = isHeadless();
|
|
55
|
+
if (headless) {
|
|
56
|
+
// Unattended environment (CI). No human to follow the URL, so we
|
|
57
|
+
// skip the loopback listener entirely instead of timing out.
|
|
58
|
+
return { kind: "declined", reason: "headless" };
|
|
59
|
+
}
|
|
60
|
+
const { server, port } = await listenOnFirstAvailablePort(options.ports ?? exports.LOOPBACK_PORTS);
|
|
61
|
+
if (!server || port === null) {
|
|
62
|
+
return { kind: "declined", reason: "no_port" };
|
|
63
|
+
}
|
|
64
|
+
const codeVerifier = generateCodeVerifier();
|
|
65
|
+
const codeChallenge = sha256Base64Url(codeVerifier);
|
|
66
|
+
const state = (0, node_crypto_1.randomBytes)(16).toString("base64url");
|
|
67
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
68
|
+
const scope = options.scope ?? "openid offline_access";
|
|
69
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
70
|
+
projectUrl: options.projectUrl,
|
|
71
|
+
clientId: options.clientId,
|
|
72
|
+
redirectUri,
|
|
73
|
+
state,
|
|
74
|
+
codeChallenge,
|
|
75
|
+
scope,
|
|
76
|
+
audience: options.audience,
|
|
77
|
+
});
|
|
78
|
+
const timeoutMs = options.timeoutMs ?? exports.DEFAULT_LOGIN_TIMEOUT_MS;
|
|
79
|
+
// Print the URL to stderr *before* attempting to launch the browser.
|
|
80
|
+
// Users in non-TTY environments (SSH without `-t`, IDE terminals, etc.)
|
|
81
|
+
// can paste this into any browser that can reach 127.0.0.1 on the
|
|
82
|
+
// loopback port (locally on the same machine, or via `ssh -L
|
|
83
|
+
// <port>:localhost:<port>` from a remote box).
|
|
84
|
+
process.stderr.write(`\nOry sign-in required. Open this URL in a browser that can reach ${redirectUri}:\n\n ${authorizeUrl}\n\n` +
|
|
85
|
+
`Waiting up to ${Math.round(timeoutMs / 1000)}s for the callback…\n\n`);
|
|
86
|
+
const callback = waitForCallback(server, {
|
|
87
|
+
expectedState: state,
|
|
88
|
+
timeoutMs,
|
|
89
|
+
signal: options.signal,
|
|
90
|
+
});
|
|
91
|
+
// Browser launch is best-effort. If `open` throws or the platform has
|
|
92
|
+
// no usable browser, we keep the callback server alive and rely on the
|
|
93
|
+
// user opening the printed URL themselves. Decline only on a real
|
|
94
|
+
// callback failure (timeout, state mismatch, user_denied).
|
|
95
|
+
try {
|
|
96
|
+
await launchBrowser(authorizeUrl, options.openBrowser);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
process.stderr.write(`Could not auto-launch a browser (${err instanceof Error ? err.message : String(err)}). ` +
|
|
100
|
+
`Open the URL above manually to continue.\n\n`);
|
|
101
|
+
}
|
|
102
|
+
const result = await callback;
|
|
103
|
+
server.close();
|
|
104
|
+
if (result.kind !== "ok")
|
|
105
|
+
return result;
|
|
106
|
+
try {
|
|
107
|
+
const tokens = await exchangeCodeForTokens({
|
|
108
|
+
projectUrl: options.projectUrl,
|
|
109
|
+
clientId: options.clientId,
|
|
110
|
+
redirectUri,
|
|
111
|
+
code: result.code,
|
|
112
|
+
codeVerifier,
|
|
113
|
+
});
|
|
114
|
+
return { kind: "ok", tokens };
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { kind: "declined", reason: "token_exchange_failed" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ─── PKCE primitives ──────────────────────────────────────────────────
|
|
121
|
+
/** Generate a 64-byte (≈86-char base64url) code verifier per RFC 7636. */
|
|
122
|
+
function generateCodeVerifier() {
|
|
123
|
+
return (0, node_crypto_1.randomBytes)(64).toString("base64url");
|
|
124
|
+
}
|
|
125
|
+
/** SHA-256 + base64url, the only supported PKCE challenge method (`S256`). */
|
|
126
|
+
function sha256Base64Url(input) {
|
|
127
|
+
return (0, node_crypto_1.createHash)("sha256").update(input).digest("base64url");
|
|
128
|
+
}
|
|
129
|
+
function buildAuthorizeUrl(params) {
|
|
130
|
+
const url = new node_url_1.URL("/oauth2/auth", params.projectUrl);
|
|
131
|
+
url.searchParams.set("response_type", "code");
|
|
132
|
+
url.searchParams.set("client_id", params.clientId);
|
|
133
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
134
|
+
url.searchParams.set("state", params.state);
|
|
135
|
+
url.searchParams.set("scope", params.scope);
|
|
136
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
137
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
138
|
+
if (params.audience)
|
|
139
|
+
url.searchParams.set("audience", params.audience);
|
|
140
|
+
return url.toString();
|
|
141
|
+
}
|
|
142
|
+
// ─── Callback server ──────────────────────────────────────────────────
|
|
143
|
+
async function listenOnFirstAvailablePort(ports) {
|
|
144
|
+
for (const requested of ports) {
|
|
145
|
+
try {
|
|
146
|
+
const server = await new Promise((resolve, reject) => {
|
|
147
|
+
const s = (0, node_http_1.createServer)();
|
|
148
|
+
const onError = (err) => {
|
|
149
|
+
s.removeListener("listening", onListening);
|
|
150
|
+
reject(err);
|
|
151
|
+
};
|
|
152
|
+
const onListening = () => {
|
|
153
|
+
s.removeListener("error", onError);
|
|
154
|
+
resolve(s);
|
|
155
|
+
};
|
|
156
|
+
s.once("error", onError);
|
|
157
|
+
s.once("listening", onListening);
|
|
158
|
+
s.listen(requested, "127.0.0.1");
|
|
159
|
+
});
|
|
160
|
+
const addr = server.address();
|
|
161
|
+
const port = typeof addr === "object" && addr ? addr.port : requested;
|
|
162
|
+
return { server, port };
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
if (err.code === "EADDRINUSE")
|
|
166
|
+
continue;
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { server: null, port: null };
|
|
171
|
+
}
|
|
172
|
+
function waitForCallback(server, opts) {
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
let settled = false;
|
|
175
|
+
const settle = (r) => {
|
|
176
|
+
if (settled)
|
|
177
|
+
return;
|
|
178
|
+
settled = true;
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
if (opts.signal)
|
|
181
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
182
|
+
server.removeListener("request", onRequest);
|
|
183
|
+
resolve(r);
|
|
184
|
+
};
|
|
185
|
+
const timer = setTimeout(() => settle({ kind: "declined", reason: "timeout" }), opts.timeoutMs);
|
|
186
|
+
const onAbort = () => settle({ kind: "declined", reason: "aborted" });
|
|
187
|
+
if (opts.signal) {
|
|
188
|
+
if (opts.signal.aborted) {
|
|
189
|
+
settle({ kind: "declined", reason: "aborted" });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
opts.signal.addEventListener("abort", onAbort);
|
|
193
|
+
}
|
|
194
|
+
const onRequest = (req, res) => {
|
|
195
|
+
if (!isLoopbackHost(req.headers.host)) {
|
|
196
|
+
respond(res, 400, "Bad Host");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const url = new node_url_1.URL(req.url ?? "/", `http://127.0.0.1`);
|
|
200
|
+
if (url.pathname !== "/callback") {
|
|
201
|
+
respond(res, 404, "Not Found");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const error = url.searchParams.get("error");
|
|
205
|
+
if (error) {
|
|
206
|
+
respond(res, 400, "Authentication declined. You may close this window.");
|
|
207
|
+
settle({ kind: "declined", reason: "user_denied" });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const state = url.searchParams.get("state");
|
|
211
|
+
const code = url.searchParams.get("code");
|
|
212
|
+
if (!code || state !== opts.expectedState) {
|
|
213
|
+
respond(res, 400, "State mismatch.");
|
|
214
|
+
settle({ kind: "declined", reason: "state_mismatch" });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
respond(res, 200, "Sign-in complete. You may close this window and return to your terminal.");
|
|
218
|
+
settle({ kind: "ok", code });
|
|
219
|
+
};
|
|
220
|
+
server.on("request", onRequest);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function isLoopbackHost(host) {
|
|
224
|
+
if (!host)
|
|
225
|
+
return false;
|
|
226
|
+
const hostOnly = host.split(":")[0];
|
|
227
|
+
return hostOnly === "127.0.0.1" || hostOnly === "localhost" || hostOnly === "[::1]";
|
|
228
|
+
}
|
|
229
|
+
function respond(res, status, body) {
|
|
230
|
+
res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
231
|
+
res.end(body + "\n");
|
|
232
|
+
}
|
|
233
|
+
// ─── Browser launch ───────────────────────────────────────────────────
|
|
234
|
+
async function launchBrowser(url, override) {
|
|
235
|
+
if (override) {
|
|
236
|
+
await override(url);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// `open` is ESM-only; dynamic import works from CJS output.
|
|
240
|
+
const mod = (await import("open"));
|
|
241
|
+
await mod.default(url);
|
|
242
|
+
}
|
|
243
|
+
// Kept as raw fetch (rather than @ory/client's OAuth2Api.oauth2TokenExchange)
|
|
244
|
+
// because the SDK method's signature does not expose code_verifier, which
|
|
245
|
+
// PKCE requires. Ory's own SDK docstring on that method says it should not
|
|
246
|
+
// be used via the SDK.
|
|
247
|
+
async function exchangeCodeForTokens(args) {
|
|
248
|
+
const body = new URLSearchParams({
|
|
249
|
+
grant_type: "authorization_code",
|
|
250
|
+
client_id: args.clientId,
|
|
251
|
+
code: args.code,
|
|
252
|
+
redirect_uri: args.redirectUri,
|
|
253
|
+
code_verifier: args.codeVerifier,
|
|
254
|
+
});
|
|
255
|
+
const tokenUrl = new node_url_1.URL("/oauth2/token", args.projectUrl).toString();
|
|
256
|
+
const res = await fetch(tokenUrl, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
259
|
+
body: body.toString(),
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) {
|
|
262
|
+
throw new Error(`Token exchange failed: HTTP ${res.status}`);
|
|
263
|
+
}
|
|
264
|
+
const json = (await res.json());
|
|
265
|
+
return tokensFromTokenResponse(json, args.clientId);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Refresh an access token using a refresh token. Throws on any non-2xx
|
|
269
|
+
* response so callers can decide whether to fall back to a fresh login.
|
|
270
|
+
*
|
|
271
|
+
* Kept as raw fetch for parity with the other two /oauth2/token flows;
|
|
272
|
+
* see `exchangeCodeForTokens` and `fetchClientCredentialsToken` for why
|
|
273
|
+
* @ory/client's OAuth2Api.oauth2TokenExchange isn't used.
|
|
274
|
+
*/
|
|
275
|
+
async function refreshAccessToken(args) {
|
|
276
|
+
const body = new URLSearchParams({
|
|
277
|
+
grant_type: "refresh_token",
|
|
278
|
+
client_id: args.clientId,
|
|
279
|
+
refresh_token: args.refreshToken,
|
|
280
|
+
});
|
|
281
|
+
const tokenUrl = new node_url_1.URL("/oauth2/token", args.projectUrl).toString();
|
|
282
|
+
const res = await fetch(tokenUrl, {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
285
|
+
body: body.toString(),
|
|
286
|
+
});
|
|
287
|
+
if (!res.ok) {
|
|
288
|
+
throw new Error(`Token refresh failed: HTTP ${res.status}`);
|
|
289
|
+
}
|
|
290
|
+
const json = (await res.json());
|
|
291
|
+
return tokensFromTokenResponse(json, args.clientId, args.refreshToken);
|
|
292
|
+
}
|
|
293
|
+
function tokensFromTokenResponse(json, clientId, fallbackRefreshToken) {
|
|
294
|
+
const accessToken = typeof json.access_token === "string" ? json.access_token : "";
|
|
295
|
+
if (!accessToken)
|
|
296
|
+
throw new Error("Token response missing access_token");
|
|
297
|
+
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 3600;
|
|
298
|
+
const refreshToken = typeof json.refresh_token === "string" ? json.refresh_token : fallbackRefreshToken;
|
|
299
|
+
return {
|
|
300
|
+
accessToken,
|
|
301
|
+
refreshToken,
|
|
302
|
+
expiresAt: Math.floor(Date.now() / 1000) + expiresIn,
|
|
303
|
+
clientId,
|
|
304
|
+
idToken: typeof json.id_token === "string" ? json.id_token : undefined,
|
|
305
|
+
scope: typeof json.scope === "string" ? json.scope : undefined,
|
|
306
|
+
subject: subjectFromIdToken(typeof json.id_token === "string" ? json.id_token : undefined),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** Best-effort sub-claim extraction from a JWT id_token. Returns undefined on any failure. */
|
|
310
|
+
function subjectFromIdToken(idToken) {
|
|
311
|
+
if (!idToken)
|
|
312
|
+
return undefined;
|
|
313
|
+
const parts = idToken.split(".");
|
|
314
|
+
if (parts.length < 2)
|
|
315
|
+
return undefined;
|
|
316
|
+
try {
|
|
317
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
318
|
+
return typeof payload.sub === "string" ? payload.sub : undefined;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `configure` command implementation for all plugin CLIs.
|
|
3
|
+
*
|
|
4
|
+
* Parses --project-url and --api-key from args. If neither is provided,
|
|
5
|
+
* shows the current configuration. Otherwise saves the provided values.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runConfigureCommand(binName: string, args: string[]): void;
|
|
8
|
+
/**
|
|
9
|
+
* Print the "Configuration:" block showing Ory project URL and API key status.
|
|
10
|
+
*/
|
|
11
|
+
export declare function printOryConfig(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Print the "Environment:" block showing Ory-related environment variables.
|
|
14
|
+
*/
|
|
15
|
+
export declare function printEnvironment(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Print the last 5 debug log entries if ORY_AGENT_LOG_FILE is set and exists.
|
|
18
|
+
*/
|
|
19
|
+
export declare function printLogTail(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Print the "Configure your Ory credentials" help text.
|
|
22
|
+
*/
|
|
23
|
+
export declare function printEnvHelp(binName: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Print the last 5 trace entries if ORY_AGENT_TRACE_FILE is set and exists.
|
|
26
|
+
*/
|
|
27
|
+
export declare function printTraceTail(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Run the live trace watcher. Tails the NDJSON trace file and prints
|
|
30
|
+
* formatted spans to stdout as they arrive. Blocks until interrupted.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runWatchCommand(args: string[]): void;
|
|
33
|
+
/**
|
|
34
|
+
* Returns true if `/dev/tty` can be opened for reading. Use this before
|
|
35
|
+
* attempting to prompt the user — the harness has already taken stdin so
|
|
36
|
+
* we cannot use `process.stdin`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isTtyAvailable(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Prompt the user to enter a value on /dev/tty. Returns the trimmed
|
|
41
|
+
* line, or null if no TTY is available. The prompt is written to stderr
|
|
42
|
+
* — never stdout.
|
|
43
|
+
*/
|
|
44
|
+
export declare function promptOnTty(prompt: string): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Inline prompt for the Ory project URL. Persists the entered URL to the
|
|
47
|
+
* shared config file and returns it. Returns null if the user cancels
|
|
48
|
+
* (empty input) or no TTY is available.
|
|
49
|
+
*
|
|
50
|
+
* This is the structured counterpart to {@link interactiveConfigPrompt},
|
|
51
|
+
* which only blocks until the user runs `configure` in another terminal.
|
|
52
|
+
*/
|
|
53
|
+
export declare function promptForProjectUrl(binName: string): Promise<string | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Display a friendly configuration prompt on stderr and block until the
|
|
56
|
+
* user presses Enter. After the user continues, re-checks config from
|
|
57
|
+
* disk and returns true if Ory is now configured.
|
|
58
|
+
*
|
|
59
|
+
* Falls back to non-interactive mode (returns false immediately) when
|
|
60
|
+
* no TTY is available (e.g. CI).
|
|
61
|
+
*/
|
|
62
|
+
export declare function interactiveConfigPrompt(binName: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Run the `agent <subcommand>` family of CLI commands. These manage the
|
|
65
|
+
* agent's dynamic OAuth2 client registration (RFC 7591). Subcommands:
|
|
66
|
+
*
|
|
67
|
+
* - `status` — print the persisted DCR identity, if any.
|
|
68
|
+
* - `unregister` — best-effort RFC 7592 DELETE on the registered
|
|
69
|
+
* client, then clear the persisted block.
|
|
70
|
+
*
|
|
71
|
+
* Returns the process exit code (0 on success, non-zero on usage error).
|
|
72
|
+
*/
|
|
73
|
+
export declare function runAgentCommand(binName: string, args: string[]): Promise<number>;
|