@stigmer/cli 3.12.9 → 3.14.0-dev.20260910084630
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/auth/login.d.ts.map +1 -1
- package/auth/login.js +30 -5
- package/auth/login.js.map +1 -1
- package/auth/token.d.ts +10 -5
- package/auth/token.d.ts.map +1 -1
- package/auth/token.js +30 -21
- package/auth/token.js.map +1 -1
- package/commands/apikey/index.d.ts.map +1 -1
- package/commands/apikey/index.js +11 -5
- package/commands/apikey/index.js.map +1 -1
- package/commands/auth/index.d.ts.map +1 -1
- package/commands/auth/index.js +19 -8
- package/commands/auth/index.js.map +1 -1
- package/commands/config/backend.d.ts.map +1 -1
- package/commands/config/backend.js +134 -12
- package/commands/config/backend.js.map +1 -1
- package/commands/connect.d.ts.map +1 -1
- package/commands/connect.js +6 -3
- package/commands/connect.js.map +1 -1
- package/commands/share.d.ts.map +1 -1
- package/commands/share.js +8 -6
- package/commands/share.js.map +1 -1
- package/commands/up.d.ts.map +1 -1
- package/commands/up.js +22 -6
- package/commands/up.js.map +1 -1
- package/config/config.d.ts +56 -7
- package/config/config.d.ts.map +1 -1
- package/config/config.js +115 -16
- package/config/config.js.map +1 -1
- package/config/index.d.ts +1 -1
- package/config/index.d.ts.map +1 -1
- package/config/index.js +1 -1
- package/config/index.js.map +1 -1
- package/config/keys.d.ts +1 -1
- package/config/keys.d.ts.map +1 -1
- package/config/keys.js +29 -6
- package/config/keys.js.map +1 -1
- package/config/resolve.d.ts +32 -21
- package/config/resolve.d.ts.map +1 -1
- package/config/resolve.js +80 -39
- package/config/resolve.js.map +1 -1
- package/local/constants.d.ts +8 -3
- package/local/constants.d.ts.map +1 -1
- package/local/constants.js +8 -3
- package/local/constants.js.map +1 -1
- package/local/daemon/components.d.ts.map +1 -1
- package/local/daemon/components.js +4 -2
- package/local/daemon/components.js.map +1 -1
- package/local/daemon/env.d.ts +16 -3
- package/local/daemon/env.d.ts.map +1 -1
- package/local/daemon/env.js +16 -3
- package/local/daemon/env.js.map +1 -1
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/daemon/launch.js +5 -4
- package/local/daemon/launch.js.map +1 -1
- package/local/daemon/process.d.ts.map +1 -1
- package/local/daemon/process.js +8 -5
- package/local/daemon/process.js.map +1 -1
- package/local/runtime/index.d.ts +2 -2
- package/local/runtime/index.d.ts.map +1 -1
- package/local/runtime/index.js +4 -2
- package/local/runtime/index.js.map +1 -1
- package/local/runtime/node.d.ts +8 -0
- package/local/runtime/node.d.ts.map +1 -1
- package/local/runtime/node.js +74 -23
- package/local/runtime/node.js.map +1 -1
- package/local/runtime/runner.d.ts +2 -1
- package/local/runtime/runner.d.ts.map +1 -1
- package/local/runtime/runner.js +4 -31
- package/local/runtime/runner.js.map +1 -1
- package/local/runtime/runtimes-install.d.ts +13 -0
- package/local/runtime/runtimes-install.d.ts.map +1 -0
- package/local/runtime/runtimes-install.js +60 -0
- package/local/runtime/runtimes-install.js.map +1 -0
- package/local/runtime/server.d.ts +26 -30
- package/local/runtime/server.d.ts.map +1 -1
- package/local/runtime/server.js +106 -108
- package/local/runtime/server.js.map +1 -1
- package/local/seedpack/content.d.ts +3 -3
- package/local/seedpack/content.d.ts.map +1 -1
- package/local/seedpack/content.js +19 -13
- package/local/seedpack/content.js.map +1 -1
- package/local/status.d.ts.map +1 -1
- package/local/status.js +4 -2
- package/local/status.js.map +1 -1
- package/local/webconsole/index.d.ts +2 -3
- package/local/webconsole/index.d.ts.map +1 -1
- package/local/webconsole/index.js +24 -10
- package/local/webconsole/index.js.map +1 -1
- package/package.json +5 -5
- package/resources/connect/connect.d.ts +4 -3
- package/resources/connect/connect.d.ts.map +1 -1
- package/resources/connect/connect.js +17 -5
- package/resources/connect/connect.js.map +1 -1
- package/resources/connect/oauth.d.ts +5 -2
- package/resources/connect/oauth.d.ts.map +1 -1
- package/resources/connect/oauth.js +5 -4
- package/resources/connect/oauth.js.map +1 -1
- package/src/auth/login.ts +54 -9
- package/src/auth/token.test.ts +69 -13
- package/src/auth/token.ts +46 -22
- package/src/client/client.test.ts +10 -2
- package/src/commands/apikey/index.ts +42 -11
- package/src/commands/auth/index.ts +52 -14
- package/src/commands/config/backend.test.ts +122 -0
- package/src/commands/config/backend.ts +195 -14
- package/src/commands/connect.test.ts +22 -4
- package/src/commands/connect.ts +37 -13
- package/src/commands/share.ts +36 -16
- package/src/commands/up.ts +24 -6
- package/src/config/config.test.ts +62 -6
- package/src/config/config.ts +165 -24
- package/src/config/index.ts +6 -0
- package/src/config/keys.test.ts +27 -4
- package/src/config/keys.ts +46 -9
- package/src/config/resolve.test.ts +135 -34
- package/src/config/resolve.ts +88 -38
- package/src/local/constants.ts +8 -4
- package/src/local/daemon/components.test.ts +9 -1
- package/src/local/daemon/components.ts +4 -2
- package/src/local/daemon/daemon.integration.test.ts +1 -1
- package/src/local/daemon/env.test.ts +15 -4
- package/src/local/daemon/env.ts +33 -5
- package/src/local/daemon/launch.ts +5 -4
- package/src/local/daemon/process.ts +7 -4
- package/src/local/runtime/index.ts +6 -5
- package/src/local/runtime/node.ts +99 -23
- package/src/local/runtime/runner.ts +5 -36
- package/src/local/runtime/runtime.test.ts +58 -14
- package/src/local/runtime/runtimes-install.test.ts +56 -0
- package/src/local/runtime/runtimes-install.ts +72 -0
- package/src/local/runtime/server.test.ts +178 -114
- package/src/local/runtime/server.ts +149 -129
- package/src/local/seedpack/content.ts +52 -21
- package/src/local/status.ts +4 -2
- package/src/local/webconsole/index.ts +27 -10
- package/src/resources/connect/connect.integration.test.ts +79 -26
- package/src/resources/connect/connect.ts +72 -19
- package/src/resources/connect/oauth.test.ts +38 -12
- package/src/resources/connect/oauth.ts +27 -11
- package/src/resources/share.test.ts +2 -2
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// pipes) we stop with actionable guidance instead of blocking for 5 minutes.
|
|
12
12
|
// Audit identity (reviewer) and token acquisition stay server-side by design.
|
|
13
13
|
import { create } from "@bufbuild/protobuf";
|
|
14
|
-
import { ConnectInputSchema, GetOAuthGrantStatusInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
15
|
-
import { connectAndWait, ConnectStillRunningError, CONNECT_SETTLE_BOUND_MS } from "@stigmer/sdk";
|
|
14
|
+
import { ConnectInputSchema, GetOAuthGrantStatusInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
15
|
+
import { connectAndWait, ConnectStillRunningError, CONNECT_SETTLE_BOUND_MS, } from "@stigmer/sdk";
|
|
16
16
|
import { CliExitError, ExitCode, UsageError } from "../../errors/index.js";
|
|
17
17
|
import { defaultRegistry } from "../../registry/index.js";
|
|
18
18
|
import { PlaceholderResolutionError } from "../mcp/placeholder-resolver.js";
|
|
@@ -100,7 +100,10 @@ async function resolveMcpServer(client, reference, org) {
|
|
|
100
100
|
const parsed = parseReference(reference, org, idPrefix);
|
|
101
101
|
if (parsed.kind === "id")
|
|
102
102
|
return client.mcpServer.get(parsed.id);
|
|
103
|
-
return client.mcpServer.getByReference({
|
|
103
|
+
return client.mcpServer.getByReference({
|
|
104
|
+
org: parsed.org,
|
|
105
|
+
slug: parsed.slug,
|
|
106
|
+
});
|
|
104
107
|
}
|
|
105
108
|
// Ensure an OAuth grant exists before connecting an auth-configured server that
|
|
106
109
|
// was given no --env credentials. On an interactive terminal, run the browser
|
|
@@ -128,14 +131,23 @@ async function ensureOAuthSatisfied(client, server, opts) {
|
|
|
128
131
|
if (!opts.interactive)
|
|
129
132
|
throw oauthGuidanceError(server, opts.reference, oauthOnly);
|
|
130
133
|
const { runOAuthFlow } = await import("./oauth.js");
|
|
131
|
-
await runOAuthFlow({
|
|
134
|
+
await runOAuthFlow({
|
|
135
|
+
client,
|
|
136
|
+
server,
|
|
137
|
+
org: opts.org,
|
|
138
|
+
consoleURL: opts.consoleURL,
|
|
139
|
+
probeLocalConsole: opts.probeLocalConsole,
|
|
140
|
+
});
|
|
132
141
|
}
|
|
133
142
|
// Turn a strict-resolution failure into actionable guidance. A declared-but-unset
|
|
134
143
|
// variable is the user's to provide; an undeclared one is a bug in the server
|
|
135
144
|
// definition. Both are far clearer than the cryptic subprocess crash (ENOENT on a
|
|
136
145
|
// literal "${VAR}" path) that motivated issue #141.
|
|
137
146
|
function unresolvedEnvError(server, err) {
|
|
138
|
-
const slug = server.metadata?.slug ??
|
|
147
|
+
const slug = server.metadata?.slug ??
|
|
148
|
+
server.metadata?.name ??
|
|
149
|
+
server.metadata?.id ??
|
|
150
|
+
"this server";
|
|
139
151
|
const decl = server.spec?.env?.[err.variableName];
|
|
140
152
|
if (decl) {
|
|
141
153
|
const hint = decl.description !== "" ? ` (${decl.description})` : "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../../src/resources/connect/connect.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,uEAAuE;AACvE,6EAA6E;AAC7E,yEAAyE;AACzE,cAAc;AACd,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAE9E,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../../src/resources/connect/connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
2
2
|
import type { Stigmer } from "@stigmer/sdk";
|
|
3
|
-
import type { BackendType } from "../../config/config.js";
|
|
4
3
|
/** Injectable side effects + inputs for {@link runOAuthFlow}. */
|
|
5
4
|
export interface OAuthFlowDeps {
|
|
6
5
|
readonly client: Stigmer;
|
|
7
6
|
readonly server: McpServer;
|
|
8
7
|
readonly org: string;
|
|
9
|
-
|
|
8
|
+
/** The web console origin (resolveConsoleURL over the caller's config). */
|
|
9
|
+
readonly consoleURL: string;
|
|
10
|
+
/** Probe the console before opening the browser (the local daemon's
|
|
11
|
+
* console may simply not be running; remote consoles answer or 404). */
|
|
12
|
+
readonly probeLocalConsole: boolean;
|
|
10
13
|
/** Opens the page URL in the user's browser. Defaults to the OS opener. */
|
|
11
14
|
readonly openBrowser?: (url: string) => Promise<void>;
|
|
12
15
|
/** Probes whether the local web console is reachable. Defaults to a 2s GET. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/resources/connect/oauth.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wDAAwD,CAAC;AAExF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/resources/connect/oauth.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wDAAwD,CAAC;AAExF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAO5C,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;4EACwE;IACxE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,oEAAoE;IACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gFAAgF;IAChF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BrE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1E;AAED,kEAAkE;AAClE,wBAAsB,eAAe,CACnC,MAAM,EAAE,OAAO,EACf,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAKlB;AAkDD,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,GAAG,EAAE,MAAM,GACV,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,CAAC,CAWhC"}
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
// real browser, network, or wall-clock delay.
|
|
13
13
|
import { create } from "@bufbuild/protobuf";
|
|
14
14
|
import { GetOAuthGrantStatusInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
|
|
15
|
-
import { resolveConsoleURL } from "../../config/index.js";
|
|
16
15
|
import { UsageError } from "../../errors/index.js";
|
|
17
16
|
const POLL_INTERVAL_MS = 3000;
|
|
18
17
|
const POLL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -24,8 +23,8 @@ const CONSOLE_PROBE_TIMEOUT_MS = 2000;
|
|
|
24
23
|
*/
|
|
25
24
|
export async function runOAuthFlow(deps) {
|
|
26
25
|
const log = deps.log ?? stderrLog;
|
|
27
|
-
const consoleURL =
|
|
28
|
-
if (deps.
|
|
26
|
+
const consoleURL = deps.consoleURL;
|
|
27
|
+
if (deps.probeLocalConsole) {
|
|
29
28
|
const probe = deps.probeConsole ?? probeWebConsole;
|
|
30
29
|
if (!(await probe(consoleURL)))
|
|
31
30
|
throw consoleUnavailableError(deps.server.metadata?.slug ?? "<slug>");
|
|
@@ -76,7 +75,9 @@ export async function checkOAuthGrant(client, mcpServerId, org) {
|
|
|
76
75
|
// an error status) means it's serving; a network/timeout failure means it isn't.
|
|
77
76
|
async function probeWebConsole(url) {
|
|
78
77
|
try {
|
|
79
|
-
const response = await fetch(url, {
|
|
78
|
+
const response = await fetch(url, {
|
|
79
|
+
signal: AbortSignal.timeout(CONSOLE_PROBE_TIMEOUT_MS),
|
|
80
|
+
});
|
|
80
81
|
// Drain the body so the socket can close promptly.
|
|
81
82
|
await response.body?.cancel();
|
|
82
83
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/resources/connect/oauth.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,8BAA8B;AAC9B,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,8CAA8C;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,8BAA8B,EAAE,MAAM,uDAAuD,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/resources/connect/oauth.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,8BAA8B;AAC9B,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,8CAA8C;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,8BAA8B,EAAE,MAAM,uDAAuD,CAAC;AAEvG,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACtC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAwBtC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAmB;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,SAAS,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IAEnC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,eAAe,CAAC;QACnD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;YAC5B,MAAM,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,QAAQ,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC;IAChD,MAAM,OAAO,GAAG,GAAG,UAAU,IAAI,IAAI,CAAC,GAAG,gBAAgB,IAAI,EAAE,CAAC;IAEhE,GAAG,CAAC,sCAAsC,IAAI,IAAI,CAAC,CAAC;IACpD,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACzD,GAAG,CAAC,4DAA4D,OAAO,IAAI,CAAC,CAAC;IAE7E,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,aAAa,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CACD,oFAAoF,CACrF,CAAC;IACJ,CAAC;IAED,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAmB;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,SAAS,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC;IAChD,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,eAAe,CAAC;IAEzC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IAEhE,SAAS,CAAC;QACR,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC9B,IAAI,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,UAAU,CAClB,2DAA2D,CAC5D,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3D,GAAG,CAAC,iCAAiC,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAe,EACf,WAAmB,EACnB,GAAW;IAEX,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,mBAAmB,CACvD,MAAM,CAAC,8BAA8B,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CACzE,CAAC;IACF,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED,gFAAgF;AAChF,iFAAiF;AACjF,KAAK,UAAU,eAAe,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,wBAAwB,CAAC;SACtD,CAAC,CAAC;QACH,mDAAmD;QACnD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,iFAAiF;AACjF,8EAA8E;AAC9E,4EAA4E;AAC5E,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO,IAAI,UAAU,CACnB,gFAAgF;QAC9E,uCAAuC;QACvC,gEAAgE,IAAI,oBAAoB;QACxF,6EAA6E,CAChF,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,gFAAgF;AAChF,wDAAwD;AACxD,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACrD,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC9D,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACxE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAC5B,QAAyB,EACzB,GAAW;IAEX,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,CAAC,UAAU,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC;QAC5D,KAAK,OAAO;YACV,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B;YACE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AACpC,CAAC"}
|
package/src/auth/login.ts
CHANGED
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
// Progress goes to stderr; the command layer renders the final result.
|
|
11
11
|
|
|
12
12
|
import open from "open";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CLOUD_BACKEND_NAME,
|
|
15
|
+
activeBackend,
|
|
16
|
+
load,
|
|
17
|
+
save,
|
|
18
|
+
} from "../config/index.js";
|
|
14
19
|
import { CliExitError, ExitCode } from "../errors/index.js";
|
|
15
20
|
import { log } from "../logger.js";
|
|
16
21
|
import { buildAuthorizeUrl, CALLBACK_PATH, CALLBACK_PORT } from "./auth0.js";
|
|
@@ -21,6 +26,27 @@ import { exchangeCode } from "./token.js";
|
|
|
21
26
|
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22
27
|
|
|
23
28
|
export async function login(): Promise<void> {
|
|
29
|
+
// Browser PKCE targets Stigmer Cloud only: the Auth0 coordinates are
|
|
30
|
+
// compiled in. From the local backend, login lands on (creating if
|
|
31
|
+
// needed) the reserved "cloud" entry — today's behavior in the named
|
|
32
|
+
// model. On a selfhost backend the flow refuses with the self-host
|
|
33
|
+
// credential story (API tokens) instead of silently writing cloud
|
|
34
|
+
// credentials the active backend would never send.
|
|
35
|
+
const preflight = load();
|
|
36
|
+
const { name, entry } = activeBackend(preflight);
|
|
37
|
+
if (entry?.type === "selfhost") {
|
|
38
|
+
throw new CliExitError(
|
|
39
|
+
`backend "${name}" is self-hosted — browser login targets Stigmer Cloud only`,
|
|
40
|
+
ExitCode.Usage,
|
|
41
|
+
[
|
|
42
|
+
"Authenticate a self-hosted backend with an API token:",
|
|
43
|
+
" stigmer apikey create --name cli",
|
|
44
|
+
` stigmer config backend add ${name} --endpoint <host:port> --api-key <key>`,
|
|
45
|
+
"or switch first: stigmer config backend use cloud",
|
|
46
|
+
],
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
const verifier = generateVerifier();
|
|
25
51
|
const codeChallenge = challengeS256(verifier);
|
|
26
52
|
const state = generateState();
|
|
@@ -30,30 +56,49 @@ export async function login(): Promise<void> {
|
|
|
30
56
|
try {
|
|
31
57
|
const authUrl = buildAuthorizeUrl({ state, codeChallenge });
|
|
32
58
|
process.stderr.write("Opening browser for authentication...\n");
|
|
33
|
-
process.stderr.write(
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`If the browser doesn't open automatically, visit:\n${authUrl}\n\n`,
|
|
61
|
+
);
|
|
34
62
|
|
|
35
63
|
try {
|
|
36
64
|
await open(authUrl);
|
|
37
65
|
} catch (err) {
|
|
38
|
-
log.debug(
|
|
39
|
-
|
|
66
|
+
log.debug(
|
|
67
|
+
`failed to open browser automatically: ${(err as Error).message}`,
|
|
68
|
+
);
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
"Could not open the browser automatically. Please open the URL above.\n",
|
|
71
|
+
);
|
|
40
72
|
}
|
|
41
73
|
|
|
42
74
|
const callback = await server.waitForCallback(LOGIN_TIMEOUT_MS);
|
|
43
75
|
if (callback.state !== state) {
|
|
44
|
-
throw new CliExitError(
|
|
76
|
+
throw new CliExitError(
|
|
77
|
+
"OAuth state mismatch — possible CSRF attack, aborting login",
|
|
78
|
+
ExitCode.Auth,
|
|
79
|
+
);
|
|
45
80
|
}
|
|
46
81
|
|
|
47
|
-
const tokens = await exchangeCode({
|
|
82
|
+
const tokens = await exchangeCode({
|
|
83
|
+
code: callback.code,
|
|
84
|
+
codeVerifier: verifier,
|
|
85
|
+
});
|
|
48
86
|
|
|
49
87
|
const config = load();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
88
|
+
const active = activeBackend(config);
|
|
89
|
+
// A cloud-type backend keeps its own login; anything else (the local
|
|
90
|
+
// daemon) lands on the reserved "cloud" entry and switches to it.
|
|
91
|
+
const targetName =
|
|
92
|
+
active.entry?.type === "cloud" ? active.name : CLOUD_BACKEND_NAME;
|
|
93
|
+
const backends = (config.backends ??= {});
|
|
94
|
+
backends[targetName] = {
|
|
95
|
+
...(backends[targetName] ?? { type: "cloud" }),
|
|
96
|
+
type: "cloud",
|
|
53
97
|
token: tokens.accessToken,
|
|
54
98
|
refresh_token: tokens.refreshToken,
|
|
55
99
|
token_expiry: tokens.expiresAt,
|
|
56
100
|
};
|
|
101
|
+
config.current_backend = targetName;
|
|
57
102
|
save(config);
|
|
58
103
|
} finally {
|
|
59
104
|
await server.shutdown();
|
package/src/auth/token.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdtempSync } from "node:fs";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import type { Config } from "../config/index.js";
|
|
5
|
+
import type { Config, NamedBackendConfig } from "../config/index.js";
|
|
6
6
|
import { createRefreshingTokenProvider } from "./token.js";
|
|
7
7
|
|
|
8
8
|
// The provider persists refreshed tokens via config.save(), which writes to
|
|
@@ -10,11 +10,35 @@ import { createRefreshingTokenProvider } from "./token.js";
|
|
|
10
10
|
// the developer's real config.
|
|
11
11
|
let originalHome: string | undefined;
|
|
12
12
|
|
|
13
|
-
function cloudConfig(overrides: Partial<
|
|
14
|
-
return {
|
|
13
|
+
function cloudConfig(overrides: Partial<NamedBackendConfig>): Config {
|
|
14
|
+
return {
|
|
15
|
+
backend: { type: "cloud" },
|
|
16
|
+
backends: {
|
|
17
|
+
cloud: { type: "cloud", endpoint: "api.stigmer.ai:443", ...overrides },
|
|
18
|
+
},
|
|
19
|
+
current_backend: "cloud",
|
|
20
|
+
};
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
function
|
|
23
|
+
function selfhostConfig(overrides: Partial<NamedBackendConfig> = {}): Config {
|
|
24
|
+
return {
|
|
25
|
+
backend: { type: "cloud" },
|
|
26
|
+
backends: {
|
|
27
|
+
staging: {
|
|
28
|
+
type: "selfhost",
|
|
29
|
+
endpoint: "stigmer.example.com:7234",
|
|
30
|
+
...overrides,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
current_backend: "staging",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mockFetch(response: {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
status?: number;
|
|
40
|
+
body: unknown;
|
|
41
|
+
}): void {
|
|
18
42
|
vi.stubGlobal(
|
|
19
43
|
"fetch",
|
|
20
44
|
vi.fn(async () => ({
|
|
@@ -42,7 +66,9 @@ describe("createRefreshingTokenProvider", () => {
|
|
|
42
66
|
process.env.STIGMER_API_KEY = "env-key";
|
|
43
67
|
const fetchSpy = vi.fn();
|
|
44
68
|
vi.stubGlobal("fetch", fetchSpy);
|
|
45
|
-
const provider = createRefreshingTokenProvider(
|
|
69
|
+
const provider = createRefreshingTokenProvider(
|
|
70
|
+
cloudConfig({ token: "stale", token_expiry: past() }),
|
|
71
|
+
);
|
|
46
72
|
expect(await provider()).toBe("env-key");
|
|
47
73
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
48
74
|
});
|
|
@@ -50,24 +76,52 @@ describe("createRefreshingTokenProvider", () => {
|
|
|
50
76
|
it("returns a non-expired token without refreshing", async () => {
|
|
51
77
|
const fetchSpy = vi.fn();
|
|
52
78
|
vi.stubGlobal("fetch", fetchSpy);
|
|
53
|
-
const provider = createRefreshingTokenProvider(
|
|
79
|
+
const provider = createRefreshingTokenProvider(
|
|
80
|
+
cloudConfig({ token: "good", token_expiry: future() }),
|
|
81
|
+
);
|
|
54
82
|
expect(await provider()).toBe("good");
|
|
55
83
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
56
84
|
});
|
|
57
85
|
|
|
58
|
-
it("refreshes an expired token and persists the new one", async () => {
|
|
59
|
-
mockFetch({
|
|
60
|
-
|
|
86
|
+
it("refreshes an expired token and persists the new one into the entry", async () => {
|
|
87
|
+
mockFetch({
|
|
88
|
+
ok: true,
|
|
89
|
+
body: { access_token: "fresh", refresh_token: "r2", expires_in: 3600 },
|
|
90
|
+
});
|
|
91
|
+
const config = cloudConfig({
|
|
92
|
+
token: "expired",
|
|
93
|
+
token_expiry: past(),
|
|
94
|
+
refresh_token: "r1",
|
|
95
|
+
});
|
|
61
96
|
const provider = createRefreshingTokenProvider(config);
|
|
62
97
|
expect(await provider()).toBe("fresh");
|
|
63
|
-
expect(config.
|
|
64
|
-
expect(config.
|
|
98
|
+
expect(config.backends?.["cloud"]?.token).toBe("fresh");
|
|
99
|
+
expect(config.backends?.["cloud"]?.refresh_token).toBe("r2");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("returns the selfhost entry's api_key without any refresh machinery", async () => {
|
|
103
|
+
const fetchSpy = vi.fn();
|
|
104
|
+
vi.stubGlobal("fetch", fetchSpy);
|
|
105
|
+
const provider = createRefreshingTokenProvider(
|
|
106
|
+
selfhostConfig({ api_key: "stk_stored" }),
|
|
107
|
+
);
|
|
108
|
+
expect(await provider()).toBe("stk_stored");
|
|
109
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns null on a selfhost backend with no stored key", async () => {
|
|
113
|
+
const provider = createRefreshingTokenProvider(selfhostConfig());
|
|
114
|
+
expect(await provider()).toBeNull();
|
|
65
115
|
});
|
|
66
116
|
|
|
67
117
|
it("falls back to the existing token when refresh fails", async () => {
|
|
68
118
|
mockFetch({ ok: false, status: 401, body: { error: "invalid_grant" } });
|
|
69
119
|
const provider = createRefreshingTokenProvider(
|
|
70
|
-
cloudConfig({
|
|
120
|
+
cloudConfig({
|
|
121
|
+
token: "expired",
|
|
122
|
+
token_expiry: past(),
|
|
123
|
+
refresh_token: "r1",
|
|
124
|
+
}),
|
|
71
125
|
);
|
|
72
126
|
expect(await provider()).toBe("expired");
|
|
73
127
|
});
|
|
@@ -80,7 +134,9 @@ describe("createRefreshingTokenProvider", () => {
|
|
|
80
134
|
it("treats a token without an expiry as valid (legacy)", async () => {
|
|
81
135
|
const fetchSpy = vi.fn();
|
|
82
136
|
vi.stubGlobal("fetch", fetchSpy);
|
|
83
|
-
const provider = createRefreshingTokenProvider(
|
|
137
|
+
const provider = createRefreshingTokenProvider(
|
|
138
|
+
cloudConfig({ token: "legacy" }),
|
|
139
|
+
);
|
|
84
140
|
expect(await provider()).toBe("legacy");
|
|
85
141
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
86
142
|
});
|
package/src/auth/token.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
// expired access token, and expose a token provider that refreshes silently.
|
|
3
3
|
|
|
4
4
|
import type { TokenProvider } from "@stigmer/sdk";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type Config,
|
|
7
|
+
type NamedBackendConfig,
|
|
8
|
+
activeBackend,
|
|
9
|
+
save,
|
|
10
|
+
} from "../config/index.js";
|
|
6
11
|
import { CliExitError, ExitCode } from "../errors/index.js";
|
|
7
12
|
import { CLIENT_ID, callbackUrl, TOKEN_URL } from "./auth0.js";
|
|
8
13
|
|
|
@@ -28,7 +33,10 @@ interface Auth0TokenResponse {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/** Exchange a PKCE authorization code for an access (+refresh) token. */
|
|
31
|
-
export async function exchangeCode(params: {
|
|
36
|
+
export async function exchangeCode(params: {
|
|
37
|
+
code: string;
|
|
38
|
+
codeVerifier: string;
|
|
39
|
+
}): Promise<TokenSet> {
|
|
32
40
|
return postToken({
|
|
33
41
|
grant_type: "authorization_code",
|
|
34
42
|
client_id: CLIENT_ID,
|
|
@@ -39,7 +47,9 @@ export async function exchangeCode(params: { code: string; codeVerifier: string
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
/** Exchange a refresh token for a fresh access token. */
|
|
42
|
-
export async function refreshAccessToken(
|
|
50
|
+
export async function refreshAccessToken(
|
|
51
|
+
refreshToken: string,
|
|
52
|
+
): Promise<TokenSet> {
|
|
43
53
|
return postToken({
|
|
44
54
|
grant_type: "refresh_token",
|
|
45
55
|
client_id: CLIENT_ID,
|
|
@@ -50,12 +60,17 @@ export async function refreshAccessToken(refreshToken: string): Promise<TokenSet
|
|
|
50
60
|
/**
|
|
51
61
|
* A token provider for the backend client that keeps the access token fresh.
|
|
52
62
|
*
|
|
53
|
-
* Precedence and behavior, evaluated per request
|
|
63
|
+
* Precedence and behavior, evaluated per request against the ACTIVE named
|
|
64
|
+
* backend:
|
|
54
65
|
* 1. STIGMER_API_KEY (env / bridged --api-key) wins and is never refreshed.
|
|
55
|
-
* 2.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
66
|
+
* 2. selfhost backends return their stored api_key (no refresh machinery —
|
|
67
|
+
* server-minted `stk_` tokens are opaque and live until deleted or
|
|
68
|
+
* expired server-side); local returns null (no auth).
|
|
69
|
+
* 3. A cloud backend's non-expired access token is returned as-is.
|
|
70
|
+
* 4. An expired token with a refresh token is refreshed; the new token,
|
|
71
|
+
* rotated refresh token, and expiry are persisted INTO THE ENTRY, then
|
|
72
|
+
* returned.
|
|
73
|
+
* 5. Otherwise the (possibly stale) token or null is returned, letting the
|
|
59
74
|
* server reject it so the error maps to a clear "please log in" exit.
|
|
60
75
|
*
|
|
61
76
|
* Closes over `config` so a refresh updates the same object the rest of the
|
|
@@ -66,30 +81,38 @@ export function createRefreshingTokenProvider(config: Config): TokenProvider {
|
|
|
66
81
|
const envKey = process.env.STIGMER_API_KEY;
|
|
67
82
|
if (envKey !== undefined && envKey !== "") return envKey;
|
|
68
83
|
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
84
|
+
const { entry } = activeBackend(config);
|
|
85
|
+
if (entry === undefined) return null;
|
|
86
|
+
if (entry.type === "selfhost") return entry.api_key ?? null;
|
|
87
|
+
|
|
88
|
+
if (entry.token === undefined || entry.token === "") {
|
|
89
|
+
return entry.refresh_token
|
|
90
|
+
? refreshAndPersist(config, entry, entry.refresh_token)
|
|
91
|
+
: null;
|
|
72
92
|
}
|
|
73
|
-
if (!isExpired(
|
|
74
|
-
if (
|
|
75
|
-
|
|
93
|
+
if (!isExpired(entry.token_expiry)) return entry.token;
|
|
94
|
+
if (entry.refresh_token === undefined || entry.refresh_token === "")
|
|
95
|
+
return entry.token;
|
|
96
|
+
return refreshAndPersist(config, entry, entry.refresh_token);
|
|
76
97
|
};
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
async function refreshAndPersist(
|
|
100
|
+
async function refreshAndPersist(
|
|
101
|
+
config: Config,
|
|
102
|
+
entry: NamedBackendConfig,
|
|
103
|
+
refreshToken: string,
|
|
104
|
+
): Promise<string | null> {
|
|
80
105
|
let next: TokenSet;
|
|
81
106
|
try {
|
|
82
107
|
next = await refreshAccessToken(refreshToken);
|
|
83
108
|
} catch {
|
|
84
109
|
// Refresh failed (revoked/expired refresh token). Fall back to the current
|
|
85
110
|
// token if any; the server will reject it and the user is prompted to log in.
|
|
86
|
-
return
|
|
111
|
+
return entry.token ?? null;
|
|
87
112
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
cloud.token_expiry = next.expiresAt;
|
|
92
|
-
config.backend.cloud = cloud;
|
|
113
|
+
entry.token = next.accessToken;
|
|
114
|
+
if (next.refreshToken !== undefined) entry.refresh_token = next.refreshToken;
|
|
115
|
+
entry.token_expiry = next.expiresAt;
|
|
93
116
|
save(config);
|
|
94
117
|
return next.accessToken;
|
|
95
118
|
}
|
|
@@ -120,7 +143,8 @@ async function postToken(form: Record<string, string>): Promise<TokenSet> {
|
|
|
120
143
|
|
|
121
144
|
const body = (await response.json().catch(() => ({}))) as Auth0TokenResponse;
|
|
122
145
|
if (!response.ok || body.access_token === undefined) {
|
|
123
|
-
const detail =
|
|
146
|
+
const detail =
|
|
147
|
+
body.error_description ?? body.error ?? `HTTP ${response.status}`;
|
|
124
148
|
throw new CliExitError(`authentication failed: ${detail}`, ExitCode.Auth);
|
|
125
149
|
}
|
|
126
150
|
|
|
@@ -4,7 +4,13 @@ import type { Config } from "../config/index.js";
|
|
|
4
4
|
import { createBackendClient } from "./client.js";
|
|
5
5
|
|
|
6
6
|
function cloud(token?: string): Config {
|
|
7
|
-
return {
|
|
7
|
+
return {
|
|
8
|
+
backend: { type: "cloud" },
|
|
9
|
+
backends: {
|
|
10
|
+
cloud: { type: "cloud", endpoint: "api.stigmer.ai:443", token },
|
|
11
|
+
},
|
|
12
|
+
current_backend: "cloud",
|
|
13
|
+
};
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
afterEach(() => {
|
|
@@ -19,7 +25,9 @@ describe("createBackendClient — endpoint normalization", () => {
|
|
|
19
25
|
});
|
|
20
26
|
|
|
21
27
|
it("normalizes the local endpoint to a plaintext base URL", () => {
|
|
22
|
-
const client = createBackendClient({
|
|
28
|
+
const client = createBackendClient({
|
|
29
|
+
config: { backend: { type: "local" } },
|
|
30
|
+
});
|
|
23
31
|
expect(client.stigmer.baseUrl).toBe("http://localhost:7234");
|
|
24
32
|
});
|
|
25
33
|
});
|
|
@@ -6,11 +6,22 @@
|
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { create } from "@bufbuild/protobuf";
|
|
8
8
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ApiKey,
|
|
11
|
+
ApiKeySchema,
|
|
12
|
+
} from "@stigmer/protos/ai/stigmer/iam/apikey/v1/api_pb";
|
|
10
13
|
import { ApiKeyHashSchema } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/io_pb";
|
|
11
14
|
import type { Command } from "commander";
|
|
12
|
-
import {
|
|
13
|
-
|
|
15
|
+
import {
|
|
16
|
+
ensureAuthenticated,
|
|
17
|
+
resolveContextOrganization,
|
|
18
|
+
} from "../../config/index.js";
|
|
19
|
+
import {
|
|
20
|
+
type OutputFlags,
|
|
21
|
+
type OutputFormat,
|
|
22
|
+
renderProtoJson,
|
|
23
|
+
renderProtoYaml,
|
|
24
|
+
} from "../../output/index.js";
|
|
14
25
|
import { addReadFlags, readFormat } from "../shared.js";
|
|
15
26
|
import { parseExpiration } from "./duration.js";
|
|
16
27
|
|
|
@@ -23,7 +34,9 @@ interface ApiKeyCreateFlags extends OutputFlags {
|
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
export function registerApiKey(program: Command): void {
|
|
26
|
-
const apikey = program
|
|
37
|
+
const apikey = program
|
|
38
|
+
.command("apikey")
|
|
39
|
+
.description("manage API keys for Stigmer Cloud authentication");
|
|
27
40
|
|
|
28
41
|
const create = apikey
|
|
29
42
|
.command("create")
|
|
@@ -71,14 +84,23 @@ async function runCreate(options: ApiKeyCreateFlags): Promise<void> {
|
|
|
71
84
|
process.stdout.write(renderCreatedBanner(created));
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
async function runFingerprint(
|
|
75
|
-
|
|
87
|
+
async function runFingerprint(
|
|
88
|
+
rawKey: string,
|
|
89
|
+
options: OutputFlags,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
// Base64URL without padding — the server's storage encoding (the Java
|
|
92
|
+
// ApiKeyHasher and the TS keymaterial module agree). This was hex until
|
|
93
|
+
// O3, which meant the computed hash could never match a stored key_hash
|
|
94
|
+
// and the lookup below always answered NotFound (gate ruling Q7).
|
|
95
|
+
const hash = createHash("sha256").update(rawKey).digest("base64url");
|
|
76
96
|
|
|
77
97
|
const { connectBackend } = await import("../../backend.js");
|
|
78
98
|
const client = connectBackend();
|
|
79
99
|
ensureAuthenticated(client.config);
|
|
80
100
|
|
|
81
|
-
const key = await client.stigmer.apiKey.getByKeyHash(
|
|
101
|
+
const key = await client.stigmer.apiKey.getByKeyHash(
|
|
102
|
+
create(ApiKeyHashSchema, { value: hash }),
|
|
103
|
+
);
|
|
82
104
|
renderApiKey(key, readFormat(options));
|
|
83
105
|
}
|
|
84
106
|
|
|
@@ -101,7 +123,8 @@ function renderApiKey(key: ApiKey, format: OutputFormat): void {
|
|
|
101
123
|
}
|
|
102
124
|
const lines = [`API Key: ${key.metadata?.id ?? ""}`, ""];
|
|
103
125
|
if (key.metadata?.name) lines.push(` Name: ${key.metadata.name}`);
|
|
104
|
-
if (key.spec?.fingerprint)
|
|
126
|
+
if (key.spec?.fingerprint)
|
|
127
|
+
lines.push(` Fingerprint: ***${key.spec.fingerprint}`);
|
|
105
128
|
lines.push(` Expires: ${formatExpiry(key)}`);
|
|
106
129
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
107
130
|
}
|
|
@@ -121,13 +144,21 @@ function renderCreatedBanner(key: ApiKey): string {
|
|
|
121
144
|
` ID: ${key.metadata?.id ?? ""}`,
|
|
122
145
|
];
|
|
123
146
|
if (key.metadata?.name) lines.push(` Name: ${key.metadata.name}`);
|
|
124
|
-
if (key.spec?.fingerprint)
|
|
125
|
-
|
|
147
|
+
if (key.spec?.fingerprint)
|
|
148
|
+
lines.push(` Fingerprint: ***${key.spec.fingerprint}`);
|
|
149
|
+
lines.push(
|
|
150
|
+
` Expires: ${formatExpiry(key)}`,
|
|
151
|
+
"",
|
|
152
|
+
"Usage:",
|
|
153
|
+
` export STIGMER_API_KEY='${key.spec?.keyHash ?? ""}'`,
|
|
154
|
+
"",
|
|
155
|
+
);
|
|
126
156
|
return `${lines.join("\n")}\n`;
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
function formatExpiry(key: ApiKey): string {
|
|
130
160
|
if (key.spec?.neverExpires) return "Never";
|
|
131
|
-
if (key.spec?.expiresAt !== undefined)
|
|
161
|
+
if (key.spec?.expiresAt !== undefined)
|
|
162
|
+
return timestampDate(key.spec.expiresAt).toISOString();
|
|
132
163
|
return "Never";
|
|
133
164
|
}
|