@ory/argus 0.13.8 → 0.14.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 +2 -1
- package/assets/commands/dashboard.md +34 -0
- package/assets/skills/ory-build-agent/SKILL.md +11 -13
- package/assets/skills/ory-e2b-sandbox/SKILL.md +0 -1
- package/assets/skills/ory-temporal-worker/SKILL.md +8 -10
- package/assets/skills/permissions-onboarding/SKILL.md +3 -3
- package/dist/adapters.d.ts +27 -4
- package/dist/adapters.js +132 -32
- package/dist/agent-auth.d.ts +34 -3
- package/dist/agent-auth.js +58 -8
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +90 -5
- package/dist/branding.d.ts +67 -0
- package/dist/branding.js +81 -0
- package/dist/build-info.json +4 -4
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +13 -52
- package/dist/client.d.ts +31 -0
- package/dist/client.js +58 -0
- package/dist/config.d.ts +48 -19
- package/dist/config.js +31 -26
- package/dist/contract-suite.d.ts +5 -3
- package/dist/contract-suite.js +13 -22
- package/dist/dashboard-cli.d.ts +8 -0
- package/dist/dashboard-cli.js +70 -0
- package/dist/dev.js +3 -4
- package/dist/index.d.ts +12 -5
- package/dist/index.js +41 -3
- package/dist/interactive-setup.d.ts +144 -23
- package/dist/interactive-setup.js +412 -224
- package/dist/local/health.d.ts +14 -0
- package/dist/local/health.js +46 -0
- package/dist/local/manager.js +1 -3
- package/dist/local/seed.d.ts +9 -20
- package/dist/local/seed.js +26 -18
- package/dist/permissions-cli.js +12 -4
- package/dist/project-api-key.d.ts +69 -0
- package/dist/project-api-key.js +147 -0
- package/dist/registry/manager.js +62 -18
- package/dist/setup-actions.d.ts +232 -0
- package/dist/setup-actions.js +507 -0
- package/dist/setup.d.ts +19 -0
- package/dist/setup.js +44 -14
- package/dist/skills.js +7 -0
- package/dist/status-cli.d.ts +2 -2
- package/dist/status-cli.js +4 -30
- package/dist/status-data.d.ts +96 -0
- package/dist/status-data.js +250 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +20 -1
- package/dist/uninstall.d.ts +33 -15
- package/dist/uninstall.js +65 -22
- package/dist/user-login.d.ts +16 -9
- package/dist/user-login.js +18 -28
- package/dist/web/api.d.ts +33 -0
- package/dist/web/api.js +294 -0
- package/dist/web/launch.d.ts +11 -0
- package/dist/web/launch.js +96 -0
- package/dist/web/server.d.ts +20 -0
- package/dist/web/server.js +233 -0
- package/dist/web/types.d.ts +65 -0
- package/dist/web/types.js +2 -0
- package/dist/webapp/assets/index-Wucl4SZs.css +1 -0
- package/dist/webapp/assets/index-m-GtEdq0.js +49 -0
- package/dist/webapp/favicon.ico +0 -0
- package/dist/webapp/index.html +15 -0
- package/package.json +17 -2
package/dist/auth.js
CHANGED
|
@@ -12,6 +12,39 @@
|
|
|
12
12
|
* package via dynamic import so the CJS-emitted core stays compatible
|
|
13
13
|
* with its ESM-only export shape.
|
|
14
14
|
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
15
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
49
|
exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = void 0;
|
|
17
50
|
exports.detectHeadless = detectHeadless;
|
|
@@ -20,9 +53,12 @@ exports.generateCodeVerifier = generateCodeVerifier;
|
|
|
20
53
|
exports.sha256Base64Url = sha256Base64Url;
|
|
21
54
|
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
22
55
|
exports.refreshAccessToken = refreshAccessToken;
|
|
56
|
+
exports.displayNameFromIdToken = displayNameFromIdToken;
|
|
23
57
|
const node_crypto_1 = require("node:crypto");
|
|
24
58
|
const node_http_1 = require("node:http");
|
|
25
59
|
const node_url_1 = require("node:url");
|
|
60
|
+
const fs = __importStar(require("node:fs"));
|
|
61
|
+
const path = __importStar(require("node:path"));
|
|
26
62
|
/** Loopback ports tried in order. All must be registered as redirect URIs on the Ory OAuth2 client. */
|
|
27
63
|
exports.LOOPBACK_PORTS = [47823, 47824, 47825, 47826];
|
|
28
64
|
/** Hard timeout if the user never returns to the browser. */
|
|
@@ -201,6 +237,10 @@ function waitForCallback(server, opts) {
|
|
|
201
237
|
return;
|
|
202
238
|
}
|
|
203
239
|
const url = new node_url_1.URL(req.url ?? "/", `http://127.0.0.1`);
|
|
240
|
+
if (url.pathname === "/favicon.ico") {
|
|
241
|
+
respondFavicon(res);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
204
244
|
if (url.pathname !== "/callback") {
|
|
205
245
|
respond(res, 404, "Not Found");
|
|
206
246
|
return;
|
|
@@ -238,18 +278,41 @@ function respondHtml(res, status, html) {
|
|
|
238
278
|
res.writeHead(status, { "content-type": "text/html; charset=utf-8" });
|
|
239
279
|
res.end(html);
|
|
240
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Serve the shared Ory favicon (identical to console.ory.com's) so the sign-in
|
|
283
|
+
* result page carries the same tab icon as the Console. It ships alongside the
|
|
284
|
+
* built dashboard at `dist/webapp/favicon.ico`; if that file isn't present
|
|
285
|
+
* (e.g. the webapp wasn't built), fall back to a 404 — the page still renders,
|
|
286
|
+
* just without a favicon. Same-origin on the loopback server, so no external
|
|
287
|
+
* request is made.
|
|
288
|
+
*/
|
|
289
|
+
function respondFavicon(res) {
|
|
290
|
+
// Compiled to dist/auth.js; the SPA (and its favicon) is built to dist/webapp.
|
|
291
|
+
const file = path.join(__dirname, "webapp", "favicon.ico");
|
|
292
|
+
try {
|
|
293
|
+
const buf = fs.readFileSync(file);
|
|
294
|
+
res.writeHead(200, {
|
|
295
|
+
"content-type": "image/x-icon",
|
|
296
|
+
"cache-control": "no-store",
|
|
297
|
+
});
|
|
298
|
+
res.end(buf);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
respond(res, 404, "Not Found");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
241
304
|
/**
|
|
242
305
|
* The page the browser lands on after the OAuth2 redirect. The whole point is
|
|
243
306
|
* to make the next action unambiguous: on success it tells the user, in plain
|
|
244
307
|
* words, that they can close the tab and return to the terminal (the #1
|
|
245
|
-
* source of "is it done? do I close this?" confusion).
|
|
246
|
-
*
|
|
247
|
-
*
|
|
308
|
+
* source of "is it done? do I close this?" confusion). Inline styles only, no
|
|
309
|
+
* external requests — the only asset is the same-origin Ory favicon the
|
|
310
|
+
* loopback server serves — so it renders identically offline.
|
|
248
311
|
*/
|
|
249
312
|
function resultPage(kind) {
|
|
250
313
|
const view = {
|
|
251
314
|
success: {
|
|
252
|
-
accent: "#
|
|
315
|
+
accent: "#22C55E", // green — a positive "done" check
|
|
253
316
|
icon: "✓", // ✓
|
|
254
317
|
title: "Signed in to Ory",
|
|
255
318
|
body: "You can <strong>close this tab</strong> and return to your terminal — setup continues automatically.",
|
|
@@ -272,6 +335,7 @@ function resultPage(kind) {
|
|
|
272
335
|
<head>
|
|
273
336
|
<meta charset="utf-8">
|
|
274
337
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
338
|
+
<link rel="icon" href="/favicon.ico">
|
|
275
339
|
<title>${view.title}</title>
|
|
276
340
|
<style>
|
|
277
341
|
:root { color-scheme: light dark; }
|
|
@@ -381,6 +445,10 @@ function tokensFromTokenResponse(json, clientId, fallbackRefreshToken) {
|
|
|
381
445
|
}
|
|
382
446
|
/** Best-effort sub-claim extraction from a JWT id_token. Returns undefined on any failure. */
|
|
383
447
|
function subjectFromIdToken(idToken) {
|
|
448
|
+
return claimsFromIdToken(idToken)?.sub;
|
|
449
|
+
}
|
|
450
|
+
/** Best-effort JWT id_token payload decode. Returns undefined on any failure. */
|
|
451
|
+
function claimsFromIdToken(idToken) {
|
|
384
452
|
if (!idToken)
|
|
385
453
|
return undefined;
|
|
386
454
|
const parts = idToken.split(".");
|
|
@@ -388,9 +456,26 @@ function subjectFromIdToken(idToken) {
|
|
|
388
456
|
return undefined;
|
|
389
457
|
try {
|
|
390
458
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
391
|
-
return typeof payload
|
|
459
|
+
return payload && typeof payload === "object" ? payload : undefined;
|
|
392
460
|
}
|
|
393
461
|
catch {
|
|
394
462
|
return undefined;
|
|
395
463
|
}
|
|
396
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Best-effort human-readable name for the signed-in user, pulled from the
|
|
467
|
+
* id_token's identity claims — `email`, then `name`, then `preferred_username`.
|
|
468
|
+
* Returns undefined when the token carries none of them (e.g. an `openid`-only
|
|
469
|
+
* token), so callers fall back to the opaque subject id.
|
|
470
|
+
*/
|
|
471
|
+
function displayNameFromIdToken(idToken) {
|
|
472
|
+
const claims = claimsFromIdToken(idToken);
|
|
473
|
+
if (!claims)
|
|
474
|
+
return undefined;
|
|
475
|
+
for (const key of ["email", "name", "preferred_username"]) {
|
|
476
|
+
const v = claims[key];
|
|
477
|
+
if (typeof v === "string" && v.trim())
|
|
478
|
+
return v.trim();
|
|
479
|
+
}
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The umbrella brand and the names for every resource the installer creates in
|
|
3
|
+
* (or for) an Ory project. Centralized here so every install path — the web
|
|
4
|
+
* wizard, the TTY wizard, and the agent-identity gates — produces the same,
|
|
5
|
+
* self-describing names, and so the naming policy lives in one place.
|
|
6
|
+
*
|
|
7
|
+
* The policy has two tiers, because many users install these plugins against
|
|
8
|
+
* the **same** Ory project:
|
|
9
|
+
*
|
|
10
|
+
* - **Shared** resources (one per project, reused by every user/harness) get a
|
|
11
|
+
* **stable, identity-free** name so all installs find and reuse the one
|
|
12
|
+
* resource instead of duplicating it: {@link USER_LOGIN_CLIENT_NAME} and the
|
|
13
|
+
* permission namespace ({@link PERMISSION_NAMESPACE}).
|
|
14
|
+
* - **Per-installer** resources (many per project by design — confidential
|
|
15
|
+
* per-machine DCR credentials, write-once API keys) get an **attributable**
|
|
16
|
+
* name carrying *who* and *where*, so a shared project's resource list stays
|
|
17
|
+
* legible and prunable: {@link agentClientName}, {@link projectApiKeyName}.
|
|
18
|
+
*
|
|
19
|
+
* Everything shares the {@link PRODUCT_NAME} prefix so the plugin's resources
|
|
20
|
+
* group together and are instantly recognizable in a shared Ory Console.
|
|
21
|
+
*/
|
|
22
|
+
/** Human-readable umbrella name for everything this product creates. */
|
|
23
|
+
export declare const PRODUCT_NAME = "Ory Agent Security";
|
|
24
|
+
/**
|
|
25
|
+
* The public PKCE client the human's browser sign-in uses. It is a *public*
|
|
26
|
+
* client (no secret), so every user on a project shares the one client — the
|
|
27
|
+
* installer dedupes by this exact name. Stable and identity-free on purpose.
|
|
28
|
+
*/
|
|
29
|
+
export declare const USER_LOGIN_CLIENT_NAME = "Ory Agent Security \u00B7 user login (PKCE)";
|
|
30
|
+
/**
|
|
31
|
+
* Names an older installer may have given the shared user-login client. The
|
|
32
|
+
* reuse probe matches these too, so renaming the client never orphans one
|
|
33
|
+
* created by a previous install (which would then mint a duplicate).
|
|
34
|
+
*/
|
|
35
|
+
export declare const LEGACY_USER_LOGIN_CLIENT_NAMES: string[];
|
|
36
|
+
/** All names the shared user-login client may carry, newest first. */
|
|
37
|
+
export declare const USER_LOGIN_CLIENT_NAMES: string[];
|
|
38
|
+
/** Default name for a project the installer creates. Harness-agnostic, since a
|
|
39
|
+
* team typically points several harnesses at one shared project. */
|
|
40
|
+
export declare const DEFAULT_PROJECT_NAME = "Ory Agent Security";
|
|
41
|
+
/** The shared permission-model namespace (an OPL class name, so it must stay a
|
|
42
|
+
* bare identifier — no spaces/branding — and stable across installs). */
|
|
43
|
+
export declare const PERMISSION_NAMESPACE = "AgentTools";
|
|
44
|
+
/**
|
|
45
|
+
* The client_name for an agent's (or sub-agent's) OAuth2 identity registered
|
|
46
|
+
* via DCR. These are confidential, per-machine credentials — many per shared
|
|
47
|
+
* project by design — so the name is attributable: the harness it belongs to
|
|
48
|
+
* and the host it was registered from (plus the sub-agent type, when it is one).
|
|
49
|
+
*/
|
|
50
|
+
export declare function agentClientName(opts: {
|
|
51
|
+
harness?: string;
|
|
52
|
+
host: string;
|
|
53
|
+
subAgentType?: string;
|
|
54
|
+
}): string;
|
|
55
|
+
/**
|
|
56
|
+
* The name for a project API key minted for runtime permission checks. Ory only
|
|
57
|
+
* returns a key's secret at creation, so a key can't be reused across machines
|
|
58
|
+
* — each installer that mints one gets its own. The name therefore carries the
|
|
59
|
+
* signed-in *user* (the axis that matters on a shared project) plus the host, so
|
|
60
|
+
* a team admin can tell whose key is whose and prune stale ones. No timestamp:
|
|
61
|
+
* the Ory Console's key listing already shows a "Date Added" column. `user`
|
|
62
|
+
* falls back to the host alone when it isn't resolvable (e.g. login skipped).
|
|
63
|
+
*/
|
|
64
|
+
export declare function projectApiKeyName(opts: {
|
|
65
|
+
host: string;
|
|
66
|
+
user?: string;
|
|
67
|
+
}): string;
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The umbrella brand and the names for every resource the installer creates in
|
|
4
|
+
* (or for) an Ory project. Centralized here so every install path — the web
|
|
5
|
+
* wizard, the TTY wizard, and the agent-identity gates — produces the same,
|
|
6
|
+
* self-describing names, and so the naming policy lives in one place.
|
|
7
|
+
*
|
|
8
|
+
* The policy has two tiers, because many users install these plugins against
|
|
9
|
+
* the **same** Ory project:
|
|
10
|
+
*
|
|
11
|
+
* - **Shared** resources (one per project, reused by every user/harness) get a
|
|
12
|
+
* **stable, identity-free** name so all installs find and reuse the one
|
|
13
|
+
* resource instead of duplicating it: {@link USER_LOGIN_CLIENT_NAME} and the
|
|
14
|
+
* permission namespace ({@link PERMISSION_NAMESPACE}).
|
|
15
|
+
* - **Per-installer** resources (many per project by design — confidential
|
|
16
|
+
* per-machine DCR credentials, write-once API keys) get an **attributable**
|
|
17
|
+
* name carrying *who* and *where*, so a shared project's resource list stays
|
|
18
|
+
* legible and prunable: {@link agentClientName}, {@link projectApiKeyName}.
|
|
19
|
+
*
|
|
20
|
+
* Everything shares the {@link PRODUCT_NAME} prefix so the plugin's resources
|
|
21
|
+
* group together and are instantly recognizable in a shared Ory Console.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.PERMISSION_NAMESPACE = exports.DEFAULT_PROJECT_NAME = exports.USER_LOGIN_CLIENT_NAMES = exports.LEGACY_USER_LOGIN_CLIENT_NAMES = exports.USER_LOGIN_CLIENT_NAME = exports.PRODUCT_NAME = void 0;
|
|
25
|
+
exports.agentClientName = agentClientName;
|
|
26
|
+
exports.projectApiKeyName = projectApiKeyName;
|
|
27
|
+
/** Human-readable umbrella name for everything this product creates. */
|
|
28
|
+
exports.PRODUCT_NAME = "Ory Agent Security";
|
|
29
|
+
/**
|
|
30
|
+
* The public PKCE client the human's browser sign-in uses. It is a *public*
|
|
31
|
+
* client (no secret), so every user on a project shares the one client — the
|
|
32
|
+
* installer dedupes by this exact name. Stable and identity-free on purpose.
|
|
33
|
+
*/
|
|
34
|
+
exports.USER_LOGIN_CLIENT_NAME = `${exports.PRODUCT_NAME} · user login (PKCE)`;
|
|
35
|
+
/**
|
|
36
|
+
* Names an older installer may have given the shared user-login client. The
|
|
37
|
+
* reuse probe matches these too, so renaming the client never orphans one
|
|
38
|
+
* created by a previous install (which would then mint a duplicate).
|
|
39
|
+
*/
|
|
40
|
+
exports.LEGACY_USER_LOGIN_CLIENT_NAMES = ["ory-agent-plugin"];
|
|
41
|
+
/** All names the shared user-login client may carry, newest first. */
|
|
42
|
+
exports.USER_LOGIN_CLIENT_NAMES = [
|
|
43
|
+
exports.USER_LOGIN_CLIENT_NAME,
|
|
44
|
+
...exports.LEGACY_USER_LOGIN_CLIENT_NAMES,
|
|
45
|
+
];
|
|
46
|
+
/** Default name for a project the installer creates. Harness-agnostic, since a
|
|
47
|
+
* team typically points several harnesses at one shared project. */
|
|
48
|
+
exports.DEFAULT_PROJECT_NAME = exports.PRODUCT_NAME;
|
|
49
|
+
/** The shared permission-model namespace (an OPL class name, so it must stay a
|
|
50
|
+
* bare identifier — no spaces/branding — and stable across installs). */
|
|
51
|
+
exports.PERMISSION_NAMESPACE = "AgentTools";
|
|
52
|
+
/**
|
|
53
|
+
* The client_name for an agent's (or sub-agent's) OAuth2 identity registered
|
|
54
|
+
* via DCR. These are confidential, per-machine credentials — many per shared
|
|
55
|
+
* project by design — so the name is attributable: the harness it belongs to
|
|
56
|
+
* and the host it was registered from (plus the sub-agent type, when it is one).
|
|
57
|
+
*/
|
|
58
|
+
function agentClientName(opts) {
|
|
59
|
+
const { harness, host, subAgentType } = opts;
|
|
60
|
+
if (subAgentType) {
|
|
61
|
+
const scope = harness ? `${harness}/${subAgentType}` : subAgentType;
|
|
62
|
+
return `${exports.PRODUCT_NAME} · sub-agent · ${scope} @ ${host}`;
|
|
63
|
+
}
|
|
64
|
+
return harness
|
|
65
|
+
? `${exports.PRODUCT_NAME} · agent · ${harness} @ ${host}`
|
|
66
|
+
: `${exports.PRODUCT_NAME} · agent @ ${host}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The name for a project API key minted for runtime permission checks. Ory only
|
|
70
|
+
* returns a key's secret at creation, so a key can't be reused across machines
|
|
71
|
+
* — each installer that mints one gets its own. The name therefore carries the
|
|
72
|
+
* signed-in *user* (the axis that matters on a shared project) plus the host, so
|
|
73
|
+
* a team admin can tell whose key is whose and prune stale ones. No timestamp:
|
|
74
|
+
* the Ory Console's key listing already shows a "Date Added" column. `user`
|
|
75
|
+
* falls back to the host alone when it isn't resolvable (e.g. login skipped).
|
|
76
|
+
*/
|
|
77
|
+
function projectApiKeyName(opts) {
|
|
78
|
+
const who = opts.user && opts.user !== "agent:unknown" ? opts.user : null;
|
|
79
|
+
const where = who ? `${who} @ ${opts.host}` : opts.host;
|
|
80
|
+
return `${exports.PRODUCT_NAME}: Permission Checks - ${where}`;
|
|
81
|
+
}
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "ory-agent-plugins",
|
|
3
|
-
"commit": "
|
|
4
|
-
"commitShort": "
|
|
3
|
+
"commit": "7568c163e59fba25d64ede49c092fcca108a43f2",
|
|
4
|
+
"commitShort": "7568c16",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"commitDate": "2026-07-
|
|
6
|
+
"commitDate": "2026-07-23T10:17:45-07:00",
|
|
7
7
|
"dirty": false,
|
|
8
|
-
"builtAt": "2026-07-
|
|
8
|
+
"builtAt": "2026-07-23T17:21:55.389Z"
|
|
9
9
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* When `--project-url` is provided, requires the OAuth2 client id to be
|
|
9
9
|
* resolvable from one of: the `--oauth2-client-id` flag, the
|
|
10
10
|
* `ORY_OAUTH2_CLIENT_ID` env var, or an already-persisted value in the
|
|
11
|
-
* shared config. The PKCE browser flow
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* shared config. The PKCE browser flow (the user login runs every session)
|
|
12
|
+
* can't self-register that client — surfacing the requirement here keeps
|
|
13
|
+
* users from a silently-broken setup later.
|
|
14
14
|
*/
|
|
15
15
|
export declare function runConfigureCommand(binName: string, args: string[]): void;
|
|
16
16
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -62,9 +62,9 @@ const client_js_1 = require("./client.js");
|
|
|
62
62
|
* When `--project-url` is provided, requires the OAuth2 client id to be
|
|
63
63
|
* resolvable from one of: the `--oauth2-client-id` flag, the
|
|
64
64
|
* `ORY_OAUTH2_CLIENT_ID` env var, or an already-persisted value in the
|
|
65
|
-
* shared config. The PKCE browser flow
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* shared config. The PKCE browser flow (the user login runs every session)
|
|
66
|
+
* can't self-register that client — surfacing the requirement here keeps
|
|
67
|
+
* users from a silently-broken setup later.
|
|
68
68
|
*/
|
|
69
69
|
function runConfigureCommand(binName, args) {
|
|
70
70
|
let projectUrl;
|
|
@@ -72,9 +72,6 @@ function runConfigureCommand(binName, args) {
|
|
|
72
72
|
let apiKey;
|
|
73
73
|
let oauth2ClientId;
|
|
74
74
|
let auditOnly = false;
|
|
75
|
-
// `undefined` = not specified (leave the persisted value alone);
|
|
76
|
-
// true/false = explicit enable/disable of the interactive user login.
|
|
77
|
-
let userLogin;
|
|
78
75
|
// `undefined` = not specified; a string sets it; empty string ("") clears it
|
|
79
76
|
// (via --no-user-subject-namespace) to fall back to direct SubjectID.
|
|
80
77
|
let userSubjectNamespace;
|
|
@@ -98,12 +95,6 @@ function runConfigureCommand(binName, args) {
|
|
|
98
95
|
case "--audit-only":
|
|
99
96
|
auditOnly = true;
|
|
100
97
|
break;
|
|
101
|
-
case "--user-login":
|
|
102
|
-
userLogin = true;
|
|
103
|
-
break;
|
|
104
|
-
case "--no-user-login":
|
|
105
|
-
userLogin = false;
|
|
106
|
-
break;
|
|
107
98
|
case "--user-subject-namespace":
|
|
108
99
|
userSubjectNamespace = args[++i];
|
|
109
100
|
break;
|
|
@@ -117,28 +108,23 @@ function runConfigureCommand(binName, args) {
|
|
|
117
108
|
!apiKey &&
|
|
118
109
|
!oauth2ClientId &&
|
|
119
110
|
!auditOnly &&
|
|
120
|
-
userLogin === undefined &&
|
|
121
111
|
userSubjectNamespace === undefined) {
|
|
122
112
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
123
113
|
const configPath = (0, config_js_1.getConfigPath)();
|
|
124
114
|
console.log("Ory Plugin Configuration");
|
|
125
115
|
console.log("========================");
|
|
126
116
|
console.log("");
|
|
127
|
-
console.log(`Config file: ${configPath}`);
|
|
117
|
+
console.log(`Config file: ${configPath}${fs.existsSync(configPath) ? "" : " (not created yet)"}`);
|
|
128
118
|
console.log(`Project URL: ${resolved.projectUrl ?? "(not set)"} [${resolved.projectUrlSource}]`);
|
|
129
119
|
console.log(`Project ID: ${resolved.projectId ?? "(not set)"} [${resolved.projectIdSource}]`);
|
|
130
120
|
console.log(`API Key: ${resolved.apiKey ? "(set)" : "(not set)"} [${resolved.apiKeySource}]`);
|
|
131
121
|
console.log(`OAuth2 Client ID: ${resolved.oauth2ClientId ?? "(not set)"} [${resolved.oauth2ClientIdSource}]`);
|
|
132
122
|
console.log(`Audit Only: ${resolved.auditOnly ? "yes" : "no"}`);
|
|
133
|
-
console.log(`User Login: ${resolved.userLogin ? "on" : "off"} [${resolved.userLoginSource}]`);
|
|
134
123
|
console.log(`User subject ns: ${resolved.userSubjectNamespace ?? "(direct subject id)"} [${resolved.userSubjectNamespaceSource}]`);
|
|
135
124
|
console.log("");
|
|
136
125
|
console.log("To configure:");
|
|
137
126
|
console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
138
127
|
console.log("");
|
|
139
|
-
console.log("To enable the interactive user login (PKCE browser flow at session start):");
|
|
140
|
-
console.log(` npx ${binName} configure --user-login (disable with --no-user-login)`);
|
|
141
|
-
console.log("");
|
|
142
128
|
console.log("To address the user as a SubjectSet in permission checks (must match your tuples):");
|
|
143
129
|
console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --user-subject-namespace User (clear with --no-user-subject-namespace)`);
|
|
144
130
|
console.log("");
|
|
@@ -151,20 +137,14 @@ function runConfigureCommand(binName, args) {
|
|
|
151
137
|
console.log(" # ORY_PROJECT_ID=<project uuid> is stored for admin operations (not an SDK URL).");
|
|
152
138
|
console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
|
|
153
139
|
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
154
|
-
console.log(" export ORY_USER_LOGIN=true");
|
|
155
140
|
return;
|
|
156
141
|
}
|
|
157
142
|
if (auditOnly) {
|
|
158
|
-
// Audit-only mode: set the kill switch
|
|
159
|
-
|
|
160
|
-
// later turned off; audit-only itself disables login regardless.
|
|
161
|
-
(0, config_js_1.saveConfig)({ auditOnly: true, ...(userLogin !== undefined ? { userLogin } : {}) });
|
|
143
|
+
// Audit-only mode: set the kill switch (disables auth and checks entirely).
|
|
144
|
+
(0, config_js_1.saveConfig)({ auditOnly: true });
|
|
162
145
|
const configPath = (0, config_js_1.getConfigPath)();
|
|
163
146
|
console.log(`Configuration saved to ${configPath}`);
|
|
164
147
|
console.log(" Mode: audit-only (logging enabled, auth and permission checks disabled)");
|
|
165
|
-
if (userLogin !== undefined) {
|
|
166
|
-
console.log(` User login: ${userLogin ? "on" : "off"} (takes effect when audit-only is disabled)`);
|
|
167
|
-
}
|
|
168
148
|
console.log("");
|
|
169
149
|
console.log("This configuration is shared across all Ory agent plugins.");
|
|
170
150
|
return;
|
|
@@ -207,34 +187,19 @@ function runConfigureCommand(binName, args) {
|
|
|
207
187
|
process.exit(1);
|
|
208
188
|
}
|
|
209
189
|
}
|
|
210
|
-
// Enabling user login needs a project URL and a public OAuth2 client id
|
|
211
|
-
// to complete the PKCE flow. Warn (don't block) if either is missing —
|
|
212
|
-
// the operator may supply them separately or via env before running.
|
|
213
|
-
if (userLogin === true) {
|
|
214
|
-
// resolveConfig() already folds in the ORY_SDK_URL alias and the config
|
|
215
|
-
// file; OR in the URL being set on this same invocation.
|
|
216
|
-
const nextProjectUrl = projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
217
|
-
const nextClientId = oauth2ClientId ?? process.env.ORY_OAUTH2_CLIENT_ID ?? (0, config_js_1.loadConfig)().oauth2ClientId;
|
|
218
|
-
if (!nextProjectUrl || !nextClientId) {
|
|
219
|
-
const missing = [
|
|
220
|
-
!nextProjectUrl ? "a project URL (--project-url / --sdk-url / ORY_PROJECT_URL)" : null,
|
|
221
|
-
!nextClientId ? "an OAuth2 client id (--oauth2-client-id / ORY_OAUTH2_CLIENT_ID)" : null,
|
|
222
|
-
].filter(Boolean);
|
|
223
|
-
console.warn(`Warning: user login is enabled but still missing ${missing.join(" and ")}.\n` +
|
|
224
|
-
"The PKCE browser flow can't complete until both are configured.");
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
190
|
const update = {};
|
|
228
191
|
if (projectUrl)
|
|
229
192
|
update.projectUrl = projectUrl;
|
|
230
193
|
if (projectId)
|
|
231
194
|
update.projectId = projectId;
|
|
232
|
-
if (apiKey)
|
|
195
|
+
if (apiKey) {
|
|
233
196
|
update.apiKey = apiKey;
|
|
197
|
+
// A hand-supplied key has no known token id; drop any stale id so uninstall
|
|
198
|
+
// never tries to delete a different token by an id that no longer matches.
|
|
199
|
+
update.apiKeyId = null;
|
|
200
|
+
}
|
|
234
201
|
if (oauth2ClientId)
|
|
235
202
|
update.oauth2ClientId = oauth2ClientId;
|
|
236
|
-
if (userLogin !== undefined)
|
|
237
|
-
update.userLogin = userLogin;
|
|
238
203
|
// A non-empty namespace sets the SubjectSet namespace; the empty string
|
|
239
204
|
// (from --no-user-subject-namespace) clears it (null ⇒ delete the key).
|
|
240
205
|
if (userSubjectNamespace !== undefined) {
|
|
@@ -243,7 +208,7 @@ function runConfigureCommand(binName, args) {
|
|
|
243
208
|
// Configuring an actual connection (a slug-based URL, api key, or client id)
|
|
244
209
|
// implies leaving audit-only mode. A bare project id is only an admin
|
|
245
210
|
// identifier — it doesn't connect anything on its own — so it doesn't flip
|
|
246
|
-
// the flag
|
|
211
|
+
// the flag.
|
|
247
212
|
if (projectUrl || apiKey || oauth2ClientId)
|
|
248
213
|
update.auditOnly = false;
|
|
249
214
|
(0, config_js_1.saveConfig)(update);
|
|
@@ -257,13 +222,11 @@ function runConfigureCommand(binName, args) {
|
|
|
257
222
|
console.log(` API Key: (set)`);
|
|
258
223
|
if (oauth2ClientId)
|
|
259
224
|
console.log(` OAuth2 Client ID: ${oauth2ClientId}`);
|
|
260
|
-
if (userLogin !== undefined)
|
|
261
|
-
console.log(` User Login: ${userLogin ? "on" : "off"}`);
|
|
262
225
|
if (userSubjectNamespace !== undefined)
|
|
263
226
|
console.log(` User subject namespace: ${userSubjectNamespace === "" ? "(cleared — direct subject id)" : userSubjectNamespace}`);
|
|
264
227
|
console.log("");
|
|
265
228
|
console.log("This configuration is shared across all Ory agent plugins.");
|
|
266
|
-
console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY, ORY_OAUTH2_CLIENT_ID
|
|
229
|
+
console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY, ORY_OAUTH2_CLIENT_ID) take precedence when set.");
|
|
267
230
|
}
|
|
268
231
|
/**
|
|
269
232
|
* Print the "Configuration:" block showing Ory project URL and API key status.
|
|
@@ -280,7 +243,6 @@ function printOryConfig() {
|
|
|
280
243
|
console.log(` API Key: ${resolved.apiKey ? "(set)" : "NOT SET"} [source: ${resolved.apiKeySource}]`);
|
|
281
244
|
console.log(` OAuth2 Client: ${resolved.oauth2ClientId ?? "NOT SET"} [source: ${resolved.oauth2ClientIdSource}]`);
|
|
282
245
|
console.log(` Permission mode: ${resolved.permissionMode} [source: ${resolved.permissionModeSource}]`);
|
|
283
|
-
console.log(` User login: ${resolved.userLogin ? "on" : "off"} [source: ${resolved.userLoginSource}]`);
|
|
284
246
|
console.log(` Namespace: ${namespace}`);
|
|
285
247
|
console.log(` User subject ns: ${resolved.userSubjectNamespace ?? "(direct subject id)"} [source: ${resolved.userSubjectNamespaceSource}]`);
|
|
286
248
|
}
|
|
@@ -294,7 +256,6 @@ function printEnvironment() {
|
|
|
294
256
|
["ORY_PROJECT_URL", process.env.ORY_PROJECT_URL, false],
|
|
295
257
|
["ORY_SDK_URL", process.env.ORY_SDK_URL, false],
|
|
296
258
|
["ORY_PROJECT_ID", process.env.ORY_PROJECT_ID, false],
|
|
297
|
-
["ORY_USER_LOGIN", process.env.ORY_USER_LOGIN, false],
|
|
298
259
|
[
|
|
299
260
|
"ORY_AGENT_API_KEY",
|
|
300
261
|
process.env.ORY_AGENT_API_KEY ? "(set)" : undefined,
|
package/dist/client.d.ts
CHANGED
|
@@ -159,6 +159,37 @@ export declare class OryAgentClient {
|
|
|
159
159
|
created: boolean;
|
|
160
160
|
alreadyExisted: boolean;
|
|
161
161
|
}>;
|
|
162
|
+
/**
|
|
163
|
+
* True if the *exact* relation tuple already exists in Keto. Queries the
|
|
164
|
+
* relation-tuple listing API filtered by the tuple's own coordinates
|
|
165
|
+
* (namespace/object/relation + subject), so — unlike `checkPermission` —
|
|
166
|
+
* it does not follow subject-set expansion: it answers "is this literal
|
|
167
|
+
* tuple stored?", not "does this permission resolve?". Throws a classified
|
|
168
|
+
* {@link OryError} if the query itself fails.
|
|
169
|
+
*/
|
|
170
|
+
relationshipExists(check: PermissionCheck): Promise<boolean>;
|
|
171
|
+
/**
|
|
172
|
+
* Idempotent relationship write. Keto's create API is **not** idempotent —
|
|
173
|
+
* `PUT /admin/relation-tuples` inserts a fresh row on every call (each with
|
|
174
|
+
* a new primary key) and never returns 409, so calling `createRelationship`
|
|
175
|
+
* on repeated bootstraps / session starts accumulates duplicate tuples.
|
|
176
|
+
* This reads the exact tuple first (via {@link relationshipExists}) and only
|
|
177
|
+
* writes when it is absent — which also self-heals if the tuple was deleted
|
|
178
|
+
* out of band.
|
|
179
|
+
*
|
|
180
|
+
* If the existence probe itself fails, the tuple is left untouched (we do
|
|
181
|
+
* not write blind, to avoid re-introducing duplicates when the read path is
|
|
182
|
+
* misbehaving) and the classified error is returned as `probeError`. Callers
|
|
183
|
+
* that must guarantee the grant exists (e.g. install-time bootstrap) can
|
|
184
|
+
* fall back to {@link createRelationship} when `probeError` is set.
|
|
185
|
+
*/
|
|
186
|
+
ensureRelationship(check: PermissionCheck, options?: {
|
|
187
|
+
spanAttributes?: Record<string, unknown>;
|
|
188
|
+
}): Promise<{
|
|
189
|
+
created: boolean;
|
|
190
|
+
alreadyExisted: boolean;
|
|
191
|
+
probeError?: OryError;
|
|
192
|
+
}>;
|
|
162
193
|
/**
|
|
163
194
|
* Delete a relation tuple in Keto. Missing tuples (404) are treated as
|
|
164
195
|
* success so callers can call this idempotently when unwinding state.
|
package/dist/client.js
CHANGED
|
@@ -551,6 +551,64 @@ class OryAgentClient {
|
|
|
551
551
|
throw oryErr;
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* True if the *exact* relation tuple already exists in Keto. Queries the
|
|
556
|
+
* relation-tuple listing API filtered by the tuple's own coordinates
|
|
557
|
+
* (namespace/object/relation + subject), so — unlike `checkPermission` —
|
|
558
|
+
* it does not follow subject-set expansion: it answers "is this literal
|
|
559
|
+
* tuple stored?", not "does this permission resolve?". Throws a classified
|
|
560
|
+
* {@link OryError} if the query itself fails.
|
|
561
|
+
*/
|
|
562
|
+
async relationshipExists(check) {
|
|
563
|
+
try {
|
|
564
|
+
const response = await this.relationship.getRelationships({
|
|
565
|
+
namespace: check.namespace,
|
|
566
|
+
object: check.object,
|
|
567
|
+
relation: check.relation,
|
|
568
|
+
...(check.subjectId ? { subjectId: check.subjectId } : {}),
|
|
569
|
+
...(check.subjectSet
|
|
570
|
+
? {
|
|
571
|
+
subjectSetNamespace: check.subjectSet.namespace,
|
|
572
|
+
subjectSetObject: check.subjectSet.object,
|
|
573
|
+
subjectSetRelation: check.subjectSet.relation,
|
|
574
|
+
}
|
|
575
|
+
: {}),
|
|
576
|
+
pageSize: 1,
|
|
577
|
+
});
|
|
578
|
+
const tuples = response.data?.relation_tuples ?? [];
|
|
579
|
+
return tuples.length > 0;
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
throw this.classifyError(err);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Idempotent relationship write. Keto's create API is **not** idempotent —
|
|
587
|
+
* `PUT /admin/relation-tuples` inserts a fresh row on every call (each with
|
|
588
|
+
* a new primary key) and never returns 409, so calling `createRelationship`
|
|
589
|
+
* on repeated bootstraps / session starts accumulates duplicate tuples.
|
|
590
|
+
* This reads the exact tuple first (via {@link relationshipExists}) and only
|
|
591
|
+
* writes when it is absent — which also self-heals if the tuple was deleted
|
|
592
|
+
* out of band.
|
|
593
|
+
*
|
|
594
|
+
* If the existence probe itself fails, the tuple is left untouched (we do
|
|
595
|
+
* not write blind, to avoid re-introducing duplicates when the read path is
|
|
596
|
+
* misbehaving) and the classified error is returned as `probeError`. Callers
|
|
597
|
+
* that must guarantee the grant exists (e.g. install-time bootstrap) can
|
|
598
|
+
* fall back to {@link createRelationship} when `probeError` is set.
|
|
599
|
+
*/
|
|
600
|
+
async ensureRelationship(check, options) {
|
|
601
|
+
let exists;
|
|
602
|
+
try {
|
|
603
|
+
exists = await this.relationshipExists(check);
|
|
604
|
+
}
|
|
605
|
+
catch (err) {
|
|
606
|
+
return { created: false, alreadyExisted: false, probeError: err };
|
|
607
|
+
}
|
|
608
|
+
if (exists)
|
|
609
|
+
return { created: false, alreadyExisted: true };
|
|
610
|
+
return this.createRelationship(check, options);
|
|
611
|
+
}
|
|
554
612
|
/**
|
|
555
613
|
* Delete a relation tuple in Keto. Missing tuples (404) are treated as
|
|
556
614
|
* success so callers can call this idempotently when unwinding state.
|