@skrr-ai/cli 0.1.79 → 0.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +4 -0
- package/dist/base-command.js +40 -4
- package/dist/commands/agents/visibility.d.ts +14 -0
- package/dist/commands/agents/visibility.js +52 -0
- package/dist/commands/create-token.d.ts +0 -1
- package/dist/commands/create-token.js +3 -1
- package/dist/commands/inbox/index.js +4 -2
- package/dist/commands/list-tokens.d.ts +0 -1
- package/dist/commands/list-tokens.js +1 -1
- package/dist/commands/login.d.ts +8 -3
- package/dist/commands/login.js +14 -4
- package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
- package/dist/commands/revoke-token.d.ts +0 -1
- package/dist/commands/revoke-token.js +1 -1
- package/dist/commands/spaces/workflows/events.js +9 -2
- package/dist/commands/store/follow.d.ts +1 -0
- package/dist/commands/store/follow.js +7 -1
- package/dist/commands/store/follows/list.d.ts +11 -0
- package/dist/commands/store/follows/list.js +29 -0
- package/dist/commands/store/follows/remove.d.ts +16 -0
- package/dist/commands/store/follows/remove.js +37 -0
- package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
- package/dist/commands/store/publishers/profile/draft.js +54 -0
- package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
- package/dist/commands/store/publishers/profile/handle.js +39 -0
- package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
- package/dist/commands/store/publishers/profile/hide.js +39 -0
- package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
- package/dist/commands/store/publishers/profile/preview.js +39 -0
- package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
- package/dist/commands/store/publishers/profile/publish.js +41 -0
- package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
- package/dist/commands/store/publishers/profile/restore.js +49 -0
- package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
- package/dist/commands/store/publishers/profile/revisions.js +30 -0
- package/dist/commands/store/publishers/profile/show.d.ts +11 -0
- package/dist/commands/store/publishers/profile/show.js +39 -0
- package/dist/commands/store/review.d.ts +26 -0
- package/dist/commands/store/review.js +93 -0
- package/dist/commands/store/reviews.d.ts +29 -0
- package/dist/commands/store/reviews.js +170 -0
- package/dist/commands/store/tip.d.ts +20 -0
- package/dist/commands/store/tip.js +57 -0
- package/dist/commands/tokens/create.d.ts +28 -0
- package/dist/commands/tokens/create.js +136 -0
- package/dist/commands/tokens/list.d.ts +11 -0
- package/dist/commands/tokens/list.js +53 -0
- package/dist/commands/tokens/revoke.d.ts +14 -0
- package/dist/commands/tokens/revoke.js +42 -0
- package/dist/lib/access-tokens.d.ts +59 -0
- package/dist/lib/access-tokens.js +50 -0
- package/dist/lib/agentic-stream.js +13 -4
- package/dist/lib/api-fetch.js +6 -2
- package/dist/lib/auth-core-init.d.ts +6 -0
- package/dist/lib/auth-core-init.js +28 -6
- package/dist/lib/brokered-dpop.d.ts +34 -0
- package/dist/lib/brokered-dpop.js +89 -0
- package/dist/lib/credential-resolver.d.ts +1 -1
- package/dist/lib/credential-resolver.js +8 -2
- package/dist/lib/daemonBroker.d.ts +84 -4
- package/dist/lib/daemonBroker.js +209 -40
- package/dist/lib/daemonBrokerRefusal.d.ts +26 -0
- package/dist/lib/daemonBrokerRefusal.js +83 -4
- package/dist/lib/daemonRestartWait.d.ts +39 -0
- package/dist/lib/daemonRestartWait.js +181 -0
- package/dist/lib/dedicated-service.js +10 -5
- package/dist/lib/dedicated-ssh.d.ts +9 -0
- package/dist/lib/dedicated-ssh.js +235 -63
- package/dist/lib/dedicated-terminal.js +6 -3
- package/dist/lib/dpop-auth.d.ts +35 -0
- package/dist/lib/dpop-auth.js +57 -0
- package/dist/lib/login.d.ts +6 -0
- package/dist/lib/login.js +13 -4
- package/dist/lib/node-adapter.js +6 -2
- package/dist/lib/publisher-profile-flags.d.ts +19 -0
- package/dist/lib/publisher-profile-flags.js +48 -0
- package/dist/lib/refresh.d.ts +1 -1
- package/dist/lib/refresh.js +6 -1
- package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
- package/dist/lib/ssh-ordered-bytes.js +31 -0
- package/dist/lib/usd-micros.d.ts +9 -0
- package/dist/lib/usd-micros.js +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +35 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +42 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +179 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +32 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +39 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3895 -3882
- package/oclif.manifest.json +34985 -33643
- package/package.json +28 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dpop-auth.ts — attach RFC 9449 proofs for a DPoP-bound hand-off token
|
|
3
|
+
* (OSK-12020).
|
|
4
|
+
*
|
|
5
|
+
* The ONE place a request path asks for a proof, so every transport the CLI
|
|
6
|
+
* has — `apiFetch`, the data-service node adapter, SSE, and Socket.IO — binds
|
|
7
|
+
* the same way. For any other credential both helpers are no-ops and the wire
|
|
8
|
+
* is byte-identical to before.
|
|
9
|
+
*
|
|
10
|
+
* The CLI never holds the key. Each proof comes from the local daemon
|
|
11
|
+
* (`brokeredDpopProof`), which keeps the private half in its own memory; that
|
|
12
|
+
* is what makes a copied token useless anywhere but on this machine.
|
|
13
|
+
*/
|
|
14
|
+
/** RFC 9449 §4.1. */
|
|
15
|
+
export declare const DPOP_HEADER = "DPoP";
|
|
16
|
+
/** Headers to add to one HTTP request made with `token`; `{}` when unbound. */
|
|
17
|
+
export declare function dpopHeadersFor(token: string | null | undefined, method: string, url: string): Promise<Record<string, string>>;
|
|
18
|
+
/**
|
|
19
|
+
* The URI a Socket.IO handshake proof commits to: the realtime origin's
|
|
20
|
+
* http(s) form plus the engine path, no trailing slash — what the server
|
|
21
|
+
* rebuilds from the handshake request (`reconstructHandshakeHtu`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function socketHandshakeHtu(wsOrigin: string, ioPath?: string): string;
|
|
24
|
+
type SocketAuth = Record<string, unknown>;
|
|
25
|
+
type SocketAuthFn = (cb: (data: SocketAuth) => void) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Socket.IO `auth` for a handshake made with `auth.token`. Unbound: the object
|
|
28
|
+
* itself, unchanged. Bound: a FUNCTION, which socket.io-client calls on every
|
|
29
|
+
* (re)connect — so each handshake presents a fresh, single-use proof instead
|
|
30
|
+
* of replaying the first one into the server's jti cache.
|
|
31
|
+
*/
|
|
32
|
+
export declare function socketAuthWithDpop(auth: SocketAuth & {
|
|
33
|
+
token?: string | null;
|
|
34
|
+
}, wsOrigin: string, ioPath?: string): SocketAuth | SocketAuthFn;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* dpop-auth.ts — attach RFC 9449 proofs for a DPoP-bound hand-off token
|
|
4
|
+
* (OSK-12020).
|
|
5
|
+
*
|
|
6
|
+
* The ONE place a request path asks for a proof, so every transport the CLI
|
|
7
|
+
* has — `apiFetch`, the data-service node adapter, SSE, and Socket.IO — binds
|
|
8
|
+
* the same way. For any other credential both helpers are no-ops and the wire
|
|
9
|
+
* is byte-identical to before.
|
|
10
|
+
*
|
|
11
|
+
* The CLI never holds the key. Each proof comes from the local daemon
|
|
12
|
+
* (`brokeredDpopProof`), which keeps the private half in its own memory; that
|
|
13
|
+
* is what makes a copied token useless anywhere but on this machine.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.DPOP_HEADER = void 0;
|
|
17
|
+
exports.dpopHeadersFor = dpopHeadersFor;
|
|
18
|
+
exports.socketHandshakeHtu = socketHandshakeHtu;
|
|
19
|
+
exports.socketAuthWithDpop = socketAuthWithDpop;
|
|
20
|
+
const brokered_dpop_1 = require("./brokered-dpop");
|
|
21
|
+
/** RFC 9449 §4.1. */
|
|
22
|
+
exports.DPOP_HEADER = 'DPoP';
|
|
23
|
+
/** Headers to add to one HTTP request made with `token`; `{}` when unbound. */
|
|
24
|
+
async function dpopHeadersFor(token, method, url) {
|
|
25
|
+
if (!(0, brokered_dpop_1.isDpopBoundHandoffToken)(token))
|
|
26
|
+
return {};
|
|
27
|
+
const proof = await (0, brokered_dpop_1.brokeredDpopProof)(token, method, url);
|
|
28
|
+
return proof ? { [exports.DPOP_HEADER]: proof } : {};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The URI a Socket.IO handshake proof commits to: the realtime origin's
|
|
32
|
+
* http(s) form plus the engine path, no trailing slash — what the server
|
|
33
|
+
* rebuilds from the handshake request (`reconstructHandshakeHtu`).
|
|
34
|
+
*/
|
|
35
|
+
function socketHandshakeHtu(wsOrigin, ioPath = '/ws/agentic') {
|
|
36
|
+
const u = new URL(wsOrigin);
|
|
37
|
+
u.protocol = u.protocol === 'wss:' ? 'https:' : u.protocol === 'ws:' ? 'http:' : u.protocol;
|
|
38
|
+
u.pathname = ioPath.replace(/\/+$/, '');
|
|
39
|
+
u.search = '';
|
|
40
|
+
u.hash = '';
|
|
41
|
+
return u.toString();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Socket.IO `auth` for a handshake made with `auth.token`. Unbound: the object
|
|
45
|
+
* itself, unchanged. Bound: a FUNCTION, which socket.io-client calls on every
|
|
46
|
+
* (re)connect — so each handshake presents a fresh, single-use proof instead
|
|
47
|
+
* of replaying the first one into the server's jti cache.
|
|
48
|
+
*/
|
|
49
|
+
function socketAuthWithDpop(auth, wsOrigin, ioPath = '/ws/agentic') {
|
|
50
|
+
const token = typeof auth.token === 'string' ? auth.token : null;
|
|
51
|
+
if (!(0, brokered_dpop_1.isDpopBoundHandoffToken)(token))
|
|
52
|
+
return auth;
|
|
53
|
+
const htu = socketHandshakeHtu(wsOrigin, ioPath);
|
|
54
|
+
return (cb) => {
|
|
55
|
+
void (0, brokered_dpop_1.brokeredDpopProof)(token, 'GET', htu).then((proof) => cb(proof ? { ...auth, dpop: proof } : auth), () => cb(auth));
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/lib/login.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface CliLoginWithRefreshTokenOptions {
|
|
|
42
42
|
* What a shell with no terminal can do instead (OSK-12117). The refusal used to
|
|
43
43
|
* name only CI tokens, which is not a sign-in: an agent session asking for one
|
|
44
44
|
* could only hand the job to a person typing `! skrr login`.
|
|
45
|
+
*
|
|
46
|
+
* It also used to send CI to `skrr create-token` + `OVERSKY_TOKEN=osk_ci_...`.
|
|
47
|
+
* That token only bootstraps a daemon; every `skrr` command rejects it with 401
|
|
48
|
+
* (create-token.ts, base-command.ts), so the advice was a dead end (OSK-13209).
|
|
49
|
+
* The route that works today is a minted machine session — see
|
|
50
|
+
* docs/cli/SKRR_AUTH.md "CI today" for its caveats.
|
|
45
51
|
*/
|
|
46
52
|
export declare const HEADLESS_LOGIN_HINT: string;
|
|
47
53
|
/**
|
package/dist/lib/login.js
CHANGED
|
@@ -43,10 +43,18 @@ const daemonBrokerRefusal_1 = require("./daemonBrokerRefusal");
|
|
|
43
43
|
* What a shell with no terminal can do instead (OSK-12117). The refusal used to
|
|
44
44
|
* name only CI tokens, which is not a sign-in: an agent session asking for one
|
|
45
45
|
* could only hand the job to a person typing `! skrr login`.
|
|
46
|
+
*
|
|
47
|
+
* It also used to send CI to `skrr create-token` + `OVERSKY_TOKEN=osk_ci_...`.
|
|
48
|
+
* That token only bootstraps a daemon; every `skrr` command rejects it with 401
|
|
49
|
+
* (create-token.ts, base-command.ts), so the advice was a dead end (OSK-13209).
|
|
50
|
+
* The route that works today is a minted machine session — see
|
|
51
|
+
* docs/cli/SKRR_AUTH.md "CI today" for its caveats.
|
|
46
52
|
*/
|
|
47
53
|
exports.HEADLESS_LOGIN_HINT = '`skrr login --device-code` works without a terminal: it prints a URL and a code for a ' +
|
|
48
|
-
'person to approve in any browser, and finishes when they do.
|
|
49
|
-
'`skrr
|
|
54
|
+
'person to approve in any browser, and finishes when they do. With no person to approve ' +
|
|
55
|
+
'(CI, a script), mint a session for this machine elsewhere with `skrr token mint --for <id>` ' +
|
|
56
|
+
'and run `skrr login` here with OVERSKY_REFRESH_TOKEN and OVERSKY_CLI_ID set. ' +
|
|
57
|
+
'`skrr create-token` tokens are not accepted by skrr commands.';
|
|
50
58
|
/**
|
|
51
59
|
* The line printed when the daemon descriptor this CLI found is for another
|
|
52
60
|
* server (OSK-12117).
|
|
@@ -164,8 +172,9 @@ async function cliLogin(opts = {}) {
|
|
|
164
172
|
// silently fall through to PKCE / device-code below.
|
|
165
173
|
if (brokerResult.reason === 'ci_token_refused') {
|
|
166
174
|
throw new Error('The skrr background service on this computer is bootstrapped from a CI token, which cannot broker an interactive CLI login. ' +
|
|
167
|
-
'
|
|
168
|
-
'
|
|
175
|
+
'Sign this CLI in on its own with `skrr login --device-code`, or with a session minted elsewhere by ' +
|
|
176
|
+
'`skrr token mint --for <id>` (OVERSKY_REFRESH_TOKEN + OVERSKY_CLI_ID). ' +
|
|
177
|
+
'A `skrr create-token` token is not accepted by skrr commands.');
|
|
169
178
|
}
|
|
170
179
|
// `base_url_mismatch` is a configuration drift the user can resolve
|
|
171
180
|
// explicitly; print a one-line hint so they're not surprised when
|
package/dist/lib/node-adapter.js
CHANGED
|
@@ -12,6 +12,7 @@ const config_1 = require("./config");
|
|
|
12
12
|
const auth_storage_1 = require("./auth-storage");
|
|
13
13
|
const credential_resolver_1 = require("./credential-resolver");
|
|
14
14
|
const daemonBroker_1 = require("./daemonBroker");
|
|
15
|
+
const dpop_auth_1 = require("./dpop-auth");
|
|
15
16
|
const refresh_1 = require("./refresh");
|
|
16
17
|
const delegated_cli_1 = require("./delegated-cli");
|
|
17
18
|
const workspace_header_1 = require("./workspace-header");
|
|
@@ -159,6 +160,9 @@ function createNodeAdapter(opts = {}) {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
const finalUrl = appendQuery(resolveUrl(url), options?.params);
|
|
163
|
+
// OSK-12020 — a DPoP-bound hand-off token needs a fresh proof per request
|
|
164
|
+
// (and per retry: proofs are single-use). No-op for every other token.
|
|
165
|
+
Object.assign(headers, await (0, dpop_auth_1.dpopHeadersFor)(currentCredential().token, method, finalUrl));
|
|
162
166
|
return fetch(finalUrl, { method, headers, body: finalBody });
|
|
163
167
|
}
|
|
164
168
|
/**
|
|
@@ -198,7 +202,7 @@ function createNodeAdapter(opts = {}) {
|
|
|
198
202
|
return false;
|
|
199
203
|
// Never refresh a CI token — they're static by construction.
|
|
200
204
|
const { token, kind } = currentCredential();
|
|
201
|
-
if (!token ||
|
|
205
|
+
if (!token || (0, auth_core_1.isStaticTokenKind)(kind))
|
|
202
206
|
return false;
|
|
203
207
|
if (refreshInFlight)
|
|
204
208
|
return refreshInFlight;
|
|
@@ -237,7 +241,7 @@ function createNodeAdapter(opts = {}) {
|
|
|
237
241
|
if (process.env.OVERSKY_SKIP_DAEMON_BROKER === '1')
|
|
238
242
|
return false;
|
|
239
243
|
const cred = currentCredential();
|
|
240
|
-
if (cred.kind
|
|
244
|
+
if ((0, auth_core_1.isStaticTokenKind)(cred.kind))
|
|
241
245
|
return false;
|
|
242
246
|
// An operator-supplied `--token` is that operator's explicit
|
|
243
247
|
// authority: when the server refuses it, the honest answer is the
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PublisherProfileDraft } from '@skrr-ai/data-provider';
|
|
2
|
+
/**
|
|
3
|
+
* Shared flags for `skrr store publishers profile *`.
|
|
4
|
+
*
|
|
5
|
+
* `--publish-as` names a workspace you publish for; omitted, the console is
|
|
6
|
+
* your personal publisher profile. The server derives the actual principal —
|
|
7
|
+
* this only selects which of your profiles to address.
|
|
8
|
+
*/
|
|
9
|
+
export declare const publishAsFlag: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
export declare const jsonFlag: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
/** A profile draft is a document; read it from a JSON file, never from argv. */
|
|
12
|
+
export declare function readDraftFile(file: string): PublisherProfileDraft;
|
|
13
|
+
/**
|
|
14
|
+
* The CAS revision a state change applies to. Publish, hide and restore act
|
|
15
|
+
* on whatever the draft is NOW, so reading it immediately before the write is
|
|
16
|
+
* the intent; a caller holding a specific revision passes it and the server
|
|
17
|
+
* refuses a stale one.
|
|
18
|
+
*/
|
|
19
|
+
export declare function currentRevision(publishAs?: string, explicit?: number): Promise<number>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsonFlag = exports.publishAsFlag = void 0;
|
|
4
|
+
exports.readDraftFile = readDraftFile;
|
|
5
|
+
exports.currentRevision = currentRevision;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const core_1 = require("@oclif/core");
|
|
8
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
9
|
+
/**
|
|
10
|
+
* Shared flags for `skrr store publishers profile *`.
|
|
11
|
+
*
|
|
12
|
+
* `--publish-as` names a workspace you publish for; omitted, the console is
|
|
13
|
+
* your personal publisher profile. The server derives the actual principal —
|
|
14
|
+
* this only selects which of your profiles to address.
|
|
15
|
+
*/
|
|
16
|
+
exports.publishAsFlag = core_1.Flags.string({
|
|
17
|
+
description: 'Workspace id to act for (omit for your personal publisher profile)',
|
|
18
|
+
});
|
|
19
|
+
exports.jsonFlag = core_1.Flags.boolean({ description: 'Output as JSON' });
|
|
20
|
+
/** A profile draft is a document; read it from a JSON file, never from argv. */
|
|
21
|
+
function readDraftFile(file) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
throw new Error(`Could not read a JSON draft from ${file}: ${err.message}`);
|
|
28
|
+
}
|
|
29
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
30
|
+
throw new Error(`${file} must contain a JSON object (the profile draft)`);
|
|
31
|
+
}
|
|
32
|
+
const doc = parsed;
|
|
33
|
+
// Accept either the bare draft or the `profile show --json` envelope's draft.
|
|
34
|
+
const nested = doc.profile?.draft ?? doc.draft;
|
|
35
|
+
return (nested && typeof nested === 'object' ? nested : doc);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The CAS revision a state change applies to. Publish, hide and restore act
|
|
39
|
+
* on whatever the draft is NOW, so reading it immediately before the write is
|
|
40
|
+
* the intent; a caller holding a specific revision passes it and the server
|
|
41
|
+
* refuses a stale one.
|
|
42
|
+
*/
|
|
43
|
+
async function currentRevision(publishAs, explicit) {
|
|
44
|
+
if (explicit !== undefined)
|
|
45
|
+
return explicit;
|
|
46
|
+
const { profile } = await data_provider_1.dataService.getStorePublisherProfileEditor(publishAs);
|
|
47
|
+
return profile.revision;
|
|
48
|
+
}
|
package/dist/lib/refresh.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface EnsureFreshCliCredentialResult {
|
|
|
47
47
|
pair?: TokenPair;
|
|
48
48
|
/** Stable diagnostic code; never contains token or server response text. */
|
|
49
49
|
refreshFailureCode?: string;
|
|
50
|
-
skippedReason?: 'no_token' | 'ci_token' | 'external_source' | 'env_override' | 'bare_mode' | 'fresh' | 'no_refresh_token' | 'refresh_failed';
|
|
50
|
+
skippedReason?: 'no_token' | 'ci_token' | 'personal_access_token' | 'external_source' | 'env_override' | 'bare_mode' | 'fresh' | 'no_refresh_token' | 'refresh_failed';
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Ensure the active CLI credential has enough remaining lifetime for a
|
package/dist/lib/refresh.js
CHANGED
|
@@ -94,7 +94,7 @@ function canMutatePersistedCredential(credential, bareMode) {
|
|
|
94
94
|
return false;
|
|
95
95
|
if (bareMode)
|
|
96
96
|
return false;
|
|
97
|
-
if (credential.kind
|
|
97
|
+
if ((0, auth_core_1.isStaticTokenKind)(credential.kind))
|
|
98
98
|
return false;
|
|
99
99
|
// These sources are operator-owned or generated on demand. Never rotate
|
|
100
100
|
// them into the local keychain/file store behind the operator's back.
|
|
@@ -120,6 +120,11 @@ async function ensureFreshCliCredential(opts = {}) {
|
|
|
120
120
|
if (credential.kind === 'ci') {
|
|
121
121
|
return { credential, refreshed: false, skippedReason: 'ci_token' };
|
|
122
122
|
}
|
|
123
|
+
// A personal access token is static by construction (OSK-13208): there is
|
|
124
|
+
// no refresh family behind it, so there is nothing to rotate.
|
|
125
|
+
if (credential.kind === 'pat') {
|
|
126
|
+
return { credential, refreshed: false, skippedReason: 'personal_access_token' };
|
|
127
|
+
}
|
|
123
128
|
if (!canMutatePersistedCredential(credential, Boolean(opts.bareMode))) {
|
|
124
129
|
return { credential, refreshed: false, skippedReason: 'external_source' };
|
|
125
130
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A bounded receiver for a retryable SSH byte stream. One frame may be
|
|
2
|
+
* outstanding. A lost acknowledgment may repeat that exact frame, never its
|
|
3
|
+
* bytes; an overlapping, changed, or skipped frame is a stream error. */
|
|
4
|
+
export declare class SshOrderedBytes {
|
|
5
|
+
nextOffset: number;
|
|
6
|
+
private last?;
|
|
7
|
+
receive(offset: number, bytes: Buffer, write: (bytes: Buffer) => Promise<void>): Promise<number>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SshOrderedBytes = void 0;
|
|
4
|
+
/** A bounded receiver for a retryable SSH byte stream. One frame may be
|
|
5
|
+
* outstanding. A lost acknowledgment may repeat that exact frame, never its
|
|
6
|
+
* bytes; an overlapping, changed, or skipped frame is a stream error. */
|
|
7
|
+
class SshOrderedBytes {
|
|
8
|
+
nextOffset = 0;
|
|
9
|
+
last;
|
|
10
|
+
receive(offset, bytes, write) {
|
|
11
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || bytes.length < 1 || bytes.length > 16 * 1024 ||
|
|
12
|
+
!Number.isSafeInteger(offset + bytes.length)) {
|
|
13
|
+
return Promise.reject(new Error('invalid SSH stream frame'));
|
|
14
|
+
}
|
|
15
|
+
if (this.last?.offset === offset) {
|
|
16
|
+
return this.last.bytes.equals(bytes)
|
|
17
|
+
? this.last.written
|
|
18
|
+
: Promise.reject(new Error('changed SSH stream replay'));
|
|
19
|
+
}
|
|
20
|
+
if (offset !== this.nextOffset)
|
|
21
|
+
return Promise.reject(new Error('SSH stream offset mismatch'));
|
|
22
|
+
const retained = Buffer.from(bytes);
|
|
23
|
+
const written = Promise.resolve().then(() => write(retained)).then(() => {
|
|
24
|
+
this.nextOffset = offset + retained.length;
|
|
25
|
+
return this.nextOffset;
|
|
26
|
+
});
|
|
27
|
+
this.last = { offset, bytes: retained, written };
|
|
28
|
+
return written;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.SshOrderedBytes = SshOrderedBytes;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a user-typed USD amount ("5", "2.50") into integer micros.
|
|
3
|
+
*
|
|
4
|
+
* Integer arithmetic on the decimal STRING, never `Number(x) * 1e6`: binary
|
|
5
|
+
* floating point turns 0.29 into 289999.99… and a money path that truncates
|
|
6
|
+
* that charges a micro less — or, rounded the other way, a micro more.
|
|
7
|
+
* Returns null for anything that is not a positive amount with ≤ 2 decimals.
|
|
8
|
+
*/
|
|
9
|
+
export declare function usdToMicros(amount: string): number | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usdToMicros = usdToMicros;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a user-typed USD amount ("5", "2.50") into integer micros.
|
|
6
|
+
*
|
|
7
|
+
* Integer arithmetic on the decimal STRING, never `Number(x) * 1e6`: binary
|
|
8
|
+
* floating point turns 0.29 into 289999.99… and a money path that truncates
|
|
9
|
+
* that charges a micro less — or, rounded the other way, a micro more.
|
|
10
|
+
* Returns null for anything that is not a positive amount with ≤ 2 decimals.
|
|
11
|
+
*/
|
|
12
|
+
function usdToMicros(amount) {
|
|
13
|
+
const trimmed = amount.trim();
|
|
14
|
+
if (!/^\d+(\.\d{1,2})?$/.test(trimmed))
|
|
15
|
+
return null;
|
|
16
|
+
const [whole, frac = ''] = trimmed.split('.');
|
|
17
|
+
const cents = Number(whole) * 100 + Number(frac.padEnd(2, '0'));
|
|
18
|
+
if (!Number.isSafeInteger(cents) || cents <= 0)
|
|
19
|
+
return null;
|
|
20
|
+
return cents * 10_000;
|
|
21
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* - `access_token`: broker mode — a short-lived access token from a
|
|
23
23
|
* daemon-owned family; nothing durable leaves the daemon.
|
|
24
24
|
*/
|
|
25
|
-
export declare const CLI_HANDOFF_MODES: readonly ["refresh_family", "access_token"];
|
|
25
|
+
export declare const CLI_HANDOFF_MODES: readonly ["refresh_family", "access_token", "access_token_dpop"];
|
|
26
26
|
export type CliHandoffMode = (typeof CLI_HANDOFF_MODES)[number];
|
|
27
27
|
/** Broker-mode redeem route on the daemon's loopback server. */
|
|
28
28
|
export declare const CLI_HANDOFF_TOKEN_PATH = "/v1/auth/cli-handoff-token";
|
|
@@ -74,10 +74,26 @@ export declare function normalizeHandoffModes(value: unknown): CliHandoffMode[];
|
|
|
74
74
|
*/
|
|
75
75
|
export declare function advertisedHandoffModes(d: CliHandoffDescriptor): CliHandoffMode[];
|
|
76
76
|
export declare function descriptorOffersAccessToken(d: CliHandoffDescriptor): boolean;
|
|
77
|
+
/** True when the token route can serve DPoP-bound tokens (OSK-12020). */
|
|
78
|
+
export declare function descriptorOffersDpop(d: CliHandoffDescriptor): boolean;
|
|
77
79
|
export interface CliHandoffTokenRequest {
|
|
78
80
|
/** Bypass the daemon broker's cached token (the 401-recovery path). */
|
|
79
81
|
forceRefresh?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The caller can fetch per-request proofs from `CLI_HANDOFF_DPOP_PROOF_PATH`
|
|
84
|
+
* and wants a DPoP-bound token. Sent only to a descriptor that advertises
|
|
85
|
+
* `access_token_dpop`; a daemon that does not know the field ignores it and
|
|
86
|
+
* answers an unbound token, which `tokenType` then says.
|
|
87
|
+
*/
|
|
88
|
+
dpop?: boolean;
|
|
80
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* How the token must be presented. `DPoP` means the token carries `cnf.jkt`
|
|
92
|
+
* and every request needs a fresh proof; absent or `Bearer` means an ordinary
|
|
93
|
+
* bearer token. Read from the RESPONSE, never assumed from the request: an
|
|
94
|
+
* older server ignores the binding request and mints an unbound token.
|
|
95
|
+
*/
|
|
96
|
+
export type CliHandoffTokenType = 'Bearer' | 'DPoP';
|
|
81
97
|
export interface CliHandoffTokenResponse {
|
|
82
98
|
accessToken: string;
|
|
83
99
|
/** Absolute expiry in ms since epoch, when the daemon knows it. */
|
|
@@ -85,6 +101,31 @@ export interface CliHandoffTokenResponse {
|
|
|
85
101
|
/** Server the minted credential belongs to — lets the caller verify it is
|
|
86
102
|
* about to use a token for the API it thinks it is talking to. */
|
|
87
103
|
serverUrl?: string;
|
|
104
|
+
tokenType?: CliHandoffTokenType;
|
|
88
105
|
}
|
|
89
106
|
/** Parse the redeem response; null when the token itself is absent. */
|
|
90
107
|
export declare function parseCliHandoffTokenResponse(value: unknown): CliHandoffTokenResponse | null;
|
|
108
|
+
/** The daemon refuses an UNBOUND token because its posture requires binding. */
|
|
109
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_REQUIRED = "HANDOFF_DPOP_REQUIRED";
|
|
110
|
+
/**
|
|
111
|
+
* The daemon signs RFC 9449 proofs for a bound token with a key that never
|
|
112
|
+
* leaves it. Same descriptor secret as the token route: using a stolen bound
|
|
113
|
+
* token therefore needs live loopback access to THIS daemon, which is the
|
|
114
|
+
* boundary the capability already has.
|
|
115
|
+
*/
|
|
116
|
+
export declare const CLI_HANDOFF_DPOP_PROOF_PATH = "/v1/auth/cli-handoff-dpop-proof";
|
|
117
|
+
/** The oracle refused to sign (bad htm/htu, or `ath` names no token it issued). */
|
|
118
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_PROOF_REFUSED = "HANDOFF_DPOP_PROOF_REFUSED";
|
|
119
|
+
/** The daemon holds no DPoP key (binding is off on this daemon). */
|
|
120
|
+
export declare const CLI_HANDOFF_ERROR_DPOP_UNAVAILABLE = "HANDOFF_DPOP_UNAVAILABLE";
|
|
121
|
+
export interface CliHandoffDpopProofRequest {
|
|
122
|
+
htm: string;
|
|
123
|
+
htu: string;
|
|
124
|
+
/** base64url SHA-256 of the bound access token. The token itself is never
|
|
125
|
+
* sent: the oracle commits to a hash, and only to one it issued. */
|
|
126
|
+
ath: string;
|
|
127
|
+
}
|
|
128
|
+
export interface CliHandoffDpopProofResponse {
|
|
129
|
+
proof: string;
|
|
130
|
+
}
|
|
131
|
+
export declare function parseCliHandoffDpopProofResponse(value: unknown): CliHandoffDpopProofResponse | null;
|
|
@@ -18,19 +18,21 @@
|
|
|
18
18
|
* must produce the full shape.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.CLI_HANDOFF_ERROR_BROKER_ONLY = exports.CLI_HANDOFF_TOKEN_ERROR_UNAVAILABLE = exports.CLI_HANDOFF_TOKEN_PATH = exports.CLI_HANDOFF_MODES = void 0;
|
|
21
|
+
exports.CLI_HANDOFF_ERROR_DPOP_UNAVAILABLE = exports.CLI_HANDOFF_ERROR_DPOP_PROOF_REFUSED = exports.CLI_HANDOFF_DPOP_PROOF_PATH = exports.CLI_HANDOFF_ERROR_DPOP_REQUIRED = exports.CLI_HANDOFF_ERROR_BROKER_ONLY = exports.CLI_HANDOFF_TOKEN_ERROR_UNAVAILABLE = exports.CLI_HANDOFF_TOKEN_PATH = exports.CLI_HANDOFF_MODES = void 0;
|
|
22
22
|
exports.parseCliHandoffDescriptor = parseCliHandoffDescriptor;
|
|
23
23
|
exports.normalizeHandoffModes = normalizeHandoffModes;
|
|
24
24
|
exports.advertisedHandoffModes = advertisedHandoffModes;
|
|
25
25
|
exports.descriptorOffersAccessToken = descriptorOffersAccessToken;
|
|
26
|
+
exports.descriptorOffersDpop = descriptorOffersDpop;
|
|
26
27
|
exports.parseCliHandoffTokenResponse = parseCliHandoffTokenResponse;
|
|
28
|
+
exports.parseCliHandoffDpopProofResponse = parseCliHandoffDpopProofResponse;
|
|
27
29
|
/**
|
|
28
30
|
* What a caller may redeem the descriptor secret for.
|
|
29
31
|
* - `refresh_family`: the legacy durable mint (90-day cli-scope family).
|
|
30
32
|
* - `access_token`: broker mode — a short-lived access token from a
|
|
31
33
|
* daemon-owned family; nothing durable leaves the daemon.
|
|
32
34
|
*/
|
|
33
|
-
exports.CLI_HANDOFF_MODES = ['refresh_family', 'access_token'];
|
|
35
|
+
exports.CLI_HANDOFF_MODES = ['refresh_family', 'access_token', 'access_token_dpop'];
|
|
34
36
|
/** Broker-mode redeem route on the daemon's loopback server. */
|
|
35
37
|
exports.CLI_HANDOFF_TOKEN_PATH = '/v1/auth/cli-handoff-token';
|
|
36
38
|
/** Error codes this contract emits. String literals so both ends compare
|
|
@@ -96,7 +98,12 @@ function advertisedHandoffModes(d) {
|
|
|
96
98
|
return declared.length > 0 ? declared : ['refresh_family'];
|
|
97
99
|
}
|
|
98
100
|
function descriptorOffersAccessToken(d) {
|
|
99
|
-
|
|
101
|
+
const modes = advertisedHandoffModes(d);
|
|
102
|
+
return modes.includes('access_token') || modes.includes('access_token_dpop');
|
|
103
|
+
}
|
|
104
|
+
/** True when the token route can serve DPoP-bound tokens (OSK-12020). */
|
|
105
|
+
function descriptorOffersDpop(d) {
|
|
106
|
+
return advertisedHandoffModes(d).includes('access_token_dpop');
|
|
100
107
|
}
|
|
101
108
|
/** Parse the redeem response; null when the token itself is absent. */
|
|
102
109
|
function parseCliHandoffTokenResponse(value) {
|
|
@@ -109,5 +116,30 @@ function parseCliHandoffTokenResponse(value) {
|
|
|
109
116
|
accessToken: v.accessToken,
|
|
110
117
|
...(typeof v.accessExpiresAt === 'number' ? { accessExpiresAt: v.accessExpiresAt } : {}),
|
|
111
118
|
...(typeof v.serverUrl === 'string' ? { serverUrl: v.serverUrl } : {}),
|
|
119
|
+
...(v.tokenType === 'DPoP' || v.tokenType === 'Bearer' ? { tokenType: v.tokenType } : {}),
|
|
112
120
|
};
|
|
113
121
|
}
|
|
122
|
+
/** The daemon refuses an UNBOUND token because its posture requires binding. */
|
|
123
|
+
exports.CLI_HANDOFF_ERROR_DPOP_REQUIRED = 'HANDOFF_DPOP_REQUIRED';
|
|
124
|
+
// ---------------------------------------------------------------------
|
|
125
|
+
// Proof oracle — POST CLI_HANDOFF_DPOP_PROOF_PATH (OSK-12020)
|
|
126
|
+
// ---------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* The daemon signs RFC 9449 proofs for a bound token with a key that never
|
|
129
|
+
* leaves it. Same descriptor secret as the token route: using a stolen bound
|
|
130
|
+
* token therefore needs live loopback access to THIS daemon, which is the
|
|
131
|
+
* boundary the capability already has.
|
|
132
|
+
*/
|
|
133
|
+
exports.CLI_HANDOFF_DPOP_PROOF_PATH = '/v1/auth/cli-handoff-dpop-proof';
|
|
134
|
+
/** The oracle refused to sign (bad htm/htu, or `ath` names no token it issued). */
|
|
135
|
+
exports.CLI_HANDOFF_ERROR_DPOP_PROOF_REFUSED = 'HANDOFF_DPOP_PROOF_REFUSED';
|
|
136
|
+
/** The daemon holds no DPoP key (binding is off on this daemon). */
|
|
137
|
+
exports.CLI_HANDOFF_ERROR_DPOP_UNAVAILABLE = 'HANDOFF_DPOP_UNAVAILABLE';
|
|
138
|
+
function parseCliHandoffDpopProofResponse(value) {
|
|
139
|
+
if (value == null || typeof value !== 'object')
|
|
140
|
+
return null;
|
|
141
|
+
const v = value;
|
|
142
|
+
if (typeof v.proof !== 'string' || v.proof.split('.').length !== 3)
|
|
143
|
+
return null;
|
|
144
|
+
return { proof: v.proof };
|
|
145
|
+
}
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* Precedence (high → low):
|
|
14
14
|
*
|
|
15
15
|
* 1. flag — `--token <jwt>` CLI flag (debugging)
|
|
16
|
-
* 2. env-token — `OVERSKY_TOKEN`
|
|
17
|
-
*
|
|
16
|
+
* 2. env-token — `OVERSKY_TOKEN` (CI / static). The CLI also reads
|
|
17
|
+
* `SKRR_TOKEN` via `envTokenNames`; `OVERSKY_TOKEN` wins
|
|
18
|
+
* when both are set, matching the CLI config overlay. A
|
|
19
|
+
* personal access token (`osk_pat_*`) is the credential
|
|
20
|
+
* CI should put here.
|
|
18
21
|
* 3. auth-helper — `OVERSKY_AUTH_HELPER` script output (rotating
|
|
19
22
|
* creds — Vault, AWS SM, internal SSO proxy)
|
|
20
23
|
* 4. fd-handoff — `OVERSKY_AUTH_FD` parent→child IPC (Electron
|
|
@@ -64,6 +67,12 @@ export interface CredentialResolverOptions {
|
|
|
64
67
|
* string to use that value directly.
|
|
65
68
|
*/
|
|
66
69
|
envTokenOverride?: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Environment variables read by the env-token slot, in precedence order.
|
|
72
|
+
* Defaults to `['OVERSKY_TOKEN']`. The CLI passes
|
|
73
|
+
* `['OVERSKY_TOKEN', 'SKRR_TOKEN']` (OSK-13208).
|
|
74
|
+
*/
|
|
75
|
+
envTokenNames?: readonly string[];
|
|
67
76
|
/** Test/explicit override for `OVERSKY_AUTH_HELPER` env. Same semantics. */
|
|
68
77
|
authHelperPathOverride?: string | null;
|
|
69
78
|
/**
|
|
@@ -143,6 +152,8 @@ export interface AuthHelperSentinelInfo {
|
|
|
143
152
|
* (POST /api/daemons/token), MUST NOT schedule refresh,
|
|
144
153
|
* and on auth failure exits 75 with a CI-specific error
|
|
145
154
|
* rather than writing needs-reauth.json.
|
|
155
|
+
* - 'pat' — personal access token (`osk_pat_*`, OSK-13208): a static
|
|
156
|
+
* bearer used AS the user by the CLI. Never refreshed.
|
|
146
157
|
* - 'install' — short-lived web-minted JWT with `scope=daemon-install`.
|
|
147
158
|
* The daemon exchanges it through the same /api/daemons/token
|
|
148
159
|
* bootstrap path, persists only the returned daemon
|
|
@@ -157,9 +168,17 @@ export interface AuthHelperSentinelInfo {
|
|
|
157
168
|
* The kill switch `OVERSKY_DISABLE_CI_TOKEN_RESOLVER=1` forces every token to
|
|
158
169
|
* `'standard'` for triage purposes.
|
|
159
170
|
*/
|
|
160
|
-
export type TokenKind = 'standard' | 'ci' | 'install';
|
|
171
|
+
export type TokenKind = 'standard' | 'ci' | 'install' | 'pat';
|
|
161
172
|
/** CI-token plaintext prefix; mirrors api/server/services/Auth/daemonCiTokens.js. */
|
|
162
173
|
export declare const CI_TOKEN_PREFIX = "osk_ci_";
|
|
174
|
+
/** Personal-access-token prefix; mirrors api/server/services/Auth/personalAccessTokens.js. */
|
|
175
|
+
export declare const PERSONAL_ACCESS_TOKEN_PREFIX = "osk_pat_";
|
|
176
|
+
/**
|
|
177
|
+
* True for a kind whose token is static by construction: never refreshed,
|
|
178
|
+
* never rotated into a store. Like `TokenKind` itself this selects a local
|
|
179
|
+
* code path and grants nothing.
|
|
180
|
+
*/
|
|
181
|
+
export declare function isStaticTokenKind(kind: TokenKind): boolean;
|
|
163
182
|
/**
|
|
164
183
|
* Classify a token by local shape. Pure function; safe to call on any string
|
|
165
184
|
* (including `null`-coerced empty). Returns `'standard'` for empty input
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CredentialResolver = exports.AuthHelperUntrustedError = exports.AuthHelperUnavailableError = exports.CI_TOKEN_PREFIX = exports.CREDENTIAL_PRECEDENCE = void 0;
|
|
3
|
+
exports.CredentialResolver = exports.AuthHelperUntrustedError = exports.AuthHelperUnavailableError = exports.PERSONAL_ACCESS_TOKEN_PREFIX = exports.CI_TOKEN_PREFIX = exports.CREDENTIAL_PRECEDENCE = void 0;
|
|
4
|
+
exports.isStaticTokenKind = isStaticTokenKind;
|
|
4
5
|
exports.classifyTokenKind = classifyTokenKind;
|
|
5
6
|
exports.__resetCredentialResolverForTest = __resetCredentialResolverForTest;
|
|
6
7
|
exports.invalidateAuthHelperCache = invalidateAuthHelperCache;
|
|
@@ -19,8 +20,11 @@ exports.invalidateAuthHelperCache = invalidateAuthHelperCache;
|
|
|
19
20
|
* Precedence (high → low):
|
|
20
21
|
*
|
|
21
22
|
* 1. flag — `--token <jwt>` CLI flag (debugging)
|
|
22
|
-
* 2. env-token — `OVERSKY_TOKEN`
|
|
23
|
-
*
|
|
23
|
+
* 2. env-token — `OVERSKY_TOKEN` (CI / static). The CLI also reads
|
|
24
|
+
* `SKRR_TOKEN` via `envTokenNames`; `OVERSKY_TOKEN` wins
|
|
25
|
+
* when both are set, matching the CLI config overlay. A
|
|
26
|
+
* personal access token (`osk_pat_*`) is the credential
|
|
27
|
+
* CI should put here.
|
|
24
28
|
* 3. auth-helper — `OVERSKY_AUTH_HELPER` script output (rotating
|
|
25
29
|
* creds — Vault, AWS SM, internal SSO proxy)
|
|
26
30
|
* 4. fd-handoff — `OVERSKY_AUTH_FD` parent→child IPC (Electron
|
|
@@ -68,6 +72,16 @@ exports.CREDENTIAL_PRECEDENCE = [
|
|
|
68
72
|
];
|
|
69
73
|
/** CI-token plaintext prefix; mirrors api/server/services/Auth/daemonCiTokens.js. */
|
|
70
74
|
exports.CI_TOKEN_PREFIX = 'osk_ci_';
|
|
75
|
+
/** Personal-access-token prefix; mirrors api/server/services/Auth/personalAccessTokens.js. */
|
|
76
|
+
exports.PERSONAL_ACCESS_TOKEN_PREFIX = 'osk_pat_';
|
|
77
|
+
/**
|
|
78
|
+
* True for a kind whose token is static by construction: never refreshed,
|
|
79
|
+
* never rotated into a store. Like `TokenKind` itself this selects a local
|
|
80
|
+
* code path and grants nothing.
|
|
81
|
+
*/
|
|
82
|
+
function isStaticTokenKind(kind) {
|
|
83
|
+
return kind === 'ci' || kind === 'pat';
|
|
84
|
+
}
|
|
71
85
|
/**
|
|
72
86
|
* Classify a token by local shape. Pure function; safe to call on any string
|
|
73
87
|
* (including `null`-coerced empty). Returns `'standard'` for empty input
|
|
@@ -103,6 +117,8 @@ function classifyTokenKind(token) {
|
|
|
103
117
|
return 'standard';
|
|
104
118
|
if (token.startsWith(exports.CI_TOKEN_PREFIX))
|
|
105
119
|
return 'ci';
|
|
120
|
+
if (token.startsWith(exports.PERSONAL_ACCESS_TOKEN_PREFIX))
|
|
121
|
+
return 'pat';
|
|
106
122
|
const payload = (0, jwtUtils_js_1.decodeJwtPayload)(token);
|
|
107
123
|
if (payload &&
|
|
108
124
|
typeof payload === 'object' &&
|
|
@@ -260,8 +276,16 @@ class CredentialResolver {
|
|
|
260
276
|
if (typeof this.opts.envTokenOverride === 'string') {
|
|
261
277
|
return this.opts.envTokenOverride.length > 0 ? this.opts.envTokenOverride : null;
|
|
262
278
|
}
|
|
263
|
-
|
|
264
|
-
|
|
279
|
+
// `OVERSKY_TOKEN` first (retained by decision, docs/product/skrr-env-inventory.md),
|
|
280
|
+
// then any renamed spelling the caller opted into (`SKRR_TOKEN` for the CLI,
|
|
281
|
+
// OSK-13208). The daemon does not opt in: a CI job that sets `SKRR_TOKEN`
|
|
282
|
+
// for `skrr` must not have a co-located `skrrd` silently adopt it.
|
|
283
|
+
for (const name of this.opts.envTokenNames ?? ['OVERSKY_TOKEN']) {
|
|
284
|
+
const v = process.env[name];
|
|
285
|
+
if (typeof v === 'string' && v.length > 0)
|
|
286
|
+
return v;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
265
289
|
}
|
|
266
290
|
async tryResolveAuthHelper() {
|
|
267
291
|
let helperPath;
|