@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/status-cli.js
CHANGED
|
@@ -26,21 +26,6 @@ const cli_invocation_js_1 = require("./cli-invocation.js");
|
|
|
26
26
|
function resolveNamespace() {
|
|
27
27
|
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Human-readable annotation for where the resolved user-login flag came
|
|
31
|
-
* from, so `status` readers know whether to look at the env var or the
|
|
32
|
-
* config file to change it.
|
|
33
|
-
*/
|
|
34
|
-
function formatUserLoginSource(source) {
|
|
35
|
-
switch (source) {
|
|
36
|
-
case "env":
|
|
37
|
-
return " (ORY_USER_LOGIN env var)";
|
|
38
|
-
case "config":
|
|
39
|
-
return " (saved in config; change with: configure --user-login|--no-user-login)";
|
|
40
|
-
case "default":
|
|
41
|
-
return "";
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
29
|
/**
|
|
45
30
|
* Format a relative duration like "4h 32m" or "12s" for a future deadline.
|
|
46
31
|
* Returns "expired" when the deadline has already passed.
|
|
@@ -61,27 +46,16 @@ function formatExpiresIn(expiresAtSec, nowMs = Date.now()) {
|
|
|
61
46
|
return `${remaining}s`;
|
|
62
47
|
}
|
|
63
48
|
/**
|
|
64
|
-
* Render the "User identity" block.
|
|
65
|
-
*
|
|
49
|
+
* Render the "User identity" block. The interactive PKCE login runs every
|
|
50
|
+
* session; this shows the cached token's subject and expiry, if any.
|
|
66
51
|
*/
|
|
67
52
|
function printUserIdentitySection() {
|
|
68
|
-
const resolved = (0, config_js_1.resolveConfig)();
|
|
69
|
-
const loginOn = resolved.userLogin;
|
|
70
53
|
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
71
54
|
console.log("");
|
|
72
55
|
console.log("User identity (interactive PKCE login):");
|
|
73
|
-
const loginState = loginOn
|
|
74
|
-
? `on${formatUserLoginSource(resolved.userLoginSource)}`
|
|
75
|
-
: "off (enable with: configure --user-login, or ORY_USER_LOGIN=true)";
|
|
76
|
-
console.log(` Login: ${loginState}`);
|
|
77
56
|
if (!tokens) {
|
|
78
57
|
console.log(" Token cache: empty");
|
|
79
|
-
|
|
80
|
-
console.log(" Subject: (will resolve at next session start)");
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
console.log(" Subject: (login disabled — no PKCE flow will run)");
|
|
84
|
-
}
|
|
58
|
+
console.log(" Subject: (will resolve at next session start)");
|
|
85
59
|
return;
|
|
86
60
|
}
|
|
87
61
|
const expired = (0, auth_store_js_1.isExpired)(tokens);
|
|
@@ -225,7 +199,7 @@ async function printPermissionsSection(binName, harness) {
|
|
|
225
199
|
}
|
|
226
200
|
const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
|
|
227
201
|
if (!hasUser) {
|
|
228
|
-
console.log(` Coverage: n/a (no cached user identity —
|
|
202
|
+
console.log(` Coverage: n/a (no cached user identity — start a session once to sign in,`);
|
|
229
203
|
console.log(` or set ORY_USER_SUBJECT_ID to probe a known subject)`);
|
|
230
204
|
return;
|
|
231
205
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type StatusSystemSection } from "./status-system.js";
|
|
2
|
+
export interface PermissionsCoverage {
|
|
3
|
+
total: number;
|
|
4
|
+
allowed: number;
|
|
5
|
+
denied: number;
|
|
6
|
+
errored: number;
|
|
7
|
+
errorCode?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Probe the harness's built-in tool catalog against Ory, returning how many
|
|
11
|
+
* tools the resolved user subject is allowed / denied / errored on. Returns
|
|
12
|
+
* undefined when a probe can't run (no project URL, empty catalog, no user
|
|
13
|
+
* identity). Never throws.
|
|
14
|
+
*/
|
|
15
|
+
export declare function probePermissionsCoverage(harness: string): Promise<PermissionsCoverage | undefined>;
|
|
16
|
+
export interface StatusConfigSection {
|
|
17
|
+
configPath: string;
|
|
18
|
+
/** Whether a config file actually exists at `configPath` (vs. where one would be written). */
|
|
19
|
+
configExists: boolean;
|
|
20
|
+
projectUrl?: string;
|
|
21
|
+
projectUrlSource: "env" | "config" | "none";
|
|
22
|
+
projectId?: string;
|
|
23
|
+
projectIdSource: "env" | "config" | "none";
|
|
24
|
+
/** Human-readable project / workspace names captured at install (display only). */
|
|
25
|
+
projectName?: string;
|
|
26
|
+
workspaceName?: string;
|
|
27
|
+
apiKeyConfigured: boolean;
|
|
28
|
+
apiKeySource: "env" | "config" | "none";
|
|
29
|
+
oauth2ClientId?: string;
|
|
30
|
+
oauth2ClientIdSource: "env" | "config" | "none";
|
|
31
|
+
/** Display name of the shared PKCE user-login client. Deterministic from
|
|
32
|
+
* branding; present only when a client id is configured. */
|
|
33
|
+
oauth2ClientName?: string;
|
|
34
|
+
auditOnly: boolean;
|
|
35
|
+
permissionMode: "observe" | "enforce";
|
|
36
|
+
permissionModeSource: "env" | "config" | "default";
|
|
37
|
+
userSubjectNamespace?: string;
|
|
38
|
+
userSubjectNamespaceSource: "env" | "config" | "none";
|
|
39
|
+
namespace: string;
|
|
40
|
+
/** Whether debug logging is on (`ORY_AGENT_DEBUG=true`). Env-only, read at
|
|
41
|
+
* process start by the logger — not persisted config, so it's shown read-only. */
|
|
42
|
+
debugEnabled: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface StatusUserSection {
|
|
45
|
+
tokenCache: "empty" | "present" | "stale";
|
|
46
|
+
subject?: string;
|
|
47
|
+
expiresInSeconds?: number;
|
|
48
|
+
clientId?: string;
|
|
49
|
+
}
|
|
50
|
+
export type AgentIdentitySource = "static_api_key" | "static_client_credentials" | "dcr" | "unregistered";
|
|
51
|
+
export interface StatusAgentSection {
|
|
52
|
+
source: AgentIdentitySource;
|
|
53
|
+
clientId?: string;
|
|
54
|
+
subject?: string;
|
|
55
|
+
registeredAt?: string;
|
|
56
|
+
projectUrlMismatch?: {
|
|
57
|
+
registeredAgainst: string;
|
|
58
|
+
current: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export type PermissionsCoverageStatus = "audit_only" | "no_project" | "no_catalog" | "no_user" | "bootstrapped" | "partial" | "not_bootstrapped" | "probe_failed";
|
|
62
|
+
export interface StatusPermissionsSection {
|
|
63
|
+
mode: "observe" | "enforce";
|
|
64
|
+
namespace: string;
|
|
65
|
+
coverageStatus: PermissionsCoverageStatus;
|
|
66
|
+
coverage?: PermissionsCoverage;
|
|
67
|
+
knownHarnesses?: string[];
|
|
68
|
+
}
|
|
69
|
+
export interface StatusActivityEntry {
|
|
70
|
+
raw: string;
|
|
71
|
+
parsed?: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
export interface StatusActivitySection {
|
|
74
|
+
traceFile?: string;
|
|
75
|
+
traceCount: number;
|
|
76
|
+
recentSpans: StatusActivityEntry[];
|
|
77
|
+
logFile?: string;
|
|
78
|
+
logCount: number;
|
|
79
|
+
recentLogs: StatusActivityEntry[];
|
|
80
|
+
}
|
|
81
|
+
export interface StatusReport {
|
|
82
|
+
harness: string;
|
|
83
|
+
config: StatusConfigSection;
|
|
84
|
+
user: StatusUserSection;
|
|
85
|
+
agent: StatusAgentSection;
|
|
86
|
+
permissions: StatusPermissionsSection;
|
|
87
|
+
activity: StatusActivitySection;
|
|
88
|
+
/** Core Ory service health (Kratos, Hydra, Keto) — the dashboard's "System information". */
|
|
89
|
+
system: StatusSystemSection;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gather the full structured status report for a harness. The permissions
|
|
93
|
+
* section runs a live network probe when a project URL and user identity are
|
|
94
|
+
* available (same conditions as the text `status` command).
|
|
95
|
+
*/
|
|
96
|
+
export declare function collectStatusReport(harness: string): Promise<StatusReport>;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.probePermissionsCoverage = probePermissionsCoverage;
|
|
37
|
+
exports.collectStatusReport = collectStatusReport;
|
|
38
|
+
/**
|
|
39
|
+
* Structured status data — the data-gathering half of the `status` command,
|
|
40
|
+
* shared by the text renderer ({@link file://./status-cli.ts}) and the local
|
|
41
|
+
* web dashboard. `collectStatusReport(harness)` returns a plain, serializable
|
|
42
|
+
* object mirroring every section `status` prints (configuration, user identity,
|
|
43
|
+
* agent identity, permissions coverage, recent activity), so the two surfaces
|
|
44
|
+
* never drift.
|
|
45
|
+
*
|
|
46
|
+
* The live permission-coverage probe ({@link probePermissionsCoverage}) lives
|
|
47
|
+
* here too and is imported back by `status-cli.ts`, so there is a single probe
|
|
48
|
+
* implementation.
|
|
49
|
+
*/
|
|
50
|
+
const config_js_1 = require("./config.js");
|
|
51
|
+
const auth_store_js_1 = require("./auth-store.js");
|
|
52
|
+
const agent_auth_js_1 = require("./agent-auth.js");
|
|
53
|
+
const client_js_1 = require("./client.js");
|
|
54
|
+
const subject_js_1 = require("./subject.js");
|
|
55
|
+
const branding_js_1 = require("./branding.js");
|
|
56
|
+
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
57
|
+
const fs = __importStar(require("node:fs"));
|
|
58
|
+
// Additive dashboard section: core Ory service health (see status-system.ts).
|
|
59
|
+
const status_system_js_1 = require("./status-system.js");
|
|
60
|
+
function resolveNamespace() {
|
|
61
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Probe the harness's built-in tool catalog against Ory, returning how many
|
|
65
|
+
* tools the resolved user subject is allowed / denied / errored on. Returns
|
|
66
|
+
* undefined when a probe can't run (no project URL, empty catalog, no user
|
|
67
|
+
* identity). Never throws.
|
|
68
|
+
*/
|
|
69
|
+
async function probePermissionsCoverage(harness) {
|
|
70
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
71
|
+
if (!resolved.projectUrl)
|
|
72
|
+
return undefined;
|
|
73
|
+
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
74
|
+
if (catalog.length === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
77
|
+
const subjectOverride = process.env.ORY_USER_SUBJECT_ID;
|
|
78
|
+
if (!subjectOverride && (!tokens || (0, auth_store_js_1.isExpired)(tokens))) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const client = client_js_1.OryAgentClient.fromEnv(harness);
|
|
82
|
+
if (tokens && !(0, auth_store_js_1.isExpired)(tokens)) {
|
|
83
|
+
client.setUserPrincipal({ subject: tokens.subject, token: tokens.accessToken });
|
|
84
|
+
}
|
|
85
|
+
await (0, agent_auth_js_1.ensureAgentIdentity)(client, { projectUrl: resolved.projectUrl }).catch(() => {
|
|
86
|
+
/* best-effort; the probe still runs with whatever credentials we have */
|
|
87
|
+
});
|
|
88
|
+
const subject = (0, subject_js_1.resolveUserSubject)(client);
|
|
89
|
+
if ((0, subject_js_1.subjectLabel)(subject) === "agent:unknown")
|
|
90
|
+
return undefined;
|
|
91
|
+
const namespace = resolveNamespace();
|
|
92
|
+
const result = { total: catalog.length, allowed: 0, denied: 0, errored: 0 };
|
|
93
|
+
for (const tool of catalog) {
|
|
94
|
+
const check = { namespace, object: tool, relation: "use", ...subject };
|
|
95
|
+
try {
|
|
96
|
+
const probe = await client.checkPermission(check, {
|
|
97
|
+
spanAttributes: { toolName: tool, source: "status_coverage" },
|
|
98
|
+
});
|
|
99
|
+
if (probe.allowed)
|
|
100
|
+
result.allowed++;
|
|
101
|
+
else
|
|
102
|
+
result.denied++;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const ory = err;
|
|
106
|
+
result.errored++;
|
|
107
|
+
result.errorCode = result.errorCode ?? ory.code ?? "unknown";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
function collectConfig(harness) {
|
|
113
|
+
const r = (0, config_js_1.resolveConfig)();
|
|
114
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
115
|
+
return {
|
|
116
|
+
configPath,
|
|
117
|
+
configExists: fs.existsSync(configPath),
|
|
118
|
+
projectUrl: r.projectUrl,
|
|
119
|
+
projectUrlSource: r.projectUrlSource,
|
|
120
|
+
projectId: r.projectId,
|
|
121
|
+
projectIdSource: r.projectIdSource,
|
|
122
|
+
projectName: r.projectName,
|
|
123
|
+
workspaceName: r.workspaceName,
|
|
124
|
+
apiKeyConfigured: !!r.apiKey,
|
|
125
|
+
apiKeySource: r.apiKeySource,
|
|
126
|
+
oauth2ClientId: r.oauth2ClientId,
|
|
127
|
+
oauth2ClientIdSource: r.oauth2ClientIdSource,
|
|
128
|
+
oauth2ClientName: r.oauth2ClientId ? branding_js_1.USER_LOGIN_CLIENT_NAME : undefined,
|
|
129
|
+
auditOnly: r.auditOnly,
|
|
130
|
+
permissionMode: r.permissionMode,
|
|
131
|
+
permissionModeSource: r.permissionModeSource,
|
|
132
|
+
userSubjectNamespace: r.userSubjectNamespace,
|
|
133
|
+
userSubjectNamespaceSource: r.userSubjectNamespaceSource,
|
|
134
|
+
namespace: resolveNamespace(),
|
|
135
|
+
debugEnabled: process.env.ORY_AGENT_DEBUG === "true",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function collectUser() {
|
|
139
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
140
|
+
if (!tokens) {
|
|
141
|
+
return { tokenCache: "empty" };
|
|
142
|
+
}
|
|
143
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
144
|
+
return {
|
|
145
|
+
tokenCache: (0, auth_store_js_1.isExpired)(tokens) ? "stale" : "present",
|
|
146
|
+
subject: tokens.subject,
|
|
147
|
+
expiresInSeconds: tokens.expiresAt - nowSec,
|
|
148
|
+
clientId: tokens.clientId,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function collectAgent() {
|
|
152
|
+
if (process.env.ORY_AGENT_API_KEY) {
|
|
153
|
+
return { source: "static_api_key", subject: process.env.ORY_AGENT_SUBJECT_ID };
|
|
154
|
+
}
|
|
155
|
+
if (process.env.ORY_AGENT_CLIENT_ID && process.env.ORY_AGENT_CLIENT_SECRET) {
|
|
156
|
+
return {
|
|
157
|
+
source: "static_client_credentials",
|
|
158
|
+
clientId: process.env.ORY_AGENT_CLIENT_ID,
|
|
159
|
+
subject: process.env.ORY_AGENT_SUBJECT_ID ?? process.env.ORY_AGENT_CLIENT_ID,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
|
|
163
|
+
if (!persisted)
|
|
164
|
+
return { source: "unregistered" };
|
|
165
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
166
|
+
const mismatch = resolved.projectUrl && persisted.projectUrl !== resolved.projectUrl
|
|
167
|
+
? { registeredAgainst: persisted.projectUrl, current: resolved.projectUrl }
|
|
168
|
+
: undefined;
|
|
169
|
+
return {
|
|
170
|
+
source: "dcr",
|
|
171
|
+
clientId: persisted.clientId,
|
|
172
|
+
subject: process.env.ORY_AGENT_SUBJECT_ID ?? persisted.clientId,
|
|
173
|
+
registeredAt: new Date(persisted.registeredAt * 1000).toISOString(),
|
|
174
|
+
projectUrlMismatch: mismatch,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function collectPermissions(harness) {
|
|
178
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
179
|
+
const namespace = resolveNamespace();
|
|
180
|
+
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
181
|
+
const base = { mode: resolved.permissionMode, namespace };
|
|
182
|
+
if (resolved.auditOnly)
|
|
183
|
+
return { ...base, coverageStatus: "audit_only" };
|
|
184
|
+
if (!resolved.projectUrl)
|
|
185
|
+
return { ...base, coverageStatus: "no_project" };
|
|
186
|
+
if (catalog.length === 0) {
|
|
187
|
+
return { ...base, coverageStatus: "no_catalog", knownHarnesses: [...tool_catalog_js_1.KNOWN_HARNESSES] };
|
|
188
|
+
}
|
|
189
|
+
const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
|
|
190
|
+
if (!hasUser)
|
|
191
|
+
return { ...base, coverageStatus: "no_user" };
|
|
192
|
+
const coverage = await probePermissionsCoverage(harness);
|
|
193
|
+
if (!coverage)
|
|
194
|
+
return { ...base, coverageStatus: "no_user" };
|
|
195
|
+
if (coverage.errored > 0 && coverage.allowed === 0 && coverage.denied === 0) {
|
|
196
|
+
return { ...base, coverageStatus: "probe_failed", coverage };
|
|
197
|
+
}
|
|
198
|
+
const status = coverage.allowed === coverage.total
|
|
199
|
+
? "bootstrapped"
|
|
200
|
+
: coverage.allowed === 0
|
|
201
|
+
? "not_bootstrapped"
|
|
202
|
+
: "partial";
|
|
203
|
+
return { ...base, coverageStatus: status, coverage };
|
|
204
|
+
}
|
|
205
|
+
function isUserTokenUsable() {
|
|
206
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
207
|
+
return !!tokens && !(0, auth_store_js_1.isExpired)(tokens);
|
|
208
|
+
}
|
|
209
|
+
function readTail(file, limit) {
|
|
210
|
+
if (!file || !fs.existsSync(file))
|
|
211
|
+
return { file, count: 0, recent: [] };
|
|
212
|
+
const lines = fs.readFileSync(file, "utf-8").trim().split("\n").filter(Boolean);
|
|
213
|
+
const recent = lines.slice(-limit).map((raw) => {
|
|
214
|
+
try {
|
|
215
|
+
return { raw, parsed: JSON.parse(raw) };
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return { raw };
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return { file, count: lines.length, recent };
|
|
222
|
+
}
|
|
223
|
+
function collectActivity(harness) {
|
|
224
|
+
const trace = readTail((0, client_js_1.resolveTraceFilePath)(harness), 10);
|
|
225
|
+
const log = readTail((0, client_js_1.resolveDebugLogPath)(harness), 10);
|
|
226
|
+
return {
|
|
227
|
+
traceFile: trace.file,
|
|
228
|
+
traceCount: trace.count,
|
|
229
|
+
recentSpans: trace.recent,
|
|
230
|
+
logFile: log.file,
|
|
231
|
+
logCount: log.count,
|
|
232
|
+
recentLogs: log.recent,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Gather the full structured status report for a harness. The permissions
|
|
237
|
+
* section runs a live network probe when a project URL and user identity are
|
|
238
|
+
* available (same conditions as the text `status` command).
|
|
239
|
+
*/
|
|
240
|
+
async function collectStatusReport(harness) {
|
|
241
|
+
return {
|
|
242
|
+
harness,
|
|
243
|
+
config: collectConfig(harness),
|
|
244
|
+
user: collectUser(),
|
|
245
|
+
agent: collectAgent(),
|
|
246
|
+
permissions: await collectPermissions(harness),
|
|
247
|
+
activity: collectActivity(harness),
|
|
248
|
+
system: await (0, status_system_js_1.collectSystemServices)(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface StatusSystemService {
|
|
2
|
+
/** Service name, e.g. "Kratos". */
|
|
3
|
+
name: string;
|
|
4
|
+
/** One-line role, e.g. "Identities & sessions". */
|
|
5
|
+
role: string;
|
|
6
|
+
/** Published host port. */
|
|
7
|
+
port: number;
|
|
8
|
+
/** Base URL for the service. */
|
|
9
|
+
url: string;
|
|
10
|
+
/** The endpoint probed to decide health. */
|
|
11
|
+
healthUrl: string;
|
|
12
|
+
/** true = healthy, false = unreachable/error, null = not probed. */
|
|
13
|
+
healthy: boolean | null;
|
|
14
|
+
/** Human-readable detail (HTTP status or error) when unhealthy. */
|
|
15
|
+
detail?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StatusSystemSection {
|
|
18
|
+
services: StatusSystemService[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Probe the core Ory services' readiness endpoints and return their health.
|
|
22
|
+
* Never throws — an unreachable service is reported as `healthy: false`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectSystemServices(): Promise<StatusSystemSection>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectSystemServices = collectSystemServices;
|
|
4
|
+
/**
|
|
5
|
+
* System-information probe for the dashboard: the health of the core Ory
|
|
6
|
+
* backend services the plugin authenticates and authorizes against — Kratos
|
|
7
|
+
* (identities), Hydra (OAuth2), and Keto (permissions).
|
|
8
|
+
*
|
|
9
|
+
* This is additive to the shared {@link file://./status-data.ts} report: the
|
|
10
|
+
* dashboard renders it as a "System information" section, while the installer
|
|
11
|
+
* simply ignores it. Ports and health endpoints are the same constants the
|
|
12
|
+
* local-stack manager and `local status` use, so the surfaces never disagree.
|
|
13
|
+
*/
|
|
14
|
+
const configs_js_1 = require("./local/configs.js");
|
|
15
|
+
/** The three core Ory services, in display order. */
|
|
16
|
+
function systemServiceSpecs() {
|
|
17
|
+
return [
|
|
18
|
+
{ name: "Kratos", role: "Identities & sessions", port: configs_js_1.KRATOS_PUBLIC_PORT, healthPath: "/health/ready" },
|
|
19
|
+
{ name: "Hydra", role: "OAuth2 & OIDC", port: configs_js_1.HYDRA_PUBLIC_PORT, healthPath: "/health/ready" },
|
|
20
|
+
{ name: "Keto", role: "Permissions (Zanzibar relation checks)", port: configs_js_1.KETO_READ_PORT, healthPath: "/health/ready" },
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
async function probe(healthUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(healthUrl, { signal: AbortSignal.timeout(2500) });
|
|
26
|
+
if (res.ok)
|
|
27
|
+
return { healthy: true };
|
|
28
|
+
return { healthy: false, detail: `HTTP ${res.status}` };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return { healthy: false, detail: err instanceof Error ? err.message : String(err) };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Probe the core Ory services' readiness endpoints and return their health.
|
|
36
|
+
* Never throws — an unreachable service is reported as `healthy: false`.
|
|
37
|
+
*/
|
|
38
|
+
async function collectSystemServices() {
|
|
39
|
+
const specs = systemServiceSpecs();
|
|
40
|
+
const services = await Promise.all(specs.map(async (spec) => {
|
|
41
|
+
const url = `http://localhost:${spec.port}`;
|
|
42
|
+
const healthUrl = `${url}${spec.healthPath}`;
|
|
43
|
+
const { healthy, detail } = await probe(healthUrl);
|
|
44
|
+
const service = {
|
|
45
|
+
name: spec.name,
|
|
46
|
+
role: spec.role,
|
|
47
|
+
port: spec.port,
|
|
48
|
+
url,
|
|
49
|
+
healthUrl,
|
|
50
|
+
healthy,
|
|
51
|
+
detail,
|
|
52
|
+
};
|
|
53
|
+
return service;
|
|
54
|
+
}));
|
|
55
|
+
return { services };
|
|
56
|
+
}
|
package/dist/testing.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ export { runHarnessContractSuite, type HarnessContractAdapter, type ContractCont
|
|
|
14
14
|
* Pass overrides to customize (e.g. a different harness name).
|
|
15
15
|
*/
|
|
16
16
|
export declare function createMockClient(overrides?: Partial<ConstructorParameters<typeof OryAgentClient>[0]>): OryAgentClient;
|
|
17
|
+
/**
|
|
18
|
+
* Control what the exact-tuple existence probe ({@link OryAgentClient.relationshipExists},
|
|
19
|
+
* used by `ensureRelationship`) reports. Pass `true` to model a tuple that is
|
|
20
|
+
* already stored (the write is then skipped), `false` for absent (the write
|
|
21
|
+
* proceeds). Returns the spy so callers can e.g. `.mockRejectedValue(...)` to
|
|
22
|
+
* model a failed probe.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stubRelationshipExists(client: OryAgentClient, exists: boolean): import("vitest").Mock<(check: import("./types.js").PermissionCheck) => Promise<boolean>>;
|
|
17
25
|
/**
|
|
18
26
|
* Stub an internal API instance method on the client.
|
|
19
27
|
* Returns a vi.fn mock so callers can assert on calls.
|
package/dist/testing.js
CHANGED
|
@@ -42,6 +42,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.BATCH_SERVER_DENIED = exports.BATCH_SERVER_ALLOWED_TOOL_DENIED = exports.BATCH_BOTH_ALLOWED = exports.PERMISSION_DENIED = exports.PERMISSION_ALLOWED = exports.MOCK_INACTIVE_OAUTH2_RESPONSE = exports.MOCK_OAUTH2_RESPONSE = exports.MOCK_INACTIVE_SESSION_RESPONSE = exports.MOCK_SESSION_RESPONSE = exports.runHarnessContractSuite = void 0;
|
|
44
44
|
exports.createMockClient = createMockClient;
|
|
45
|
+
exports.stubRelationshipExists = stubRelationshipExists;
|
|
45
46
|
exports.stubApi = stubApi;
|
|
46
47
|
exports.makeAxiosError = makeAxiosError;
|
|
47
48
|
exports.makeNetworkError = makeNetworkError;
|
|
@@ -85,13 +86,31 @@ Object.defineProperty(exports, "runHarnessContractSuite", { enumerable: true, ge
|
|
|
85
86
|
* Pass overrides to customize (e.g. a different harness name).
|
|
86
87
|
*/
|
|
87
88
|
function createMockClient(overrides) {
|
|
88
|
-
|
|
89
|
+
const client = new client_js_1.OryAgentClient({
|
|
89
90
|
projectUrl: "https://test.projects.oryapis.com",
|
|
90
91
|
apiKey: "test-api-key",
|
|
91
92
|
harness: "test",
|
|
92
93
|
sessionCacheTtlMs: 0,
|
|
93
94
|
...overrides,
|
|
94
95
|
});
|
|
96
|
+
// `ensureRelationship` probes for the exact tuple before writing. Default
|
|
97
|
+
// to "no tuple exists" so a plain createRelationship-asserting test sees the
|
|
98
|
+
// write proceed; tests that model an already-present tuple override this via
|
|
99
|
+
// `stubRelationshipExists`. We spy the public `relationshipExists` method
|
|
100
|
+
// (not the internal getRelationships API) because setAgentPrincipal rebuilds
|
|
101
|
+
// the Keto API instance and would discard an API-level stub.
|
|
102
|
+
vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(false);
|
|
103
|
+
return client;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Control what the exact-tuple existence probe ({@link OryAgentClient.relationshipExists},
|
|
107
|
+
* used by `ensureRelationship`) reports. Pass `true` to model a tuple that is
|
|
108
|
+
* already stored (the write is then skipped), `false` for absent (the write
|
|
109
|
+
* proceeds). Returns the spy so callers can e.g. `.mockRejectedValue(...)` to
|
|
110
|
+
* model a failed probe.
|
|
111
|
+
*/
|
|
112
|
+
function stubRelationshipExists(client, exists) {
|
|
113
|
+
return vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(exists);
|
|
95
114
|
}
|
|
96
115
|
/**
|
|
97
116
|
* Stub an internal API instance method on the client.
|
package/dist/uninstall.d.ts
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Config teardown shared by every plugin's `uninstall` command.
|
|
3
3
|
*
|
|
4
|
-
* Uninstalling a plugin
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* inheriting stale
|
|
4
|
+
* Uninstalling a plugin wipes the shared config file completely — every
|
|
5
|
+
* value is removed, both *authentication* state (user tokens, agent DCR
|
|
6
|
+
* credentials, static API key) and *configuration* (project URL, OAuth2
|
|
7
|
+
* client id, audit mode, permission mode, user-login flag). The file is
|
|
8
|
+
* left as an empty object, so a later re-install starts from a clean
|
|
9
|
+
* slate rather than inheriting any stale state.
|
|
10
10
|
*
|
|
11
11
|
* What is removed:
|
|
12
12
|
* - the user's persisted OAuth2 tokens (`user`)
|
|
13
13
|
* - the agent's dynamic-registration credentials and every sub-agent
|
|
14
14
|
* registration (`agent`)
|
|
15
15
|
* - the operator-supplied static agent API key (`apiKey`)
|
|
16
|
+
* - all configuration values (`projectUrl`, `projectId`, `projectName`,
|
|
17
|
+
* `workspaceName`, `oauth2ClientId`, `auditOnly`, `permissionMode`,
|
|
18
|
+
* `userLogin`, `userSubjectNamespace`)
|
|
16
19
|
* - any dangling PKCE-flight lock
|
|
17
20
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* Ory project isn't left
|
|
21
|
-
* install/uninstall cycles.
|
|
22
|
-
*
|
|
21
|
+
* Server-side teardown runs first, best-effort:
|
|
22
|
+
* - Each dynamically-registered OAuth2 client (the agent and every
|
|
23
|
+
* sub-agent) is revoked via RFC 7592, so the Ory project isn't left
|
|
24
|
+
* with orphaned clients accumulating across install/uninstall cycles.
|
|
25
|
+
* - The project API key the installer minted is deleted via the Ory
|
|
26
|
+
* Console API (addressed by the persisted token id).
|
|
27
|
+
*
|
|
28
|
+
* The one server-side resource deliberately **left behind** is the shared
|
|
29
|
+
* user-login PKCE OAuth2 client (`oauth2ClientId`): it is a public,
|
|
30
|
+
* identity-free client that every user and every harness install on the
|
|
31
|
+
* project reuses, so deleting it would break other installations. It is
|
|
32
|
+
* only forgotten locally (its id is wiped from the config file). Every
|
|
33
|
+
* server-side step is best-effort: failures warn and local state is
|
|
34
|
+
* cleared regardless.
|
|
23
35
|
*/
|
|
24
36
|
export interface ClearCredentialsOptions {
|
|
25
37
|
/**
|
|
@@ -38,10 +50,16 @@ export interface ClearCredentialsResult {
|
|
|
38
50
|
revoked: number;
|
|
39
51
|
/** Number of dynamic clients whose server-side revoke failed. */
|
|
40
52
|
revokeFailures: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the project API key was deleted server-side (includes an
|
|
55
|
+
* already-gone key). `false` when there was none to delete, its token id
|
|
56
|
+
* wasn't tracked, or the deletion failed/was skipped.
|
|
57
|
+
*/
|
|
58
|
+
apiKeyDeleted: boolean;
|
|
41
59
|
}
|
|
42
60
|
/**
|
|
43
|
-
*
|
|
44
|
-
* dynamically-registered clients server-side
|
|
45
|
-
*
|
|
61
|
+
* Wipe the shared config file completely — every value, both credentials
|
|
62
|
+
* and configuration — revoking dynamically-registered clients server-side
|
|
63
|
+
* first. The file is left as an empty object. Never throws.
|
|
46
64
|
*/
|
|
47
65
|
export declare function clearCredentialsForUninstall(options?: ClearCredentialsOptions): Promise<ClearCredentialsResult>;
|