@ory/argus 0.13.9 → 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 +12 -5
- package/dist/adapters.js +59 -10
- 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/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 +11 -4
- package/dist/index.js +39 -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 +1 -1
- 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/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/uninstall.js
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Config teardown shared by every plugin's `uninstall` command.
|
|
4
4
|
*
|
|
5
|
-
* Uninstalling a plugin
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* inheriting stale
|
|
5
|
+
* Uninstalling a plugin wipes the shared config file completely — every
|
|
6
|
+
* value is removed, both *authentication* state (user tokens, agent DCR
|
|
7
|
+
* credentials, static API key) and *configuration* (project URL, OAuth2
|
|
8
|
+
* client id, audit mode, permission mode, user-login flag). The file is
|
|
9
|
+
* left as an empty object, so a later re-install starts from a clean
|
|
10
|
+
* slate rather than inheriting any stale state.
|
|
11
11
|
*
|
|
12
12
|
* What is removed:
|
|
13
13
|
* - the user's persisted OAuth2 tokens (`user`)
|
|
14
14
|
* - the agent's dynamic-registration credentials and every sub-agent
|
|
15
15
|
* registration (`agent`)
|
|
16
16
|
* - the operator-supplied static agent API key (`apiKey`)
|
|
17
|
+
* - all configuration values (`projectUrl`, `projectId`, `projectName`,
|
|
18
|
+
* `workspaceName`, `oauth2ClientId`, `auditOnly`, `permissionMode`,
|
|
19
|
+
* `userLogin`, `userSubjectNamespace`)
|
|
17
20
|
* - any dangling PKCE-flight lock
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Ory project isn't left
|
|
22
|
-
* install/uninstall cycles.
|
|
23
|
-
*
|
|
22
|
+
* Server-side teardown runs first, best-effort:
|
|
23
|
+
* - Each dynamically-registered OAuth2 client (the agent and every
|
|
24
|
+
* sub-agent) is revoked via RFC 7592, so the Ory project isn't left
|
|
25
|
+
* with orphaned clients accumulating across install/uninstall cycles.
|
|
26
|
+
* - The project API key the installer minted is deleted via the Ory
|
|
27
|
+
* Console API (addressed by the persisted token id).
|
|
28
|
+
*
|
|
29
|
+
* The one server-side resource deliberately **left behind** is the shared
|
|
30
|
+
* user-login PKCE OAuth2 client (`oauth2ClientId`): it is a public,
|
|
31
|
+
* identity-free client that every user and every harness install on the
|
|
32
|
+
* project reuses, so deleting it would break other installations. It is
|
|
33
|
+
* only forgotten locally (its id is wiped from the config file). Every
|
|
34
|
+
* server-side step is best-effort: failures warn and local state is
|
|
35
|
+
* cleared regardless.
|
|
24
36
|
*/
|
|
25
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
38
|
exports.clearCredentialsForUninstall = clearCredentialsForUninstall;
|
|
27
39
|
const config_js_1 = require("./config.js");
|
|
28
40
|
const agent_auth_js_1 = require("./agent-auth.js");
|
|
29
41
|
const auth_store_js_1 = require("./auth-store.js");
|
|
42
|
+
const project_api_key_js_1 = require("./project-api-key.js");
|
|
30
43
|
/**
|
|
31
|
-
*
|
|
32
|
-
* dynamically-registered clients server-side
|
|
33
|
-
*
|
|
44
|
+
* Wipe the shared config file completely — every value, both credentials
|
|
45
|
+
* and configuration — revoking dynamically-registered clients server-side
|
|
46
|
+
* first. The file is left as an empty object. Never throws.
|
|
34
47
|
*/
|
|
35
48
|
async function clearCredentialsForUninstall(options = {}) {
|
|
36
49
|
const log = options.log ?? ((msg) => console.log(msg));
|
|
@@ -40,12 +53,13 @@ async function clearCredentialsForUninstall(options = {}) {
|
|
|
40
53
|
const subAgents = config.agent?.subAgents ?? {};
|
|
41
54
|
const subAgentEntries = Object.entries(subAgents);
|
|
42
55
|
const hadCredentials = Boolean(config.user || dynamic || subAgentEntries.length > 0 || config.apiKey);
|
|
43
|
-
if (
|
|
44
|
-
log("No
|
|
45
|
-
return { hadCredentials: false, revoked: 0, revokeFailures: 0 };
|
|
56
|
+
if (Object.keys(config).length === 0) {
|
|
57
|
+
log("No Ory configuration to remove.");
|
|
58
|
+
return { hadCredentials: false, revoked: 0, revokeFailures: 0, apiKeyDeleted: false };
|
|
46
59
|
}
|
|
47
60
|
let revoked = 0;
|
|
48
61
|
let revokeFailures = 0;
|
|
62
|
+
let apiKeyDeleted = false;
|
|
49
63
|
if (revoke) {
|
|
50
64
|
const targets = [];
|
|
51
65
|
if (dynamic) {
|
|
@@ -81,10 +95,39 @@ async function clearCredentialsForUninstall(options = {}) {
|
|
|
81
95
|
log(` Warning: server-side revoke of ${target.label} ${target.clientId} failed (${detail}). Clearing local state anyway.`);
|
|
82
96
|
}
|
|
83
97
|
}
|
|
98
|
+
// Delete the project API key the installer minted. Deletion addresses the
|
|
99
|
+
// token by its id (its secret value can't be used), so it needs both the
|
|
100
|
+
// project id and the persisted token id. A key present without a tracked id
|
|
101
|
+
// (minted before id-tracking, or supplied by hand via `configure --api-key`)
|
|
102
|
+
// can't be removed automatically — warn so it can be pruned in the Console.
|
|
103
|
+
if (config.apiKey) {
|
|
104
|
+
if (config.projectId && config.apiKeyId) {
|
|
105
|
+
const outcome = await (0, project_api_key_js_1.deleteProjectApiKeyViaConsole)({
|
|
106
|
+
projectId: config.projectId,
|
|
107
|
+
tokenId: config.apiKeyId,
|
|
108
|
+
});
|
|
109
|
+
if (outcome.status === "deleted") {
|
|
110
|
+
apiKeyDeleted = true;
|
|
111
|
+
log(" Deleted the project API key.");
|
|
112
|
+
}
|
|
113
|
+
else if (outcome.status === "skipped") {
|
|
114
|
+
log(` Could not delete the project API key (${outcome.message}). Clearing local state anyway.`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const detail = typeof outcome.httpStatus === "number"
|
|
118
|
+
? `HTTP ${outcome.httpStatus}`
|
|
119
|
+
: (outcome.message ?? "unknown error");
|
|
120
|
+
log(` Warning: server-side deletion of the project API key failed (${detail}). Clearing local state anyway.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
log(" The stored project API key has no tracked token id — remove it manually in the Ory Console if it's no longer needed. Clearing local state anyway.");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
84
127
|
}
|
|
85
|
-
//
|
|
86
|
-
(0, config_js_1.
|
|
128
|
+
// Wipe the entire config file — credentials and configuration alike.
|
|
129
|
+
(0, config_js_1.mutateConfig)(() => ({}));
|
|
87
130
|
(0, auth_store_js_1.clearPkceFlightLock)();
|
|
88
|
-
log("Removed
|
|
89
|
-
return { hadCredentials
|
|
131
|
+
log("Removed all Ory configuration and credentials.");
|
|
132
|
+
return { hadCredentials, revoked, revokeFailures, apiKeyDeleted };
|
|
90
133
|
}
|
package/dist/user-login.d.ts
CHANGED
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
* Every terminal path emits exactly one `user.auth` trace span so that
|
|
18
18
|
* the audit trail is complete regardless of outcome.
|
|
19
19
|
*
|
|
20
|
-
* The
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* The flow runs on **every** session and is **never** blocking: it
|
|
21
|
+
* establishes or refreshes the user identity for attribution, delegation,
|
|
22
|
+
* and permission-subject resolution, but a failure to authenticate never
|
|
23
|
+
* stops the session. Enforcement is governed solely by `permissionMode`
|
|
24
|
+
* (observe/enforce) at tool-call time — see `checkAndDecide`. The one
|
|
25
|
+
* exception is `auditOnly`, which disables auth entirely (mode
|
|
26
|
+
* `audit_only`). When the user can't be authenticated the session still
|
|
27
|
+
* proceeds; under `enforce` a missing user identity naturally denies at
|
|
28
|
+
* the tool (no matching tuples), and under `observe` it is audited.
|
|
25
29
|
*
|
|
26
30
|
* This authenticates the *user* (the human at the keyboard). The
|
|
27
31
|
* separate agent identity (the AI process making the calls) is resolved
|
|
@@ -32,14 +36,12 @@ import { OryAgentClient } from "./client.js";
|
|
|
32
36
|
import { type OryOAuth2Tokens } from "./config.js";
|
|
33
37
|
import { promptForProjectUrl } from "./cli.js";
|
|
34
38
|
import { pkceLogin } from "./auth.js";
|
|
35
|
-
export type UserLoginMode = "
|
|
39
|
+
export type UserLoginMode = "audit_only" | "env_token" | "ok" | "refreshed" | "skipped" | "declined" | "error";
|
|
36
40
|
export interface UserLoginOptions {
|
|
37
41
|
/** Bin name used in user-facing prompts (e.g. "ory-claude"). */
|
|
38
42
|
binName: string;
|
|
39
43
|
/** Logical harness name (used by the tracer). */
|
|
40
44
|
harness: string;
|
|
41
|
-
/** Whether the caller can hard-block on auth failure. */
|
|
42
|
-
allowBlock: boolean;
|
|
43
45
|
/**
|
|
44
46
|
* Optional override of the PKCE login function — used by tests and by
|
|
45
47
|
* harnesses that want to plug in a different flow.
|
|
@@ -51,7 +53,12 @@ export interface UserLoginOptions {
|
|
|
51
53
|
promptForProjectUrlFn?: typeof promptForProjectUrl;
|
|
52
54
|
}
|
|
53
55
|
export interface UserLoginDecision {
|
|
54
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Whether the caller should let the session proceed. User authentication
|
|
58
|
+
* is non-blocking, so this is **always** `true` — it is retained so the
|
|
59
|
+
* session-start adapters keep a stable shape. Enforcement lives in
|
|
60
|
+
* `permissionMode` at tool-call time, not here.
|
|
61
|
+
*/
|
|
55
62
|
proceed: boolean;
|
|
56
63
|
/** Stable mode identifier for telemetry and tests. */
|
|
57
64
|
mode: UserLoginMode;
|
package/dist/user-login.js
CHANGED
|
@@ -18,11 +18,15 @@
|
|
|
18
18
|
* Every terminal path emits exactly one `user.auth` trace span so that
|
|
19
19
|
* the audit trail is complete regardless of outcome.
|
|
20
20
|
*
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* The flow runs on **every** session and is **never** blocking: it
|
|
22
|
+
* establishes or refreshes the user identity for attribution, delegation,
|
|
23
|
+
* and permission-subject resolution, but a failure to authenticate never
|
|
24
|
+
* stops the session. Enforcement is governed solely by `permissionMode`
|
|
25
|
+
* (observe/enforce) at tool-call time — see `checkAndDecide`. The one
|
|
26
|
+
* exception is `auditOnly`, which disables auth entirely (mode
|
|
27
|
+
* `audit_only`). When the user can't be authenticated the session still
|
|
28
|
+
* proceeds; under `enforce` a missing user identity naturally denies at
|
|
29
|
+
* the tool (no matching tuples), and under `observe` it is audited.
|
|
26
30
|
*
|
|
27
31
|
* This authenticates the *user* (the human at the keyboard). The
|
|
28
32
|
* separate agent identity (the AI process making the calls) is resolved
|
|
@@ -37,12 +41,6 @@ const cli_js_1 = require("./cli.js");
|
|
|
37
41
|
const cli_invocation_js_1 = require("./cli-invocation.js");
|
|
38
42
|
const auth_store_js_1 = require("./auth-store.js");
|
|
39
43
|
const auth_js_1 = require("./auth.js");
|
|
40
|
-
function isUserLoginEnabled() {
|
|
41
|
-
// Resolves ORY_USER_LOGIN (env, when set) over the persisted
|
|
42
|
-
// `userLogin` config flag. Centralized in resolveConfig so the gate,
|
|
43
|
-
// status CLI, and configure command stay in lockstep.
|
|
44
|
-
return (0, config_js_1.resolveConfig)().userLogin;
|
|
45
|
-
}
|
|
46
44
|
function clientId() {
|
|
47
45
|
// resolveConfig already merges env (ORY_OAUTH2_CLIENT_ID) with the
|
|
48
46
|
// persisted value, env winning. Centralizing here keeps the user gate,
|
|
@@ -71,15 +69,6 @@ function recordAuthSpan(client, decision) {
|
|
|
71
69
|
* to authenticate the human user. Always returns a decision; never throws.
|
|
72
70
|
*/
|
|
73
71
|
async function ensureUserAuthenticated(client, options) {
|
|
74
|
-
if (!isUserLoginEnabled()) {
|
|
75
|
-
const decision = {
|
|
76
|
-
proceed: true,
|
|
77
|
-
mode: "disabled",
|
|
78
|
-
reason: "User login is not enabled (ORY_USER_LOGIN env / userLogin config)",
|
|
79
|
-
};
|
|
80
|
-
recordAuthSpan(client, decision);
|
|
81
|
-
return decision;
|
|
82
|
-
}
|
|
83
72
|
try {
|
|
84
73
|
const decision = await runUserLogin(client, options);
|
|
85
74
|
attachUserPrincipal(client, decision);
|
|
@@ -88,7 +77,7 @@ async function ensureUserAuthenticated(client, options) {
|
|
|
88
77
|
}
|
|
89
78
|
catch (err) {
|
|
90
79
|
const decision = {
|
|
91
|
-
proceed:
|
|
80
|
+
proceed: true,
|
|
92
81
|
mode: "error",
|
|
93
82
|
reason: err instanceof Error ? err.message : String(err),
|
|
94
83
|
};
|
|
@@ -146,7 +135,7 @@ async function runUserLogin(client, options) {
|
|
|
146
135
|
if (!resolved.projectUrl) {
|
|
147
136
|
if (!tty) {
|
|
148
137
|
return {
|
|
149
|
-
proceed:
|
|
138
|
+
proceed: true,
|
|
150
139
|
mode: "skipped",
|
|
151
140
|
reason: "No ORY_PROJECT_URL configured and no TTY to prompt",
|
|
152
141
|
};
|
|
@@ -155,7 +144,7 @@ async function runUserLogin(client, options) {
|
|
|
155
144
|
const url = await promptFn(options.binName);
|
|
156
145
|
if (!url) {
|
|
157
146
|
return {
|
|
158
|
-
proceed:
|
|
147
|
+
proceed: true,
|
|
159
148
|
mode: "declined",
|
|
160
149
|
reason: "User declined to enter an Ory project URL",
|
|
161
150
|
};
|
|
@@ -163,7 +152,7 @@ async function runUserLogin(client, options) {
|
|
|
163
152
|
resolved = (0, config_js_1.resolveConfig)();
|
|
164
153
|
if (!resolved.projectUrl) {
|
|
165
154
|
return {
|
|
166
|
-
proceed:
|
|
155
|
+
proceed: true,
|
|
167
156
|
mode: "error",
|
|
168
157
|
reason: "Project URL was entered but config did not persist",
|
|
169
158
|
};
|
|
@@ -220,7 +209,7 @@ async function runUserLogin(client, options) {
|
|
|
220
209
|
const cid = clientId();
|
|
221
210
|
if (!cid) {
|
|
222
211
|
return {
|
|
223
|
-
proceed:
|
|
212
|
+
proceed: true,
|
|
224
213
|
mode: "skipped",
|
|
225
214
|
reason: "ORY_OAUTH2_CLIENT_ID is not set; cannot start browser login (run `" +
|
|
226
215
|
(0, cli_invocation_js_1.oryNpx)(options.binName) +
|
|
@@ -236,7 +225,8 @@ async function runUserLogin(client, options) {
|
|
|
236
225
|
// complete sign-in by pasting the URL into any browser that can reach
|
|
237
226
|
// 127.0.0.1. Truly unattended runs (CI=true) are short-circuited by
|
|
238
227
|
// `pkceLogin` itself via `detectHeadless`, and operators can bypass
|
|
239
|
-
// login entirely
|
|
228
|
+
// login entirely by supplying `ORY_USER_SESSION_TOKEN`, and enforcement
|
|
229
|
+
// consequences are governed by `permissionMode`, not by this gate.
|
|
240
230
|
// PKCE-flight lock so concurrent processes share a single browser flow.
|
|
241
231
|
const lock = (0, auth_store_js_1.tryAcquirePkceFlightLock)();
|
|
242
232
|
if (!lock) {
|
|
@@ -251,7 +241,7 @@ async function runUserLogin(client, options) {
|
|
|
251
241
|
};
|
|
252
242
|
}
|
|
253
243
|
return {
|
|
254
|
-
proceed:
|
|
244
|
+
proceed: true,
|
|
255
245
|
mode: "skipped",
|
|
256
246
|
reason: "Another agent process is mid-login; gave up waiting",
|
|
257
247
|
};
|
|
@@ -272,7 +262,7 @@ async function runUserLogin(client, options) {
|
|
|
272
262
|
};
|
|
273
263
|
}
|
|
274
264
|
return {
|
|
275
|
-
proceed:
|
|
265
|
+
proceed: true,
|
|
276
266
|
mode: declineToMode(outcome.reason),
|
|
277
267
|
reason: declineMessage(outcome.reason),
|
|
278
268
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON API for the local web installer / status dashboard. Each route is a
|
|
3
|
+
* thin wrapper over a headless action in {@link file://../setup-actions.ts} or
|
|
4
|
+
* the status data in {@link file://../status-data.ts}. All state for one
|
|
5
|
+
* install run (the resolved `ory` runner, the selected workspace/project, and
|
|
6
|
+
* the in-flight browser sub-flows) lives in a single {@link SetupSession} held
|
|
7
|
+
* by the server for its lifetime.
|
|
8
|
+
*
|
|
9
|
+
* Handlers never throw for expected failures — they return an `{ error }`
|
|
10
|
+
* envelope with an appropriate HTTP status so the SPA can surface it and the
|
|
11
|
+
* user can retry, exactly like the TTY wizard's fail-open steps.
|
|
12
|
+
*/
|
|
13
|
+
import { type SetupActionDeps } from "../setup-actions.js";
|
|
14
|
+
import type { WebAppMode } from "./types.js";
|
|
15
|
+
export interface ApiResult {
|
|
16
|
+
status: number;
|
|
17
|
+
json: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface ApiContext {
|
|
20
|
+
mode: WebAppMode;
|
|
21
|
+
harness: string;
|
|
22
|
+
binName: string;
|
|
23
|
+
deps: SetupActionDeps;
|
|
24
|
+
/** Called when the SPA POSTs /api/shutdown. */
|
|
25
|
+
onShutdown: () => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the API request handler bound to one server run. Returns
|
|
29
|
+
* `handle(method, path, body)` producing an {@link ApiResult}.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createApi(ctx: ApiContext): {
|
|
32
|
+
handle: (method: string, path: string, body: Record<string, unknown>) => Promise<ApiResult>;
|
|
33
|
+
};
|
package/dist/web/api.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createApi = createApi;
|
|
4
|
+
/**
|
|
5
|
+
* JSON API for the local web installer / status dashboard. Each route is a
|
|
6
|
+
* thin wrapper over a headless action in {@link file://../setup-actions.ts} or
|
|
7
|
+
* the status data in {@link file://../status-data.ts}. All state for one
|
|
8
|
+
* install run (the resolved `ory` runner, the selected workspace/project, and
|
|
9
|
+
* the in-flight browser sub-flows) lives in a single {@link SetupSession} held
|
|
10
|
+
* by the server for its lifetime.
|
|
11
|
+
*
|
|
12
|
+
* Handlers never throw for expected failures — they return an `{ error }`
|
|
13
|
+
* envelope with an appropriate HTTP status so the SPA can surface it and the
|
|
14
|
+
* user can retry, exactly like the TTY wizard's fail-open steps.
|
|
15
|
+
*/
|
|
16
|
+
const setup_actions_js_1 = require("../setup-actions.js");
|
|
17
|
+
const status_data_js_1 = require("../status-data.js");
|
|
18
|
+
const config_js_1 = require("../config.js");
|
|
19
|
+
const version_cli_js_1 = require("../version-cli.js");
|
|
20
|
+
const ok = (json = { ok: true }) => ({ status: 200, json });
|
|
21
|
+
const bad = (error, status = 400) => ({ status, json: { error } });
|
|
22
|
+
/**
|
|
23
|
+
* Build the API request handler bound to one server run. Returns
|
|
24
|
+
* `handle(method, path, body)` producing an {@link ApiResult}.
|
|
25
|
+
*/
|
|
26
|
+
function createApi(ctx) {
|
|
27
|
+
const session = {
|
|
28
|
+
runner: ctx.deps.runner ?? null,
|
|
29
|
+
subject: null,
|
|
30
|
+
};
|
|
31
|
+
/** The runner to use, failing loudly if `POST /api/ory-cli` hasn't run. */
|
|
32
|
+
function runner() {
|
|
33
|
+
return session.runner;
|
|
34
|
+
}
|
|
35
|
+
async function handle(method, path, body) {
|
|
36
|
+
const route = `${method} ${path}`;
|
|
37
|
+
switch (route) {
|
|
38
|
+
case "GET /api/bootstrap": {
|
|
39
|
+
const cfg = (0, config_js_1.resolveConfig)();
|
|
40
|
+
const payload = {
|
|
41
|
+
mode: ctx.mode,
|
|
42
|
+
harness: ctx.harness,
|
|
43
|
+
binName: ctx.binName,
|
|
44
|
+
version: (0, version_cli_js_1.resolveCoreVersion)(),
|
|
45
|
+
alreadyConfigured: !!cfg.projectUrl || cfg.auditOnly,
|
|
46
|
+
auditOnly: cfg.auditOnly,
|
|
47
|
+
projectUrl: cfg.projectUrl,
|
|
48
|
+
};
|
|
49
|
+
return ok(payload);
|
|
50
|
+
}
|
|
51
|
+
case "GET /api/status":
|
|
52
|
+
return ok(await (0, status_data_js_1.collectStatusReport)(ctx.harness));
|
|
53
|
+
case "POST /api/ory-cli": {
|
|
54
|
+
const res = await (0, setup_actions_js_1.ensureOryCli)(ctx.deps);
|
|
55
|
+
session.runner = res.runner;
|
|
56
|
+
return ok({ present: res.present, installed: res.installed });
|
|
57
|
+
}
|
|
58
|
+
case "GET /api/signin": {
|
|
59
|
+
const r = runner();
|
|
60
|
+
return ok({ signedIn: r ? (0, setup_actions_js_1.isSignedIn)(r) : false });
|
|
61
|
+
}
|
|
62
|
+
case "POST /api/signin": {
|
|
63
|
+
const r = runner();
|
|
64
|
+
if (!r)
|
|
65
|
+
return bad("Ory CLI is not ready", 409);
|
|
66
|
+
if ((0, setup_actions_js_1.isSignedIn)(r))
|
|
67
|
+
return ok({ started: false, signedIn: true });
|
|
68
|
+
const handle = (0, setup_actions_js_1.startSignIn)(r);
|
|
69
|
+
const task = { handle, state: "running" };
|
|
70
|
+
session.signIn = task;
|
|
71
|
+
handle.done.then((success) => {
|
|
72
|
+
task.state = success ? "done" : "failed";
|
|
73
|
+
});
|
|
74
|
+
return ok({ started: true, signedIn: false });
|
|
75
|
+
}
|
|
76
|
+
case "GET /api/signin/status": {
|
|
77
|
+
const r = runner();
|
|
78
|
+
const task = session.signIn;
|
|
79
|
+
const state = task?.state ?? "idle";
|
|
80
|
+
// Treat a live session as done even if we never launched `ory auth`
|
|
81
|
+
// (the CLI may already have been signed in).
|
|
82
|
+
const signedIn = state === "done" || (!!r && (0, setup_actions_js_1.isSignedIn)(r));
|
|
83
|
+
return ok({ state: signedIn ? "done" : state, signedIn });
|
|
84
|
+
}
|
|
85
|
+
case "GET /api/workspaces": {
|
|
86
|
+
const r = runner();
|
|
87
|
+
if (!r)
|
|
88
|
+
return bad("Ory CLI is not ready", 409);
|
|
89
|
+
const workspaces = (0, setup_actions_js_1.listWorkspaces)(r);
|
|
90
|
+
if (workspaces === null)
|
|
91
|
+
return bad("Could not list workspaces", 502);
|
|
92
|
+
session.workspaces = workspaces;
|
|
93
|
+
return ok({ workspaces });
|
|
94
|
+
}
|
|
95
|
+
case "POST /api/workspaces": {
|
|
96
|
+
const r = runner();
|
|
97
|
+
if (!r)
|
|
98
|
+
return bad("Ory CLI is not ready", 409);
|
|
99
|
+
const name = str(body.name);
|
|
100
|
+
if (!name)
|
|
101
|
+
return bad("A workspace name is required");
|
|
102
|
+
const res = (0, setup_actions_js_1.createWorkspace)(r, name);
|
|
103
|
+
if (!res.ok)
|
|
104
|
+
return bad(res.error ?? "Could not create workspace", 502);
|
|
105
|
+
return ok({ workspace: res.value });
|
|
106
|
+
}
|
|
107
|
+
case "GET /api/projects": {
|
|
108
|
+
const r = runner();
|
|
109
|
+
if (!r)
|
|
110
|
+
return bad("Ory CLI is not ready", 409);
|
|
111
|
+
const workspaceId = str(body.workspaceId);
|
|
112
|
+
if (!workspaceId)
|
|
113
|
+
return bad("A workspaceId is required");
|
|
114
|
+
session.workspaceId = workspaceId;
|
|
115
|
+
session.workspaceName = session.workspaces?.find((w) => w.id === workspaceId)?.name;
|
|
116
|
+
const projects = (0, setup_actions_js_1.listProjects)(r, workspaceId);
|
|
117
|
+
if (projects === null)
|
|
118
|
+
return bad("Could not list projects", 502);
|
|
119
|
+
return ok({ projects, cap: setup_actions_js_1.PROJECT_CAP, canCreate: projects.length < setup_actions_js_1.PROJECT_CAP });
|
|
120
|
+
}
|
|
121
|
+
case "POST /api/projects": {
|
|
122
|
+
const r = runner();
|
|
123
|
+
if (!r)
|
|
124
|
+
return bad("Ory CLI is not ready", 409);
|
|
125
|
+
const workspaceId = str(body.workspaceId) ?? session.workspaceId;
|
|
126
|
+
const name = str(body.name);
|
|
127
|
+
if (!workspaceId || !name)
|
|
128
|
+
return bad("workspaceId and name are required");
|
|
129
|
+
const res = (0, setup_actions_js_1.createProject)(r, workspaceId, name);
|
|
130
|
+
if (!res.ok)
|
|
131
|
+
return bad(res.error ?? "Could not create project", 502);
|
|
132
|
+
return ok({ project: res.value });
|
|
133
|
+
}
|
|
134
|
+
case "POST /api/select-project": {
|
|
135
|
+
const r = runner();
|
|
136
|
+
if (!r)
|
|
137
|
+
return bad("Ory CLI is not ready", 409);
|
|
138
|
+
const project = body.project;
|
|
139
|
+
if (!project?.id)
|
|
140
|
+
return bad("A project is required");
|
|
141
|
+
const url = (0, setup_actions_js_1.resolveProjectUrl)(project);
|
|
142
|
+
if (!url.ok)
|
|
143
|
+
return bad(url.error ?? "Could not resolve project URL", 422);
|
|
144
|
+
session.project = project;
|
|
145
|
+
session.projectUrl = url.value;
|
|
146
|
+
// Ensure the OAuth2 login/consent UI is the Ory Managed Account
|
|
147
|
+
// Experience so the PKCE user login (step 7) renders a working page —
|
|
148
|
+
// a CLI-created project defaults these to the localhost:3000 sample.
|
|
149
|
+
const loginUi = (0, setup_actions_js_1.ensureManagedLoginUi)(r, project.id);
|
|
150
|
+
return ok({ projectUrl: url.value, dcrEnabled: (0, setup_actions_js_1.getDcrState)(r, project.id), loginUi });
|
|
151
|
+
}
|
|
152
|
+
case "POST /api/enable-dcr": {
|
|
153
|
+
const r = runner();
|
|
154
|
+
if (!r || !session.project)
|
|
155
|
+
return bad("No project selected", 409);
|
|
156
|
+
return ok({ ok: (0, setup_actions_js_1.enableDcrAction)(r, session.project.id) });
|
|
157
|
+
}
|
|
158
|
+
case "POST /api/oauth2-client": {
|
|
159
|
+
const r = runner();
|
|
160
|
+
if (!r || !session.project)
|
|
161
|
+
return bad("No project selected", 409);
|
|
162
|
+
const res = (0, setup_actions_js_1.provisionOAuth2Client)(r, session.project.id);
|
|
163
|
+
if (!res.ok || !res.clientId)
|
|
164
|
+
return bad("Could not provision the OAuth2 client", 502);
|
|
165
|
+
session.oauth2ClientId = res.clientId;
|
|
166
|
+
return ok({ clientId: res.clientId, reused: res.reused, name: res.name });
|
|
167
|
+
}
|
|
168
|
+
case "POST /api/save": {
|
|
169
|
+
if (!session.project || !session.projectUrl || !session.oauth2ClientId) {
|
|
170
|
+
return bad("Setup is incomplete (missing project / client)", 409);
|
|
171
|
+
}
|
|
172
|
+
(0, setup_actions_js_1.saveNetworkConfig)({
|
|
173
|
+
projectUrl: session.projectUrl,
|
|
174
|
+
projectId: session.project.id,
|
|
175
|
+
oauth2ClientId: session.oauth2ClientId,
|
|
176
|
+
projectName: session.project.name ?? session.project.slug,
|
|
177
|
+
workspaceName: session.workspaceName,
|
|
178
|
+
});
|
|
179
|
+
return ok();
|
|
180
|
+
}
|
|
181
|
+
case "POST /api/login": {
|
|
182
|
+
if (session.login?.state === "running")
|
|
183
|
+
return ok({ started: false });
|
|
184
|
+
// Pin the project the wizard resolved so a stale ORY_PROJECT_URL in the
|
|
185
|
+
// user's shell can't send the login to the wrong URL (e.g. :3000).
|
|
186
|
+
const task = {
|
|
187
|
+
promise: (0, setup_actions_js_1.runFullSetup)(ctx.binName, ctx.harness, ctx.deps, {
|
|
188
|
+
projectUrl: session.projectUrl,
|
|
189
|
+
oauth2ClientId: session.oauth2ClientId,
|
|
190
|
+
}),
|
|
191
|
+
state: "running",
|
|
192
|
+
};
|
|
193
|
+
session.login = task;
|
|
194
|
+
task.promise.then((result) => {
|
|
195
|
+
task.state = result.authenticated ? "done" : "failed";
|
|
196
|
+
task.result = result;
|
|
197
|
+
if (result.subject) {
|
|
198
|
+
session.subject = result.subject;
|
|
199
|
+
// The login only resolves an opaque subject id; look the identity up
|
|
200
|
+
// via the admin CLI to show a readable name (email) instead of a UUID.
|
|
201
|
+
const sid = "subjectId" in result.subject
|
|
202
|
+
? result.subject.subjectId
|
|
203
|
+
: result.subject.subjectSet.object;
|
|
204
|
+
const r = session.runner;
|
|
205
|
+
if (r && session.project && sid) {
|
|
206
|
+
const name = (0, setup_actions_js_1.resolveIdentityName)(r, session.project.id, sid);
|
|
207
|
+
if (name)
|
|
208
|
+
task.result = { ...result, subjectLabel: name };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return ok({ started: true });
|
|
213
|
+
}
|
|
214
|
+
case "GET /api/login/status": {
|
|
215
|
+
const task = session.login;
|
|
216
|
+
return ok({
|
|
217
|
+
state: task?.state ?? "idle",
|
|
218
|
+
authenticated: task?.result?.authenticated ?? false,
|
|
219
|
+
subjectLabel: task?.result?.subjectLabel ?? null,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
case "GET /api/permissions/model": {
|
|
223
|
+
const r = runner();
|
|
224
|
+
if (!r || !session.project)
|
|
225
|
+
return bad("No project selected", 409);
|
|
226
|
+
return ok(await (0, setup_actions_js_1.inspectPermissions)(r, session.project.id));
|
|
227
|
+
}
|
|
228
|
+
case "POST /api/permissions/provision": {
|
|
229
|
+
const r = runner();
|
|
230
|
+
if (!r || !session.project)
|
|
231
|
+
return bad("No project selected", 409);
|
|
232
|
+
const opl = str(body.opl);
|
|
233
|
+
return ok({ ok: (0, setup_actions_js_1.provisionPermissions)(r, session.project.id, undefined, opl) });
|
|
234
|
+
}
|
|
235
|
+
case "POST /api/permissions/grant": {
|
|
236
|
+
const r = runner();
|
|
237
|
+
if (!r || !session.project)
|
|
238
|
+
return bad("No project selected", 409);
|
|
239
|
+
if (!session.subject)
|
|
240
|
+
return bad("Sign in first so permissions can be granted to you", 409);
|
|
241
|
+
const res = (0, setup_actions_js_1.grantToolPermissions)(r, session.project.id, ctx.harness, session.subject);
|
|
242
|
+
return ok(res);
|
|
243
|
+
}
|
|
244
|
+
case "POST /api/api-key": {
|
|
245
|
+
if (!session.project || !session.projectUrl)
|
|
246
|
+
return bad("No project selected", 409);
|
|
247
|
+
const res = await (0, setup_actions_js_1.provisionProjectApiKey)({
|
|
248
|
+
projectId: session.project.id,
|
|
249
|
+
projectUrl: session.projectUrl,
|
|
250
|
+
harness: ctx.harness,
|
|
251
|
+
user: session.subject
|
|
252
|
+
? (0, setup_actions_js_1.resourceUserLabel)(runner(), session.project.id, session.subject)
|
|
253
|
+
: undefined,
|
|
254
|
+
}, ctx.deps);
|
|
255
|
+
return ok(res);
|
|
256
|
+
}
|
|
257
|
+
case "POST /api/audit-only": {
|
|
258
|
+
(0, setup_actions_js_1.configureAuditOnly)();
|
|
259
|
+
return ok();
|
|
260
|
+
}
|
|
261
|
+
// Live toggle from the dashboard (permission posture).
|
|
262
|
+
case "POST /api/config": {
|
|
263
|
+
const patch = {};
|
|
264
|
+
if ("permissionMode" in body) {
|
|
265
|
+
if (body.permissionMode !== "observe" && body.permissionMode !== "enforce") {
|
|
266
|
+
return bad("permissionMode must be 'observe' or 'enforce'");
|
|
267
|
+
}
|
|
268
|
+
(0, setup_actions_js_1.setPermissionMode)(body.permissionMode);
|
|
269
|
+
patch.permissionMode = body.permissionMode;
|
|
270
|
+
}
|
|
271
|
+
if (Object.keys(patch).length === 0)
|
|
272
|
+
return bad("No supported config fields in request");
|
|
273
|
+
return ok(patch);
|
|
274
|
+
}
|
|
275
|
+
case "POST /api/local": {
|
|
276
|
+
const res = await (0, setup_actions_js_1.configureLocalStack)(ctx.deps);
|
|
277
|
+
// Record the local connection so the subsequent login pins to it too.
|
|
278
|
+
session.projectUrl = res.projectUrl;
|
|
279
|
+
session.oauth2ClientId = res.oauth2ClientId;
|
|
280
|
+
return ok(res);
|
|
281
|
+
}
|
|
282
|
+
case "POST /api/shutdown": {
|
|
283
|
+
ctx.onShutdown();
|
|
284
|
+
return ok();
|
|
285
|
+
}
|
|
286
|
+
default:
|
|
287
|
+
return bad(`Unknown route: ${route}`, 404);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { handle };
|
|
291
|
+
}
|
|
292
|
+
function str(v) {
|
|
293
|
+
return typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
294
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SetupActionDeps } from "../setup-actions.js";
|
|
2
|
+
/**
|
|
3
|
+
* Heuristic for whether we can meaningfully open a browser for the user:
|
|
4
|
+
* an interactive TTY, not CI, not opted-out, and — on Linux — an actual
|
|
5
|
+
* display server. macOS/Windows are assumed to have a default browser.
|
|
6
|
+
*/
|
|
7
|
+
export declare function canLaunchBrowser(): boolean;
|
|
8
|
+
/** Open the guided install/setup wizard in the browser. */
|
|
9
|
+
export declare function launchWebInstaller(binName: string, harness: string, deps?: SetupActionDeps): Promise<boolean>;
|
|
10
|
+
/** Open the status dashboard in the browser. */
|
|
11
|
+
export declare function launchWebDashboard(binName: string, harness: string, deps?: SetupActionDeps): Promise<boolean>;
|