@ory/argus 0.13.3 → 0.13.4
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/dist/build-info.json +4 -4
- package/dist/client.d.ts +45 -7
- package/dist/client.js +94 -8
- package/dist/interactive-setup.d.ts +43 -1
- package/dist/interactive-setup.js +180 -1
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "ory-agent-plugins",
|
|
3
|
-
"commit": "
|
|
4
|
-
"commitShort": "
|
|
3
|
+
"commit": "2d5b9f7c6730c0189020f68b6a431348561c0c2a",
|
|
4
|
+
"commitShort": "2d5b9f7",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"commitDate": "2026-07-
|
|
6
|
+
"commitDate": "2026-07-15T06:07:39-07:00",
|
|
7
7
|
"dirty": false,
|
|
8
|
-
"builtAt": "2026-07-
|
|
8
|
+
"builtAt": "2026-07-15T13:11:41.044Z"
|
|
9
9
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -58,6 +58,23 @@ export declare class OryAgentClient {
|
|
|
58
58
|
* Ory API calls. Populated by `ensureAgentIdentity`.
|
|
59
59
|
*/
|
|
60
60
|
private _agentPrincipal;
|
|
61
|
+
/**
|
|
62
|
+
* Admin credential — an Ory Network **project API key** (`ory_pat_…`) —
|
|
63
|
+
* used to authenticate the Keto Permission and Relationship APIs.
|
|
64
|
+
*
|
|
65
|
+
* This is deliberately distinct from the agent principal's token. Ory
|
|
66
|
+
* Network's permission APIs authenticate with a project API key and
|
|
67
|
+
* **reject project-issued OAuth2 access tokens** (a DCR client-credentials
|
|
68
|
+
* token is answered with `401 "Access token is not active"`). So when the
|
|
69
|
+
* agent identity resolves to a DCR OAuth2 client — the default path — its
|
|
70
|
+
* token cannot authenticate permission checks. Holding the admin key here
|
|
71
|
+
* lets {@link buildApis} route Keto through it while the agent OAuth2 token
|
|
72
|
+
* still authenticates the OAuth2 / Frontend APIs and carries attribution.
|
|
73
|
+
*
|
|
74
|
+
* When unset, Keto calls fall back to the agent token (legacy
|
|
75
|
+
* single-credential behavior, unchanged for static-key deployments).
|
|
76
|
+
*/
|
|
77
|
+
private _adminApiKey?;
|
|
61
78
|
constructor(config: OryAgentConfig);
|
|
62
79
|
/** Build a fresh set of Ory API instances using the current agent token. */
|
|
63
80
|
private buildApis;
|
|
@@ -78,13 +95,25 @@ export declare class OryAgentClient {
|
|
|
78
95
|
* Pass `{}` (or fields set to undefined) to clear.
|
|
79
96
|
*/
|
|
80
97
|
setUserPrincipal(principal: PrincipalIdentity): void;
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
98
|
+
/** Whether an admin API key is set for the Keto APIs (never exposes it). */
|
|
99
|
+
get hasAdminApiKey(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Set or update the admin API key (an Ory Network project API key) used to
|
|
102
|
+
* authenticate the Keto Permission / Relationship APIs. Rebuilds those API
|
|
103
|
+
* instances when the key changes; a no-op update leaves them alone so test
|
|
104
|
+
* stubs survive. See {@link _adminApiKey} for why Keto needs a credential
|
|
105
|
+
* distinct from the agent's OAuth2 token.
|
|
106
|
+
*/
|
|
107
|
+
setAdminApiKey(apiKey: string | undefined): void;
|
|
108
|
+
/**
|
|
109
|
+
* Set or update the AI agent principal. The agent's token (when present)
|
|
110
|
+
* authenticates outgoing calls to the OAuth2 / Frontend APIs and carries
|
|
111
|
+
* audit attribution ("agent X acting on behalf of user Y"). Note the Keto
|
|
112
|
+
* Permission / Relationship APIs authenticate with {@link _adminApiKey}
|
|
113
|
+
* instead when one is set — Ory Network rejects OAuth2 tokens there.
|
|
114
|
+
* Rebuilds the underlying Ory API instances when the token actually changes
|
|
115
|
+
* so subsequent calls pick it up; otherwise leaves the API instances alone
|
|
116
|
+
* (so test stubs survive a no-op update).
|
|
88
117
|
*/
|
|
89
118
|
setAgentPrincipal(principal: PrincipalIdentity): void;
|
|
90
119
|
/**
|
|
@@ -146,6 +175,15 @@ export declare class OryAgentClient {
|
|
|
146
175
|
* across the user/agent split.
|
|
147
176
|
*/
|
|
148
177
|
private principalSpanAttributes;
|
|
178
|
+
/**
|
|
179
|
+
* When a Keto call is auth-rejected while it was authenticated by something
|
|
180
|
+
* other than an Ory Network project API key (`ory_pat_…`) — e.g. a DCR
|
|
181
|
+
* OAuth2 access token — the credential *type* is wrong, not merely expired.
|
|
182
|
+
* Ory Network's Permission / Relationship APIs only accept a project API
|
|
183
|
+
* key. Emit an actionable hint so the failure isn't misread as a stale
|
|
184
|
+
* session (the generic `session_inactive` classification of the raw 401).
|
|
185
|
+
*/
|
|
186
|
+
private warnIfKetoCredentialMismatch;
|
|
149
187
|
/**
|
|
150
188
|
* Classify an error from any Ory API call into a structured OryError.
|
|
151
189
|
*/
|
package/dist/client.js
CHANGED
|
@@ -63,6 +63,23 @@ class OryAgentClient {
|
|
|
63
63
|
* Ory API calls. Populated by `ensureAgentIdentity`.
|
|
64
64
|
*/
|
|
65
65
|
_agentPrincipal = {};
|
|
66
|
+
/**
|
|
67
|
+
* Admin credential — an Ory Network **project API key** (`ory_pat_…`) —
|
|
68
|
+
* used to authenticate the Keto Permission and Relationship APIs.
|
|
69
|
+
*
|
|
70
|
+
* This is deliberately distinct from the agent principal's token. Ory
|
|
71
|
+
* Network's permission APIs authenticate with a project API key and
|
|
72
|
+
* **reject project-issued OAuth2 access tokens** (a DCR client-credentials
|
|
73
|
+
* token is answered with `401 "Access token is not active"`). So when the
|
|
74
|
+
* agent identity resolves to a DCR OAuth2 client — the default path — its
|
|
75
|
+
* token cannot authenticate permission checks. Holding the admin key here
|
|
76
|
+
* lets {@link buildApis} route Keto through it while the agent OAuth2 token
|
|
77
|
+
* still authenticates the OAuth2 / Frontend APIs and carries attribution.
|
|
78
|
+
*
|
|
79
|
+
* When unset, Keto calls fall back to the agent token (legacy
|
|
80
|
+
* single-credential behavior, unchanged for static-key deployments).
|
|
81
|
+
*/
|
|
82
|
+
_adminApiKey;
|
|
66
83
|
constructor(config) {
|
|
67
84
|
this.config = config;
|
|
68
85
|
this.sessionCacheTtlMs = config.sessionCacheTtlMs ?? 60_000;
|
|
@@ -83,6 +100,13 @@ class OryAgentClient {
|
|
|
83
100
|
// call sites keep working until they migrate to ensureAgentIdentity.
|
|
84
101
|
if (config.apiKey)
|
|
85
102
|
this._agentPrincipal.token = config.apiKey;
|
|
103
|
+
// The `apiKey` is also the admin credential for the Keto Permission /
|
|
104
|
+
// Relationship APIs. Seed it separately so that when `ensureAgentIdentity`
|
|
105
|
+
// later replaces the agent token with a DCR OAuth2 token, permission
|
|
106
|
+
// checks keep authenticating with the project API key rather than the
|
|
107
|
+
// OAuth2 token Ory Network rejects. See {@link _adminApiKey}.
|
|
108
|
+
if (config.apiKey)
|
|
109
|
+
this._adminApiKey = config.apiKey;
|
|
86
110
|
const { frontend, oauth2, permission, relationship } = this.buildApis();
|
|
87
111
|
this.frontend = frontend;
|
|
88
112
|
this.oauth2 = oauth2;
|
|
@@ -96,11 +120,23 @@ class OryAgentClient {
|
|
|
96
120
|
basePath: this.config.projectUrl,
|
|
97
121
|
...(accessToken ? { accessToken } : {}),
|
|
98
122
|
});
|
|
123
|
+
// Keto (Permission + Relationship) authenticates with the admin project
|
|
124
|
+
// API key when one is set; otherwise it reuses the agent token (legacy
|
|
125
|
+
// single-credential behavior). When the two credentials are identical we
|
|
126
|
+
// reuse the same Configuration object so a single-credential deployment —
|
|
127
|
+
// and tests that stub one Configuration — see exactly one.
|
|
128
|
+
const ketoToken = this._adminApiKey ?? accessToken;
|
|
129
|
+
const ketoConfig = ketoToken === accessToken
|
|
130
|
+
? apiConfig
|
|
131
|
+
: new client_1.Configuration({
|
|
132
|
+
basePath: this.config.projectUrl,
|
|
133
|
+
...(ketoToken ? { accessToken: ketoToken } : {}),
|
|
134
|
+
});
|
|
99
135
|
return {
|
|
100
136
|
frontend: new client_1.FrontendApi(apiConfig),
|
|
101
137
|
oauth2: new client_1.OAuth2Api(apiConfig),
|
|
102
|
-
permission: new client_1.PermissionApi(
|
|
103
|
-
relationship: new client_1.RelationshipApi(
|
|
138
|
+
permission: new client_1.PermissionApi(ketoConfig),
|
|
139
|
+
relationship: new client_1.RelationshipApi(ketoConfig),
|
|
104
140
|
};
|
|
105
141
|
}
|
|
106
142
|
/** Snapshot of the current user principal. */
|
|
@@ -128,13 +164,36 @@ class OryAgentClient {
|
|
|
128
164
|
setUserPrincipal(principal) {
|
|
129
165
|
this._userPrincipal = { ...principal };
|
|
130
166
|
}
|
|
167
|
+
/** Whether an admin API key is set for the Keto APIs (never exposes it). */
|
|
168
|
+
get hasAdminApiKey() {
|
|
169
|
+
return !!this._adminApiKey;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Set or update the admin API key (an Ory Network project API key) used to
|
|
173
|
+
* authenticate the Keto Permission / Relationship APIs. Rebuilds those API
|
|
174
|
+
* instances when the key changes; a no-op update leaves them alone so test
|
|
175
|
+
* stubs survive. See {@link _adminApiKey} for why Keto needs a credential
|
|
176
|
+
* distinct from the agent's OAuth2 token.
|
|
177
|
+
*/
|
|
178
|
+
setAdminApiKey(apiKey) {
|
|
179
|
+
if (this._adminApiKey === apiKey)
|
|
180
|
+
return;
|
|
181
|
+
this._adminApiKey = apiKey;
|
|
182
|
+
const { frontend, oauth2, permission, relationship } = this.buildApis();
|
|
183
|
+
this.frontend = frontend;
|
|
184
|
+
this.oauth2 = oauth2;
|
|
185
|
+
this.permission = permission;
|
|
186
|
+
this.relationship = relationship;
|
|
187
|
+
}
|
|
131
188
|
/**
|
|
132
|
-
* Set or update the AI agent principal. The agent's token (when
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* the API instances
|
|
189
|
+
* Set or update the AI agent principal. The agent's token (when present)
|
|
190
|
+
* authenticates outgoing calls to the OAuth2 / Frontend APIs and carries
|
|
191
|
+
* audit attribution ("agent X acting on behalf of user Y"). Note the Keto
|
|
192
|
+
* Permission / Relationship APIs authenticate with {@link _adminApiKey}
|
|
193
|
+
* instead when one is set — Ory Network rejects OAuth2 tokens there.
|
|
194
|
+
* Rebuilds the underlying Ory API instances when the token actually changes
|
|
195
|
+
* so subsequent calls pick it up; otherwise leaves the API instances alone
|
|
196
|
+
* (so test stubs survive a no-op update).
|
|
138
197
|
*/
|
|
139
198
|
setAgentPrincipal(principal) {
|
|
140
199
|
const tokenChanged = this._agentPrincipal.token !== principal.token;
|
|
@@ -320,6 +379,7 @@ class OryAgentClient {
|
|
|
320
379
|
}
|
|
321
380
|
catch (err) {
|
|
322
381
|
const oryErr = this.classifyError(err);
|
|
382
|
+
this.warnIfKetoCredentialMismatch(oryErr);
|
|
323
383
|
this.logger.error("permission.check.failed", {
|
|
324
384
|
code: oryErr.code,
|
|
325
385
|
status: oryErr.status,
|
|
@@ -394,6 +454,7 @@ class OryAgentClient {
|
|
|
394
454
|
}
|
|
395
455
|
catch (err) {
|
|
396
456
|
const oryErr = this.classifyError(err);
|
|
457
|
+
this.warnIfKetoCredentialMismatch(oryErr);
|
|
397
458
|
this.logger.error("permission.batch_check.failed", {
|
|
398
459
|
code: oryErr.code,
|
|
399
460
|
status: oryErr.status,
|
|
@@ -475,6 +536,7 @@ class OryAgentClient {
|
|
|
475
536
|
});
|
|
476
537
|
return { created: false, alreadyExisted: true };
|
|
477
538
|
}
|
|
539
|
+
this.warnIfKetoCredentialMismatch(oryErr);
|
|
478
540
|
this.logger.error("relationship.create.failed", {
|
|
479
541
|
code: oryErr.code,
|
|
480
542
|
status: oryErr.status,
|
|
@@ -534,6 +596,7 @@ class OryAgentClient {
|
|
|
534
596
|
});
|
|
535
597
|
return { deleted: false, notFound: true };
|
|
536
598
|
}
|
|
599
|
+
this.warnIfKetoCredentialMismatch(oryErr);
|
|
537
600
|
this.logger.error("relationship.delete.failed", {
|
|
538
601
|
code: oryErr.code,
|
|
539
602
|
status: oryErr.status,
|
|
@@ -560,6 +623,29 @@ class OryAgentClient {
|
|
|
560
623
|
out.agentSubject = this._agentPrincipal.subject;
|
|
561
624
|
return out;
|
|
562
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* When a Keto call is auth-rejected while it was authenticated by something
|
|
628
|
+
* other than an Ory Network project API key (`ory_pat_…`) — e.g. a DCR
|
|
629
|
+
* OAuth2 access token — the credential *type* is wrong, not merely expired.
|
|
630
|
+
* Ory Network's Permission / Relationship APIs only accept a project API
|
|
631
|
+
* key. Emit an actionable hint so the failure isn't misread as a stale
|
|
632
|
+
* session (the generic `session_inactive` classification of the raw 401).
|
|
633
|
+
*/
|
|
634
|
+
warnIfKetoCredentialMismatch(oryErr) {
|
|
635
|
+
if (oryErr.code !== "session_inactive" && oryErr.code !== "forbidden")
|
|
636
|
+
return;
|
|
637
|
+
const ketoToken = this._adminApiKey ?? this._agentPrincipal.token;
|
|
638
|
+
// A real project API key is already in use — treat as a genuine auth error.
|
|
639
|
+
if (typeof ketoToken === "string" && ketoToken.startsWith("ory_pat_"))
|
|
640
|
+
return;
|
|
641
|
+
this.logger.warn("permission.credential_mismatch", {
|
|
642
|
+
code: oryErr.code,
|
|
643
|
+
message: "Ory Network's Permission/Relationship APIs require a project API key " +
|
|
644
|
+
"(ory_pat_…); the agent's OAuth2 access token is not accepted. Create a " +
|
|
645
|
+
"key in the Ory Console (Project settings → API keys) and set it with " +
|
|
646
|
+
"`configure --api-key <key>` or the ORY_AGENT_API_KEY env var.",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
563
649
|
// ─── Error Classification ────────────────────────────────────────
|
|
564
650
|
/**
|
|
565
651
|
* Classify an error from any Ory API call into a structured OryError.
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
* rather than re-implementing the Console API: the CLI already owns Network
|
|
24
24
|
* browser login + account creation, workspace/project listing, and OAuth2
|
|
25
25
|
* client creation, and the client it creates is exactly the one the plugin
|
|
26
|
-
* READMEs document for manual setup.
|
|
26
|
+
* READMEs document for manual setup. The one exception is minting the project
|
|
27
|
+
* API key that runtime permission checks need — the CLI exposes no command for
|
|
28
|
+
* it, so that single step calls the Console API directly, reusing the CLI's
|
|
29
|
+
* stored session for auth (see {@link createProjectApiKeyViaConsole}).
|
|
27
30
|
*/
|
|
28
31
|
import { OryAgentClient } from "./client.js";
|
|
29
32
|
import { ensureUserAuthenticated } from "./user-login.js";
|
|
@@ -83,6 +86,20 @@ export interface InteractiveSetupDeps {
|
|
|
83
86
|
* failed. Defaults to a real `npm install` of {@link ORY_CLI_NPM_SPEC}.
|
|
84
87
|
*/
|
|
85
88
|
installOryCliFn?: () => Promise<OryCliRunner | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Injectable project-API-key minter. Returns the `ory_pat_…` value or null on
|
|
91
|
+
* any failure. Defaults to {@link createProjectApiKeyViaConsole} (Ory Console
|
|
92
|
+
* API, authenticated with the `ory` CLI's stored session). Tests stub this to
|
|
93
|
+
* avoid real HTTP.
|
|
94
|
+
*/
|
|
95
|
+
createProjectApiKeyFn?: (args: CreateProjectApiKeyArgs) => Promise<string | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Injectable validity probe for an already-stored project API key. Returns
|
|
98
|
+
* true when the key still authenticates against Keto, false when it's been
|
|
99
|
+
* revoked/deleted (so provisioning re-mints one). Defaults to
|
|
100
|
+
* {@link apiKeyAuthenticates}. Tests stub this to avoid real HTTP.
|
|
101
|
+
*/
|
|
102
|
+
validateApiKeyFn?: (apiKey: string, projectUrl: string) => Promise<boolean>;
|
|
86
103
|
}
|
|
87
104
|
export type InteractiveSetupOutcome = "skipped_flag" | "skipped_no_tty" | "skipped_configured" | "audit_only" | "local_configured" | "network_configured" | "network_fallback";
|
|
88
105
|
export interface InteractiveSetupResult {
|
|
@@ -121,3 +138,28 @@ export declare function runInteractiveSetup(binName: string, harness: string, ar
|
|
|
121
138
|
* silently misconfigure the plugin.
|
|
122
139
|
*/
|
|
123
140
|
export declare function projectUrlFromSlug(slug: string): string;
|
|
141
|
+
export interface CreateProjectApiKeyArgs {
|
|
142
|
+
projectId: string;
|
|
143
|
+
/** Human-readable label shown in the Console's API-keys list. */
|
|
144
|
+
name: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Mint an Ory Network **project API key** via the Console API
|
|
148
|
+
* (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
|
|
149
|
+
* Returns the `ory_pat_…` value, or null on any failure (no session, non-2xx,
|
|
150
|
+
* missing value). Never throws — provisioning is best-effort.
|
|
151
|
+
*
|
|
152
|
+
* This is the one place the wizard reaches past the `ory` CLI to the Console
|
|
153
|
+
* API: the CLI (as of v1.3) exposes no project-API-key command, and Keto
|
|
154
|
+
* permission checks can't authenticate without one.
|
|
155
|
+
*/
|
|
156
|
+
export declare function createProjectApiKeyViaConsole(args: CreateProjectApiKeyArgs): Promise<string | null>;
|
|
157
|
+
/**
|
|
158
|
+
* Best-effort check that an already-stored project API key still authenticates
|
|
159
|
+
* against Keto. Runs one permission check with the key: if it completes (any
|
|
160
|
+
* `allowed` value) the key is valid; an auth rejection (`session_inactive` /
|
|
161
|
+
* `forbidden` / `session_aal2_required`) means it was revoked or deleted, so we
|
|
162
|
+
* return false and the caller re-provisions. Transient failures (network /
|
|
163
|
+
* rate-limit / unknown) return true so we don't re-mint on a blip. Never throws.
|
|
164
|
+
*/
|
|
165
|
+
export declare function apiKeyAuthenticates(apiKey: string, projectUrl: string): Promise<boolean>;
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
* rather than re-implementing the Console API: the CLI already owns Network
|
|
25
25
|
* browser login + account creation, workspace/project listing, and OAuth2
|
|
26
26
|
* client creation, and the client it creates is exactly the one the plugin
|
|
27
|
-
* READMEs document for manual setup.
|
|
27
|
+
* READMEs document for manual setup. The one exception is minting the project
|
|
28
|
+
* API key that runtime permission checks need — the CLI exposes no command for
|
|
29
|
+
* it, so that single step calls the Console API directly, reusing the CLI's
|
|
30
|
+
* stored session for auth (see {@link createProjectApiKeyViaConsole}).
|
|
28
31
|
*/
|
|
29
32
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
30
33
|
if (k2 === undefined) k2 = k;
|
|
@@ -65,6 +68,8 @@ exports.createOryRunner = createOryRunner;
|
|
|
65
68
|
exports.runPostInstall = runPostInstall;
|
|
66
69
|
exports.runInteractiveSetup = runInteractiveSetup;
|
|
67
70
|
exports.projectUrlFromSlug = projectUrlFromSlug;
|
|
71
|
+
exports.createProjectApiKeyViaConsole = createProjectApiKeyViaConsole;
|
|
72
|
+
exports.apiKeyAuthenticates = apiKeyAuthenticates;
|
|
68
73
|
const node_child_process_1 = require("node:child_process");
|
|
69
74
|
const fs = __importStar(require("node:fs"));
|
|
70
75
|
const os = __importStar(require("node:os"));
|
|
@@ -601,6 +606,12 @@ async function configureNetwork(binName, harness, ctx) {
|
|
|
601
606
|
if (subject) {
|
|
602
607
|
await bootstrapPermissionsViaOry(runner, project.id, harness, subject, prompt);
|
|
603
608
|
}
|
|
609
|
+
// 10. Provision the project API key runtime permission *checks* need. Same
|
|
610
|
+
// root cause as bootstrap's admin path — the DCR OAuth2 token can't
|
|
611
|
+
// authenticate Ory Network's Permission API — but this credential is used
|
|
612
|
+
// at runtime, so it's persisted to config. Placed last so its prompt
|
|
613
|
+
// doesn't consume answers meant for earlier steps.
|
|
614
|
+
await maybeProvisionProjectApiKey(binName, harness, project.id, projectUrl, prompt, ctx.deps.createProjectApiKeyFn ?? createProjectApiKeyViaConsole, ctx.deps.validateApiKeyFn ?? apiKeyAuthenticates);
|
|
604
615
|
return {
|
|
605
616
|
outcome: "network_configured",
|
|
606
617
|
projectUrl,
|
|
@@ -1317,3 +1328,171 @@ function printManualOAuth2ClientHelp(binName, projectId) {
|
|
|
1317
1328
|
ui.blank();
|
|
1318
1329
|
ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} configure --oauth2-client-id <CLIENT_ID>`);
|
|
1319
1330
|
}
|
|
1331
|
+
/** Ory Console API base. The `ory` CLI has no project-API-key command, so key
|
|
1332
|
+
* provisioning talks to the Console API directly, authenticated with the CLI's
|
|
1333
|
+
* stored Ory Network session. Override with `ORY_CONSOLE_API_URL`. */
|
|
1334
|
+
const CONSOLE_API_URL = process.env.ORY_CONSOLE_API_URL?.trim() || "https://api.console.ory.sh";
|
|
1335
|
+
/** Path to the session the `ory` CLI persists after `ory auth`. */
|
|
1336
|
+
const ORY_CLI_SESSION_FILE = ".ory-cloud.json";
|
|
1337
|
+
/**
|
|
1338
|
+
* Read the Ory Network session bearer the `ory` CLI stores at
|
|
1339
|
+
* `~/.ory-cloud.json`. Returns null when the file is absent, unparseable, has
|
|
1340
|
+
* no token, or the token has expired. Never throws.
|
|
1341
|
+
*/
|
|
1342
|
+
function readConsoleSessionToken() {
|
|
1343
|
+
try {
|
|
1344
|
+
const raw = fs.readFileSync(path.join(os.homedir(), ORY_CLI_SESSION_FILE), "utf-8");
|
|
1345
|
+
const parsed = JSON.parse(raw);
|
|
1346
|
+
const token = parsed.access_token?.access_token;
|
|
1347
|
+
if (!token)
|
|
1348
|
+
return null;
|
|
1349
|
+
const expiry = parsed.access_token?.expiry;
|
|
1350
|
+
if (expiry) {
|
|
1351
|
+
const expMs = Date.parse(expiry);
|
|
1352
|
+
if (Number.isFinite(expMs) && expMs <= Date.now())
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
return token;
|
|
1356
|
+
}
|
|
1357
|
+
catch {
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Mint an Ory Network **project API key** via the Console API
|
|
1363
|
+
* (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
|
|
1364
|
+
* Returns the `ory_pat_…` value, or null on any failure (no session, non-2xx,
|
|
1365
|
+
* missing value). Never throws — provisioning is best-effort.
|
|
1366
|
+
*
|
|
1367
|
+
* This is the one place the wizard reaches past the `ory` CLI to the Console
|
|
1368
|
+
* API: the CLI (as of v1.3) exposes no project-API-key command, and Keto
|
|
1369
|
+
* permission checks can't authenticate without one.
|
|
1370
|
+
*/
|
|
1371
|
+
async function createProjectApiKeyViaConsole(args) {
|
|
1372
|
+
const token = readConsoleSessionToken();
|
|
1373
|
+
if (!token)
|
|
1374
|
+
return null;
|
|
1375
|
+
try {
|
|
1376
|
+
const res = await fetch(`${CONSOLE_API_URL}/projects/${args.projectId}/tokens`, {
|
|
1377
|
+
method: "POST",
|
|
1378
|
+
headers: {
|
|
1379
|
+
Authorization: `Bearer ${token}`,
|
|
1380
|
+
"Content-Type": "application/json",
|
|
1381
|
+
},
|
|
1382
|
+
body: JSON.stringify({ name: args.name }),
|
|
1383
|
+
});
|
|
1384
|
+
if (!res.ok)
|
|
1385
|
+
return null;
|
|
1386
|
+
const body = (await res.json());
|
|
1387
|
+
return typeof body.value === "string" && body.value.length > 0 ? body.value : null;
|
|
1388
|
+
}
|
|
1389
|
+
catch {
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Best-effort check that an already-stored project API key still authenticates
|
|
1395
|
+
* against Keto. Runs one permission check with the key: if it completes (any
|
|
1396
|
+
* `allowed` value) the key is valid; an auth rejection (`session_inactive` /
|
|
1397
|
+
* `forbidden` / `session_aal2_required`) means it was revoked or deleted, so we
|
|
1398
|
+
* return false and the caller re-provisions. Transient failures (network /
|
|
1399
|
+
* rate-limit / unknown) return true so we don't re-mint on a blip. Never throws.
|
|
1400
|
+
*/
|
|
1401
|
+
async function apiKeyAuthenticates(apiKey, projectUrl) {
|
|
1402
|
+
try {
|
|
1403
|
+
// Direct construction (no fromEnv) seeds the key as both agent token and
|
|
1404
|
+
// admin key and skips DCR, so the probe uses exactly this key. No trace
|
|
1405
|
+
// file is configured, so nothing is written to disk.
|
|
1406
|
+
const probe = new client_js_1.OryAgentClient({ projectUrl, apiKey, harness: "install-probe" });
|
|
1407
|
+
await probe.checkPermission({
|
|
1408
|
+
namespace: process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools",
|
|
1409
|
+
object: "__ory_agent_key_probe__",
|
|
1410
|
+
relation: "__ory_agent_key_probe__",
|
|
1411
|
+
subjectId: "__ory_agent_key_probe__",
|
|
1412
|
+
});
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
catch (err) {
|
|
1416
|
+
const code = err?.code;
|
|
1417
|
+
if (code === "session_inactive" || code === "forbidden" || code === "session_aal2_required") {
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
return true;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Manual fallback steps for the project API key, shown when auto-provisioning
|
|
1425
|
+
* is declined or fails. The `configure --api-key` command is the stable part;
|
|
1426
|
+
* the Console link is a convenience.
|
|
1427
|
+
*/
|
|
1428
|
+
function printManualApiKeySteps(binName, projectId) {
|
|
1429
|
+
ui.info("Create one in the Ory Console (Project settings → API keys):");
|
|
1430
|
+
ui.command(`https://console.ory.sh/projects/${projectId}/settings`);
|
|
1431
|
+
ui.info("then store it so permission checks authenticate with it:");
|
|
1432
|
+
ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} configure --api-key <ory_pat_…>`);
|
|
1433
|
+
ui.hint("Observe mode works without it; enforce mode needs it to allow tools.");
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Provision the project API key that runtime Keto permission checks
|
|
1437
|
+
* authenticate with. The agent's DCR OAuth2 token can't — Ory Network rejects
|
|
1438
|
+
* it — so `enforce` mode needs a project API key. Best-effort and skippable:
|
|
1439
|
+
* offers to mint one via the Console API and persist it, and on decline / no
|
|
1440
|
+
* TTY / any failure falls back to manual guidance. No-ops when a key is already
|
|
1441
|
+
* configured (env or config file).
|
|
1442
|
+
*/
|
|
1443
|
+
async function maybeProvisionProjectApiKey(binName, harness, projectId, projectUrl, prompt, createFn, validateFn) {
|
|
1444
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
1445
|
+
if (resolved.apiKey) {
|
|
1446
|
+
// An operator-set env key is authoritative — never touch it.
|
|
1447
|
+
if (resolved.apiKeySource === "env")
|
|
1448
|
+
return;
|
|
1449
|
+
// A stored (config-file) key that no longer authenticates — revoked or
|
|
1450
|
+
// deleted — must not wedge setup: validate it and re-provision if it's
|
|
1451
|
+
// dead. Presence alone isn't enough (a dangling key still fails at runtime
|
|
1452
|
+
// with session_inactive).
|
|
1453
|
+
if (await validateFn(resolved.apiKey, projectUrl))
|
|
1454
|
+
return;
|
|
1455
|
+
ui.heading("Project API key");
|
|
1456
|
+
ui.warning("The stored project API key is no longer valid (revoked or deleted).");
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
ui.heading("Project API key");
|
|
1460
|
+
ui.info("Runtime permission checks call Ory's Permission API, which authenticates");
|
|
1461
|
+
ui.hint("with a project API key — the agent's OAuth2 token isn't accepted there.");
|
|
1462
|
+
ui.hint("(Observe mode works without it; enforce mode needs it to allow tools.)");
|
|
1463
|
+
}
|
|
1464
|
+
ui.blank();
|
|
1465
|
+
const answer = await prompt(ui.promptLine("Create a project API key now and store it?", { hint: "[Y/n]" }));
|
|
1466
|
+
// `null` = no TTY / cancelled; treat as skip so we never mint without consent.
|
|
1467
|
+
if (answer !== null && !isNo(answer)) {
|
|
1468
|
+
ui.step("Creating a project API key via the Ory Console…");
|
|
1469
|
+
let key = null;
|
|
1470
|
+
try {
|
|
1471
|
+
// Name each key uniquely (harness + host + timestamp) so keys are
|
|
1472
|
+
// distinguishable in the Console and a re-provision never collides with
|
|
1473
|
+
// an existing entry.
|
|
1474
|
+
key = await createFn({ projectId, name: projectApiKeyName(harness) });
|
|
1475
|
+
}
|
|
1476
|
+
catch {
|
|
1477
|
+
key = null;
|
|
1478
|
+
}
|
|
1479
|
+
if (key) {
|
|
1480
|
+
(0, config_js_1.saveConfig)({ apiKey: key });
|
|
1481
|
+
ui.success("Project API key created and stored — permission checks will use it.");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
ui.warning("Couldn't create a project API key automatically (Console session may be missing or expired).");
|
|
1485
|
+
}
|
|
1486
|
+
printManualApiKeySteps(binName, projectId);
|
|
1487
|
+
}
|
|
1488
|
+
/** Distinguishable, collision-free label for a provisioned project API key. */
|
|
1489
|
+
function projectApiKeyName(harness) {
|
|
1490
|
+
let host = "unknown-host";
|
|
1491
|
+
try {
|
|
1492
|
+
host = os.hostname();
|
|
1493
|
+
}
|
|
1494
|
+
catch {
|
|
1495
|
+
/* keep fallback */
|
|
1496
|
+
}
|
|
1497
|
+
return `ory-agent-plugins ${harness} ${host} ${new Date().toISOString()}`;
|
|
1498
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|