@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/local/health.d.ts
CHANGED
|
@@ -19,6 +19,20 @@ export declare function checkAllServices(options?: {
|
|
|
19
19
|
projectRoot?: string;
|
|
20
20
|
gatewayUrl?: string;
|
|
21
21
|
}): Promise<ServiceHealth[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Wait until every backend service the seed writes to reports ready, polling
|
|
24
|
+
* at the given interval. Returns `{ ready: true }` on success, or
|
|
25
|
+
* `{ ready: false, pending }` listing the services that never came up within
|
|
26
|
+
* the timeout.
|
|
27
|
+
*
|
|
28
|
+
* Call this before seeding: it closes the race where the gateway is green and
|
|
29
|
+
* containers are "running" but Kratos / Keto / Hydra are not yet serving on
|
|
30
|
+
* their admin/write ports.
|
|
31
|
+
*/
|
|
32
|
+
export declare function waitForSeedTargets(timeoutMs?: number, intervalMs?: number): Promise<{
|
|
33
|
+
ready: boolean;
|
|
34
|
+
pending: string[];
|
|
35
|
+
}>;
|
|
22
36
|
/**
|
|
23
37
|
* Wait for the gateway to become healthy, polling at the given interval.
|
|
24
38
|
* Returns true if healthy within the timeout, false otherwise.
|
package/dist/local/health.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.checkAllServices = checkAllServices;
|
|
7
|
+
exports.waitForSeedTargets = waitForSeedTargets;
|
|
7
8
|
exports.waitForGateway = waitForGateway;
|
|
8
9
|
const configs_js_1 = require("./configs.js");
|
|
9
10
|
async function checkEndpoint(name, url) {
|
|
@@ -38,6 +39,51 @@ async function checkAllServices(options = {}) {
|
|
|
38
39
|
];
|
|
39
40
|
return Promise.all(checks);
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Backend services the seed writes to, addressed at the exact host-published
|
|
44
|
+
* ports the seed uses (Kratos **admin**, Keto **write**, Hydra **admin**), so
|
|
45
|
+
* a green result means the seed's target APIs are actually accepting
|
|
46
|
+
* connections — not merely that the container is "running" or that the
|
|
47
|
+
* static nginx gateway `/health` is up.
|
|
48
|
+
*
|
|
49
|
+
* This is the readiness signal the gateway probe cannot provide: the gateway
|
|
50
|
+
* `/health` is a literal nginx `return 200` that never proxies a backend, and
|
|
51
|
+
* the Ory service containers declare no Docker healthcheck, so
|
|
52
|
+
* `docker compose up -d --wait` returns as soon as they are *started*, well
|
|
53
|
+
* before they are *serving*. Seeding against that gap intermittently fails to
|
|
54
|
+
* create identities and permission tuples on cold starts.
|
|
55
|
+
*/
|
|
56
|
+
function seedTargetServices() {
|
|
57
|
+
return [
|
|
58
|
+
{ name: "Kratos", url: `http://localhost:${configs_js_1.KRATOS_ADMIN_PORT}/health/ready` },
|
|
59
|
+
{ name: "Keto", url: `http://localhost:${configs_js_1.KETO_WRITE_PORT}/health/ready` },
|
|
60
|
+
{ name: "Hydra", url: `http://localhost:${configs_js_1.HYDRA_ADMIN_PORT}/health/ready` },
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wait until every backend service the seed writes to reports ready, polling
|
|
65
|
+
* at the given interval. Returns `{ ready: true }` on success, or
|
|
66
|
+
* `{ ready: false, pending }` listing the services that never came up within
|
|
67
|
+
* the timeout.
|
|
68
|
+
*
|
|
69
|
+
* Call this before seeding: it closes the race where the gateway is green and
|
|
70
|
+
* containers are "running" but Kratos / Keto / Hydra are not yet serving on
|
|
71
|
+
* their admin/write ports.
|
|
72
|
+
*/
|
|
73
|
+
async function waitForSeedTargets(timeoutMs = 60_000, intervalMs = 1000) {
|
|
74
|
+
const services = seedTargetServices();
|
|
75
|
+
const deadline = Date.now() + timeoutMs;
|
|
76
|
+
// eslint-disable-next-line no-constant-condition
|
|
77
|
+
while (true) {
|
|
78
|
+
const results = await Promise.all(services.map((s) => checkEndpoint(s.name, s.url)));
|
|
79
|
+
const pending = results.filter((r) => !r.healthy).map((r) => r.name);
|
|
80
|
+
if (pending.length === 0)
|
|
81
|
+
return { ready: true, pending: [] };
|
|
82
|
+
if (Date.now() >= deadline)
|
|
83
|
+
return { ready: false, pending };
|
|
84
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
41
87
|
/**
|
|
42
88
|
* Wait for the gateway to become healthy, polling at the given interval.
|
|
43
89
|
* Returns true if healthy within the timeout, false otherwise.
|
package/dist/local/manager.js
CHANGED
|
@@ -719,8 +719,7 @@ function printSeedResult(result) {
|
|
|
719
719
|
console.log("Seed complete!");
|
|
720
720
|
console.log("==============");
|
|
721
721
|
console.log("");
|
|
722
|
-
console.log(` Agent identity:
|
|
723
|
-
console.log(` (OAuth2 client self-registers on first run via DCR)`);
|
|
722
|
+
console.log(` Agent identity: self-registers on first run via DCR (nothing seeded)`);
|
|
724
723
|
console.log("");
|
|
725
724
|
console.log(` User identity: ${result.user.identity.email} (${result.user.identity.id})`);
|
|
726
725
|
console.log(` User password: ${result.user.password}`);
|
|
@@ -731,7 +730,6 @@ function printSeedResult(result) {
|
|
|
731
730
|
console.log("To use with any Ory agent plugin, set these environment variables:");
|
|
732
731
|
console.log("");
|
|
733
732
|
console.log(` export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
|
|
734
|
-
console.log(` export ORY_USER_LOGIN=true`);
|
|
735
733
|
console.log(` export ORY_OAUTH2_CLIENT_ID=${result.user.client.clientId}`);
|
|
736
734
|
console.log(` export ORY_USER_SUBJECT_NAMESPACE=User`);
|
|
737
735
|
console.log(` export ORY_USER_SUBJECT_ID=${result.user.identity.id}`);
|
package/dist/local/seed.d.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Seed the local Ory environment with test data for development.
|
|
3
3
|
*
|
|
4
|
-
* Creates
|
|
4
|
+
* Creates the **User** (`user@ory-local.dev`) — a human identity with a
|
|
5
|
+
* known password (printed by the launcher) and an OAuth2
|
|
6
|
+
* `authorization_code` + PKCE client registered against the loopback
|
|
7
|
+
* redirect URIs. The launcher always runs the real PKCE browser flow; the
|
|
8
|
+
* user types these credentials into the login UI to complete the flow.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 2. **User** (`user@ory-local.dev`) — a human identity with a known
|
|
11
|
-
* password (printed by the launcher) and an OAuth2
|
|
12
|
-
* `authorization_code` + PKCE client registered against the
|
|
13
|
-
* loopback redirect URIs. The launcher always runs the real PKCE
|
|
14
|
-
* browser flow; the user types these credentials into the login
|
|
15
|
-
* UI to complete the flow.
|
|
10
|
+
* No agent principal is seeded: the harness self-registers a DCR OAuth2
|
|
11
|
+
* client for the agent on first session start (using the user's bearer as
|
|
12
|
+
* the initial access token), and permission checks are made against the
|
|
13
|
+
* user subject — so a pre-seeded agent Kratos identity would be unused.
|
|
16
14
|
*
|
|
17
15
|
* Permission tuples are written as SubjectSet `User:<identity.id>` so
|
|
18
16
|
* the Console's *Add relationship* dialog (which always emits SubjectSet
|
|
@@ -39,15 +37,6 @@ export interface SeededOAuth2Client {
|
|
|
39
37
|
clientSecret?: string;
|
|
40
38
|
}
|
|
41
39
|
export interface SeedResult {
|
|
42
|
-
/**
|
|
43
|
-
* Agent (machine) identity — Kratos identity only. The agent's OAuth2
|
|
44
|
-
* client is *not* pre-registered: the harness self-registers via DCR
|
|
45
|
-
* (RFC 7591) on first session start using the user's bearer as the
|
|
46
|
-
* initial access token.
|
|
47
|
-
*/
|
|
48
|
-
agent: {
|
|
49
|
-
identity: SeededIdentity;
|
|
50
|
-
};
|
|
51
40
|
/** User (human) identity — subject of permission checks. */
|
|
52
41
|
user: {
|
|
53
42
|
identity: SeededIdentity;
|
package/dist/local/seed.js
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Seed the local Ory environment with test data for development.
|
|
4
4
|
*
|
|
5
|
-
* Creates
|
|
5
|
+
* Creates the **User** (`user@ory-local.dev`) — a human identity with a
|
|
6
|
+
* known password (printed by the launcher) and an OAuth2
|
|
7
|
+
* `authorization_code` + PKCE client registered against the loopback
|
|
8
|
+
* redirect URIs. The launcher always runs the real PKCE browser flow; the
|
|
9
|
+
* user types these credentials into the login UI to complete the flow.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 2. **User** (`user@ory-local.dev`) — a human identity with a known
|
|
12
|
-
* password (printed by the launcher) and an OAuth2
|
|
13
|
-
* `authorization_code` + PKCE client registered against the
|
|
14
|
-
* loopback redirect URIs. The launcher always runs the real PKCE
|
|
15
|
-
* browser flow; the user types these credentials into the login
|
|
16
|
-
* UI to complete the flow.
|
|
11
|
+
* No agent principal is seeded: the harness self-registers a DCR OAuth2
|
|
12
|
+
* client for the agent on first session start (using the user's bearer as
|
|
13
|
+
* the initial access token), and permission checks are made against the
|
|
14
|
+
* user subject — so a pre-seeded agent Kratos identity would be unused.
|
|
17
15
|
*
|
|
18
16
|
* Permission tuples are written as SubjectSet `User:<identity.id>` so
|
|
19
17
|
* the Console's *Add relationship* dialog (which always emits SubjectSet
|
|
@@ -30,11 +28,10 @@ exports.seedLocalEnvironment = seedLocalEnvironment;
|
|
|
30
28
|
const auth_js_1 = require("../auth.js");
|
|
31
29
|
const tool_catalog_js_1 = require("../tool-catalog.js");
|
|
32
30
|
const configs_js_1 = require("./configs.js");
|
|
31
|
+
const health_js_1 = require("./health.js");
|
|
33
32
|
const KRATOS_ADMIN = `http://localhost:${configs_js_1.KRATOS_ADMIN_PORT}`;
|
|
34
33
|
const KETO_WRITE = `http://localhost:${configs_js_1.KETO_WRITE_PORT}`;
|
|
35
34
|
const HYDRA_ADMIN = `http://localhost:${configs_js_1.HYDRA_ADMIN_PORT}`;
|
|
36
|
-
const AGENT_EMAIL = "agent@ory-local.dev";
|
|
37
|
-
const AGENT_PASSWORD = "ory-agent-local-dev-password!";
|
|
38
35
|
const USER_EMAIL = "user@ory-local.dev";
|
|
39
36
|
const USER_PASSWORD = "ory-user-local-dev-password!";
|
|
40
37
|
const USER_CLIENT_NAME = "ory-agent-plugins-local-user";
|
|
@@ -179,10 +176,22 @@ function loopbackRedirectUris() {
|
|
|
179
176
|
*/
|
|
180
177
|
async function seedLocalEnvironment(namespace = "AgentTools") {
|
|
181
178
|
process.stderr.write("Seeding local Ory environment...\n\n");
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
// 0. Readiness barrier. The seed writes directly to Kratos (admin),
|
|
180
|
+
// Keto (write), and Hydra (admin). Neither the static nginx gateway
|
|
181
|
+
// `/health` nor `docker compose up -d --wait` (the Ory containers
|
|
182
|
+
// declare no healthcheck) proves those APIs are actually serving, so
|
|
183
|
+
// on a cold start the writes can race ahead of the services and
|
|
184
|
+
// silently create no identities or permission tuples. Block until all
|
|
185
|
+
// three report ready before touching them.
|
|
186
|
+
process.stderr.write(" Waiting for backend services to be ready...\n");
|
|
187
|
+
const readiness = await (0, health_js_1.waitForSeedTargets)();
|
|
188
|
+
if (!readiness.ready) {
|
|
189
|
+
throw new Error(`backend services not ready before seed: ${readiness.pending.join(", ")} ` +
|
|
190
|
+
`did not report ready within the timeout. The stack may still be starting — retry in a moment.`);
|
|
191
|
+
}
|
|
192
|
+
// 1. User identity. No agent identity is seeded — the harness
|
|
193
|
+
// self-registers a DCR OAuth2 client for the agent on first session
|
|
194
|
+
// start, and permission checks target the user subject.
|
|
186
195
|
process.stderr.write(" Creating user identity...\n");
|
|
187
196
|
const userIdentity = await seedIdentity(USER_EMAIL, USER_PASSWORD, "Local User");
|
|
188
197
|
process.stderr.write(` User: ${userIdentity.id} (${userIdentity.email})\n`);
|
|
@@ -219,7 +228,6 @@ async function seedLocalEnvironment(namespace = "AgentTools") {
|
|
|
219
228
|
process.stderr.write(` Warning: user OAuth2 client registration failed: ${msg}\n`);
|
|
220
229
|
}
|
|
221
230
|
return {
|
|
222
|
-
agent: { identity: agentIdentity },
|
|
223
231
|
user: { identity: userIdentity, password: USER_PASSWORD, client: userClient },
|
|
224
232
|
permissions: { namespace, subject: subjectLabel, tuples: tupleCount },
|
|
225
233
|
};
|
package/dist/permissions-cli.js
CHANGED
|
@@ -258,7 +258,7 @@ async function runPermissionsBootstrap(binName, harness, args) {
|
|
|
258
258
|
console.error("No user identity resolved — refusing to write permissions for an unknown subject.");
|
|
259
259
|
console.error("");
|
|
260
260
|
console.error("Either:");
|
|
261
|
-
console.error(" - run the harness once
|
|
261
|
+
console.error(" - run the harness once so the user login caches a token, or");
|
|
262
262
|
console.error(" - set ORY_USER_SUBJECT_ID=<id> to target a known subject.");
|
|
263
263
|
return 1;
|
|
264
264
|
}
|
|
@@ -288,9 +288,17 @@ async function runPermissionsBootstrap(binName, harness, args) {
|
|
|
288
288
|
continue;
|
|
289
289
|
}
|
|
290
290
|
try {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
// Read-before-write: Keto's create API is not idempotent (every PUT
|
|
292
|
+
// inserts a new row and never 409s), so a plain createRelationship on
|
|
293
|
+
// re-install would duplicate the whole catalog. ensureRelationship
|
|
294
|
+
// probes for the exact tuple first and only writes when it is absent.
|
|
295
|
+
const spanAttributes = { toolName: tool, source: "permissions_bootstrap" };
|
|
296
|
+
let res = await client.ensureRelationship(check, { spanAttributes });
|
|
297
|
+
if (res.probeError) {
|
|
298
|
+
// Couldn't confirm existence — fall back to a direct write so a
|
|
299
|
+
// needed grant is still created (bootstrap's job is to guarantee it).
|
|
300
|
+
res = await client.createRelationship(check, { spanAttributes });
|
|
301
|
+
}
|
|
294
302
|
if (res.alreadyExisted) {
|
|
295
303
|
existed++;
|
|
296
304
|
console.log(` = ${tool} (already exists)`);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ory Network **project API key** (project token) lifecycle via the Ory
|
|
3
|
+
* Console API.
|
|
4
|
+
*
|
|
5
|
+
* Runtime permission checks authenticate to Ory's Permission (Keto) API with a
|
|
6
|
+
* project API key — the agent's OAuth2 token isn't accepted there — so the
|
|
7
|
+
* installer mints one. The `ory` CLI (as of v1.3) exposes no project-API-key
|
|
8
|
+
* command, so this is the one place the plugin reaches past the CLI to the
|
|
9
|
+
* Console API directly, authenticating with the CLI's stored Ory Network
|
|
10
|
+
* session (`~/.ory-cloud.json`).
|
|
11
|
+
*
|
|
12
|
+
* These helpers own the full lifecycle so both the installer (create) and the
|
|
13
|
+
* uninstaller (delete) speak to the same endpoint with the same auth. Every
|
|
14
|
+
* function is best-effort and never throws — provisioning and teardown must not
|
|
15
|
+
* crash on a missing session or a network blip.
|
|
16
|
+
*/
|
|
17
|
+
export interface CreateProjectApiKeyArgs {
|
|
18
|
+
projectId: string;
|
|
19
|
+
/** Human-readable label shown in the Console's API-keys list. */
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A minted project API key. `value` is the `ory_pat_…` secret (returned only at
|
|
24
|
+
* creation); `id` is the stable token id used to manage/delete the key later.
|
|
25
|
+
*/
|
|
26
|
+
export interface ProjectApiKeyResult {
|
|
27
|
+
value: string;
|
|
28
|
+
/** Token id, when the Console returned one — needed for later deletion. */
|
|
29
|
+
id?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Outcome of a best-effort server-side project-API-key deletion. */
|
|
32
|
+
export type ProjectApiKeyDeleteResult = {
|
|
33
|
+
status: "deleted";
|
|
34
|
+
}
|
|
35
|
+
/** Nothing to do server-side (no session, or no id to address the token). */
|
|
36
|
+
| {
|
|
37
|
+
status: "skipped";
|
|
38
|
+
message: string;
|
|
39
|
+
} | {
|
|
40
|
+
status: "failed";
|
|
41
|
+
message?: string;
|
|
42
|
+
httpStatus?: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Read the Ory Network session bearer the `ory` CLI stores at
|
|
46
|
+
* `~/.ory-cloud.json`. Returns null when the file is absent, unparseable, has
|
|
47
|
+
* no token, or the token has expired. Never throws.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readConsoleSessionToken(): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Mint an Ory Network **project API key** via the Console API
|
|
52
|
+
* (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
|
|
53
|
+
* Returns the `{ value, id }` pair, or null on any failure (no session, non-2xx,
|
|
54
|
+
* missing value). Never throws — provisioning is best-effort.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createProjectApiKeyViaConsole(args: CreateProjectApiKeyArgs): Promise<ProjectApiKeyResult | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Delete an Ory Network project API key via the Console API
|
|
59
|
+
* (`DELETE /projects/{projectId}/tokens/{tokenId}`), authenticated with the
|
|
60
|
+
* `ory` CLI session. Best-effort and never throws.
|
|
61
|
+
*
|
|
62
|
+
* A 404 (already gone) counts as `deleted` — the desired end state is reached.
|
|
63
|
+
* When no CLI session is available there is nothing we can do server-side, so
|
|
64
|
+
* the result is `skipped` (not a failure).
|
|
65
|
+
*/
|
|
66
|
+
export declare function deleteProjectApiKeyViaConsole(args: {
|
|
67
|
+
projectId: string;
|
|
68
|
+
tokenId: string;
|
|
69
|
+
}): Promise<ProjectApiKeyDeleteResult>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ory Network **project API key** (project token) lifecycle via the Ory
|
|
4
|
+
* Console API.
|
|
5
|
+
*
|
|
6
|
+
* Runtime permission checks authenticate to Ory's Permission (Keto) API with a
|
|
7
|
+
* project API key — the agent's OAuth2 token isn't accepted there — so the
|
|
8
|
+
* installer mints one. The `ory` CLI (as of v1.3) exposes no project-API-key
|
|
9
|
+
* command, so this is the one place the plugin reaches past the CLI to the
|
|
10
|
+
* Console API directly, authenticating with the CLI's stored Ory Network
|
|
11
|
+
* session (`~/.ory-cloud.json`).
|
|
12
|
+
*
|
|
13
|
+
* These helpers own the full lifecycle so both the installer (create) and the
|
|
14
|
+
* uninstaller (delete) speak to the same endpoint with the same auth. Every
|
|
15
|
+
* function is best-effort and never throws — provisioning and teardown must not
|
|
16
|
+
* crash on a missing session or a network blip.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.readConsoleSessionToken = readConsoleSessionToken;
|
|
53
|
+
exports.createProjectApiKeyViaConsole = createProjectApiKeyViaConsole;
|
|
54
|
+
exports.deleteProjectApiKeyViaConsole = deleteProjectApiKeyViaConsole;
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const os = __importStar(require("node:os"));
|
|
57
|
+
const path = __importStar(require("node:path"));
|
|
58
|
+
/** Base URL for the Ory Console API. Override with `ORY_CONSOLE_API_URL`. */
|
|
59
|
+
const CONSOLE_API_URL = process.env.ORY_CONSOLE_API_URL?.trim() || "https://api.console.ory.sh";
|
|
60
|
+
/** Path to the session the `ory` CLI persists after `ory auth`. */
|
|
61
|
+
const ORY_CLI_SESSION_FILE = ".ory-cloud.json";
|
|
62
|
+
/**
|
|
63
|
+
* Read the Ory Network session bearer the `ory` CLI stores at
|
|
64
|
+
* `~/.ory-cloud.json`. Returns null when the file is absent, unparseable, has
|
|
65
|
+
* no token, or the token has expired. Never throws.
|
|
66
|
+
*/
|
|
67
|
+
function readConsoleSessionToken() {
|
|
68
|
+
try {
|
|
69
|
+
const raw = fs.readFileSync(path.join(os.homedir(), ORY_CLI_SESSION_FILE), "utf-8");
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
const token = parsed.access_token?.access_token;
|
|
72
|
+
if (!token)
|
|
73
|
+
return null;
|
|
74
|
+
const expiry = parsed.access_token?.expiry;
|
|
75
|
+
if (expiry) {
|
|
76
|
+
const expMs = Date.parse(expiry);
|
|
77
|
+
if (Number.isFinite(expMs) && expMs <= Date.now())
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return token;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Mint an Ory Network **project API key** via the Console API
|
|
88
|
+
* (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
|
|
89
|
+
* Returns the `{ value, id }` pair, or null on any failure (no session, non-2xx,
|
|
90
|
+
* missing value). Never throws — provisioning is best-effort.
|
|
91
|
+
*/
|
|
92
|
+
async function createProjectApiKeyViaConsole(args) {
|
|
93
|
+
const token = readConsoleSessionToken();
|
|
94
|
+
if (!token)
|
|
95
|
+
return null;
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(`${CONSOLE_API_URL}/projects/${args.projectId}/tokens`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${token}`,
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({ name: args.name }),
|
|
104
|
+
});
|
|
105
|
+
if (!res.ok)
|
|
106
|
+
return null;
|
|
107
|
+
const body = (await res.json());
|
|
108
|
+
const value = typeof body.value === "string" && body.value.length > 0 ? body.value : null;
|
|
109
|
+
if (!value)
|
|
110
|
+
return null;
|
|
111
|
+
const id = typeof body.id === "string" && body.id.length > 0 ? body.id : undefined;
|
|
112
|
+
return { value, id };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Delete an Ory Network project API key via the Console API
|
|
120
|
+
* (`DELETE /projects/{projectId}/tokens/{tokenId}`), authenticated with the
|
|
121
|
+
* `ory` CLI session. Best-effort and never throws.
|
|
122
|
+
*
|
|
123
|
+
* A 404 (already gone) counts as `deleted` — the desired end state is reached.
|
|
124
|
+
* When no CLI session is available there is nothing we can do server-side, so
|
|
125
|
+
* the result is `skipped` (not a failure).
|
|
126
|
+
*/
|
|
127
|
+
async function deleteProjectApiKeyViaConsole(args) {
|
|
128
|
+
const token = readConsoleSessionToken();
|
|
129
|
+
if (!token) {
|
|
130
|
+
return {
|
|
131
|
+
status: "skipped",
|
|
132
|
+
message: "no Ory Console session (run `ory auth` to enable server-side removal)",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch(`${CONSOLE_API_URL}/projects/${args.projectId}/tokens/${args.tokenId}`, {
|
|
137
|
+
method: "DELETE",
|
|
138
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
139
|
+
});
|
|
140
|
+
if (res.ok || res.status === 404)
|
|
141
|
+
return { status: "deleted" };
|
|
142
|
+
return { status: "failed", httpStatus: res.status };
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return { status: "failed", message: err?.message };
|
|
146
|
+
}
|
|
147
|
+
}
|
package/dist/registry/manager.js
CHANGED
|
@@ -139,32 +139,72 @@ function readPackageName(pkgJsonPath) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
142
|
+
* Compute a unique local-dev version for a publish run: the base version's
|
|
143
|
+
* `major.minor.patch` with a `-dev.<timestamp>` prerelease suffix (e.g.
|
|
144
|
+
* `0.13.9-dev.1721710000000`). All `@ory/*` packages share one version (the
|
|
145
|
+
* changesets `fixed` group), so we read the base from the first package and
|
|
146
|
+
* stamp the same value onto every package — keeping `workspace:*` cross-deps
|
|
147
|
+
* resolvable to the just-published version.
|
|
144
148
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* `
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
* Why a fresh version every publish: `npx`/pacote cache installed trees and
|
|
150
|
+
* tarballs by `name@version`. Republishing the *same* version over a rebuilt
|
|
151
|
+
* tarball lets a client's `~/.npm/_npx` or `_cacache` serve stale bytes even
|
|
152
|
+
* after Verdaccio has the fresh code. A new version each run guarantees a
|
|
153
|
+
* cache miss so `@latest` always resolves to what we just built.
|
|
154
|
+
*/
|
|
155
|
+
function makeLocalDevVersion(pkgJsonFiles, stamp) {
|
|
156
|
+
let base = "0.0.0";
|
|
157
|
+
for (const file of pkgJsonFiles) {
|
|
158
|
+
try {
|
|
159
|
+
const v = JSON.parse(fs.readFileSync(file, "utf-8")).version;
|
|
160
|
+
if (v) {
|
|
161
|
+
base = v;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// ignore unreadable package.json; fall back to the next one
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Strip any existing prerelease/build suffix so we don't stack `-dev` tags.
|
|
170
|
+
const core = base.split(/[-+]/)[0];
|
|
171
|
+
return `${core}-dev.${stamp}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Prepare every `@ory/*` package.json for a local publish for the duration of
|
|
175
|
+
* `fn`, then restore each file exactly as it was. Two transforms are applied:
|
|
176
|
+
*
|
|
177
|
+
* 1. **Strip `publishConfig.registry` / `publishConfig.provenance`.** The OIDC
|
|
178
|
+
* trusted-publishing release workflow pins each package's
|
|
179
|
+
* `publishConfig.registry` to `https://registry.npmjs.org/` with
|
|
180
|
+
* `provenance: true`. `publishConfig` overrides both the `--registry` CLI
|
|
181
|
+
* flag and the `npm_config_registry` env var, so without this transform a
|
|
182
|
+
* local publish still targets real npmjs.org and fails with `E404
|
|
183
|
+
* undefined` (no auth) / provenance errors.
|
|
184
|
+
*
|
|
185
|
+
* 2. **Stamp a unique `-dev.<timestamp>` version** (see
|
|
186
|
+
* {@link makeLocalDevVersion}) so `@latest` changes on every publish and
|
|
187
|
+
* clients can't serve a stale cached tarball for a version we already used.
|
|
188
|
+
*
|
|
189
|
+
* Both are on-disk only for the publish call; the files are put back exactly as
|
|
190
|
+
* they were so the CI release workflow (and git) keep working.
|
|
154
191
|
*/
|
|
155
192
|
async function withLocalPublishConfig(workspaceRoot, fn) {
|
|
156
193
|
const files = listWorkspaceScopedPackageJsons(workspaceRoot);
|
|
194
|
+
const stamp = Date.now();
|
|
195
|
+
const devVersion = makeLocalDevVersion(files, stamp);
|
|
157
196
|
const backups = new Map();
|
|
158
197
|
for (const file of files) {
|
|
159
198
|
const original = fs.readFileSync(file, "utf-8");
|
|
160
199
|
const pkg = JSON.parse(original);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
200
|
+
const next = { ...pkg, version: devVersion };
|
|
201
|
+
if (pkg.publishConfig) {
|
|
202
|
+
const stripped = { ...pkg.publishConfig };
|
|
203
|
+
delete stripped.registry;
|
|
204
|
+
delete stripped.provenance;
|
|
205
|
+
next.publishConfig = stripped;
|
|
206
|
+
}
|
|
166
207
|
backups.set(file, original);
|
|
167
|
-
const next = { ...pkg, publishConfig: stripped };
|
|
168
208
|
fs.writeFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
169
209
|
}
|
|
170
210
|
const restore = () => {
|
|
@@ -461,7 +501,7 @@ async function publishAllOryToLocal(opts = {}) {
|
|
|
461
501
|
const { name, version } = readPackageNameVersion(pkgJsonPath);
|
|
462
502
|
const pkgName = name ?? path.basename(pkgDir);
|
|
463
503
|
if (!opts.quiet) {
|
|
464
|
-
console.log(`Publishing ${pkgName}...`);
|
|
504
|
+
console.log(`Publishing ${pkgName}@${version}...`);
|
|
465
505
|
}
|
|
466
506
|
const packResult = (0, node_child_process_1.spawnSync)("pnpm", ["pack", "--pack-destination", packDir], {
|
|
467
507
|
cwd: pkgDir,
|
|
@@ -495,6 +535,10 @@ async function publishAllOryToLocal(opts = {}) {
|
|
|
495
535
|
config_js_1.REGISTRY_URL,
|
|
496
536
|
...scopeRegistryArg,
|
|
497
537
|
"--access=public",
|
|
538
|
+
// Explicit so a `-dev.<ts>` prerelease still becomes `@latest` in the
|
|
539
|
+
// local registry (some npm versions won't auto-tag prereleases).
|
|
540
|
+
"--tag",
|
|
541
|
+
"latest",
|
|
498
542
|
], {
|
|
499
543
|
cwd: pkgDir,
|
|
500
544
|
stdio: opts.quiet ? "pipe" : "inherit",
|