@skrr-ai/auth-core 0.1.2

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.
Files changed (147) hide show
  1. package/README.md +21 -0
  2. package/dist/cjs/aead.d.ts +24 -0
  3. package/dist/cjs/aead.js +65 -0
  4. package/dist/cjs/authExpiredDetector.d.ts +65 -0
  5. package/dist/cjs/authExpiredDetector.js +89 -0
  6. package/dist/cjs/authHelper.d.ts +51 -0
  7. package/dist/cjs/authHelper.js +321 -0
  8. package/dist/cjs/credentialEnvelope.d.ts +85 -0
  9. package/dist/cjs/credentialEnvelope.js +230 -0
  10. package/dist/cjs/credentialEnvelopeBridge.d.ts +116 -0
  11. package/dist/cjs/credentialEnvelopeBridge.js +637 -0
  12. package/dist/cjs/credentialResolver.d.ts +278 -0
  13. package/dist/cjs/credentialResolver.js +423 -0
  14. package/dist/cjs/daemonPair.d.ts +172 -0
  15. package/dist/cjs/daemonPair.js +495 -0
  16. package/dist/cjs/daemonScopes.d.ts +74 -0
  17. package/dist/cjs/daemonScopes.js +84 -0
  18. package/dist/cjs/deviceIdentityBridge.d.ts +113 -0
  19. package/dist/cjs/deviceIdentityBridge.js +591 -0
  20. package/dist/cjs/deviceKey.d.ts +167 -0
  21. package/dist/cjs/deviceKey.js +332 -0
  22. package/dist/cjs/fdToken.d.ts +17 -0
  23. package/dist/cjs/fdToken.js +238 -0
  24. package/dist/cjs/harnessTrust.d.ts +72 -0
  25. package/dist/cjs/harnessTrust.js +103 -0
  26. package/dist/cjs/headers-helper.d.ts +25 -0
  27. package/dist/cjs/headers-helper.js +323 -0
  28. package/dist/cjs/index.d.ts +37 -0
  29. package/dist/cjs/index.js +305 -0
  30. package/dist/cjs/jwtUtils.d.ts +35 -0
  31. package/dist/cjs/jwtUtils.js +61 -0
  32. package/dist/cjs/kek/index.d.ts +89 -0
  33. package/dist/cjs/kek/index.js +208 -0
  34. package/dist/cjs/kek/linux.d.ts +92 -0
  35. package/dist/cjs/kek/linux.js +596 -0
  36. package/dist/cjs/kek/macos.d.ts +112 -0
  37. package/dist/cjs/kek/macos.js +599 -0
  38. package/dist/cjs/kek/types.d.ts +111 -0
  39. package/dist/cjs/kek/types.js +43 -0
  40. package/dist/cjs/kek/windows.d.ts +71 -0
  41. package/dist/cjs/kek/windows.js +350 -0
  42. package/dist/cjs/kek/zeroize-registry.d.ts +44 -0
  43. package/dist/cjs/kek/zeroize-registry.js +63 -0
  44. package/dist/cjs/legacyStatePreflight.d.ts +67 -0
  45. package/dist/cjs/legacyStatePreflight.js +78 -0
  46. package/dist/cjs/localIdentity.d.ts +54 -0
  47. package/dist/cjs/localIdentity.js +57 -0
  48. package/dist/cjs/loginLocalhost.d.ts +115 -0
  49. package/dist/cjs/loginLocalhost.js +368 -0
  50. package/dist/cjs/loginWithLocalhost.d.ts +131 -0
  51. package/dist/cjs/loginWithLocalhost.js +359 -0
  52. package/dist/cjs/machineId.d.ts +35 -0
  53. package/dist/cjs/machineId.js +239 -0
  54. package/dist/cjs/messages.d.ts +9 -0
  55. package/dist/cjs/messages.js +44 -0
  56. package/dist/cjs/package.json +3 -0
  57. package/dist/cjs/pkce.d.ts +62 -0
  58. package/dist/cjs/pkce.js +158 -0
  59. package/dist/cjs/recoveryCode.d.ts +61 -0
  60. package/dist/cjs/recoveryCode.js +213 -0
  61. package/dist/cjs/refresh.d.ts +215 -0
  62. package/dist/cjs/refresh.js +877 -0
  63. package/dist/cjs/refreshClassification.d.ts +53 -0
  64. package/dist/cjs/refreshClassification.js +105 -0
  65. package/dist/cjs/refreshScheduler.d.ts +53 -0
  66. package/dist/cjs/refreshScheduler.js +332 -0
  67. package/dist/cjs/runtime.d.ts +149 -0
  68. package/dist/cjs/runtime.js +136 -0
  69. package/dist/cjs/spawnEnv.d.ts +76 -0
  70. package/dist/cjs/spawnEnv.js +153 -0
  71. package/dist/cjs/ttlParser.d.ts +54 -0
  72. package/dist/cjs/ttlParser.js +87 -0
  73. package/dist/cjs/types.d.ts +91 -0
  74. package/dist/cjs/types.js +26 -0
  75. package/dist/esm/aead.d.ts +24 -0
  76. package/dist/esm/aead.js +57 -0
  77. package/dist/esm/authExpiredDetector.d.ts +65 -0
  78. package/dist/esm/authExpiredDetector.js +85 -0
  79. package/dist/esm/authHelper.d.ts +51 -0
  80. package/dist/esm/authHelper.js +311 -0
  81. package/dist/esm/credentialEnvelope.d.ts +85 -0
  82. package/dist/esm/credentialEnvelope.js +213 -0
  83. package/dist/esm/credentialEnvelopeBridge.d.ts +116 -0
  84. package/dist/esm/credentialEnvelopeBridge.js +620 -0
  85. package/dist/esm/credentialResolver.d.ts +278 -0
  86. package/dist/esm/credentialResolver.js +414 -0
  87. package/dist/esm/daemonPair.d.ts +172 -0
  88. package/dist/esm/daemonPair.js +476 -0
  89. package/dist/esm/daemonScopes.d.ts +74 -0
  90. package/dist/esm/daemonScopes.js +80 -0
  91. package/dist/esm/deviceIdentityBridge.d.ts +113 -0
  92. package/dist/esm/deviceIdentityBridge.js +572 -0
  93. package/dist/esm/deviceKey.d.ts +167 -0
  94. package/dist/esm/deviceKey.js +320 -0
  95. package/dist/esm/fdToken.d.ts +17 -0
  96. package/dist/esm/fdToken.js +231 -0
  97. package/dist/esm/harnessTrust.d.ts +72 -0
  98. package/dist/esm/harnessTrust.js +97 -0
  99. package/dist/esm/headers-helper.d.ts +25 -0
  100. package/dist/esm/headers-helper.js +316 -0
  101. package/dist/esm/index.d.ts +37 -0
  102. package/dist/esm/index.js +144 -0
  103. package/dist/esm/jwtUtils.d.ts +35 -0
  104. package/dist/esm/jwtUtils.js +57 -0
  105. package/dist/esm/kek/index.d.ts +89 -0
  106. package/dist/esm/kek/index.js +162 -0
  107. package/dist/esm/kek/linux.d.ts +92 -0
  108. package/dist/esm/kek/linux.js +585 -0
  109. package/dist/esm/kek/macos.d.ts +112 -0
  110. package/dist/esm/kek/macos.js +586 -0
  111. package/dist/esm/kek/types.d.ts +111 -0
  112. package/dist/esm/kek/types.js +39 -0
  113. package/dist/esm/kek/windows.d.ts +71 -0
  114. package/dist/esm/kek/windows.js +344 -0
  115. package/dist/esm/kek/zeroize-registry.d.ts +44 -0
  116. package/dist/esm/kek/zeroize-registry.js +58 -0
  117. package/dist/esm/legacyStatePreflight.d.ts +67 -0
  118. package/dist/esm/legacyStatePreflight.js +71 -0
  119. package/dist/esm/localIdentity.d.ts +54 -0
  120. package/dist/esm/localIdentity.js +54 -0
  121. package/dist/esm/loginLocalhost.d.ts +115 -0
  122. package/dist/esm/loginLocalhost.js +355 -0
  123. package/dist/esm/loginWithLocalhost.d.ts +131 -0
  124. package/dist/esm/loginWithLocalhost.js +353 -0
  125. package/dist/esm/machineId.d.ts +35 -0
  126. package/dist/esm/machineId.js +231 -0
  127. package/dist/esm/messages.d.ts +9 -0
  128. package/dist/esm/messages.js +40 -0
  129. package/dist/esm/pkce.d.ts +62 -0
  130. package/dist/esm/pkce.js +148 -0
  131. package/dist/esm/recoveryCode.d.ts +61 -0
  132. package/dist/esm/recoveryCode.js +207 -0
  133. package/dist/esm/refresh.d.ts +215 -0
  134. package/dist/esm/refresh.js +863 -0
  135. package/dist/esm/refreshClassification.d.ts +53 -0
  136. package/dist/esm/refreshClassification.js +106 -0
  137. package/dist/esm/refreshScheduler.d.ts +53 -0
  138. package/dist/esm/refreshScheduler.js +329 -0
  139. package/dist/esm/runtime.d.ts +149 -0
  140. package/dist/esm/runtime.js +121 -0
  141. package/dist/esm/spawnEnv.d.ts +76 -0
  142. package/dist/esm/spawnEnv.js +149 -0
  143. package/dist/esm/ttlParser.d.ts +54 -0
  144. package/dist/esm/ttlParser.js +83 -0
  145. package/dist/esm/types.d.ts +91 -0
  146. package/dist/esm/types.js +21 -0
  147. package/package.json +82 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * credentialResolver.ts — documented credential precedence for the
3
+ * OverSky daemon and CLI.
4
+ *
5
+ * Roadmap: docs/daemon-auth-roadmap.md, Phase H.
6
+ *
7
+ * The resolver walks a fixed, ordered list of credential sources and
8
+ * returns the first non-null token along with its source name. Source
9
+ * names are part of the public surface — they show up in
10
+ * `oversky doctor`, `oversky status`, and `sky whoami`, and operators
11
+ * use them to triage "where did this token come from?" tickets.
12
+ *
13
+ * Precedence (high → low):
14
+ *
15
+ * 1. flag — `--token <jwt>` CLI flag (debugging)
16
+ * 2. env-token — `OVERSKY_TOKEN` env (CI / static; Phase I adds
17
+ * server-side scope=ci validation)
18
+ * 3. auth-helper — `OVERSKY_AUTH_HELPER` script output (rotating
19
+ * creds — Vault, AWS SM, internal SSO proxy)
20
+ * 4. fd-handoff — `OVERSKY_AUTH_FD` parent→child IPC (Electron
21
+ * supervisor → daemon child). Internal; never
22
+ * user-configured.
23
+ * 5. keychain — macOS Keychain (`ai.skrr.daemon`)
24
+ * 6. file — `~/.skrr/auth.json` (0600)
25
+ * 7. interactive — Browser/device-code login (TTY only, blocked
26
+ * under launchd via `isHeadless()`)
27
+ *
28
+ * Skip rules (Phase H.2 + H.4):
29
+ * - `bareMode: true` skips slots 4–7 (no platform storage, no IPC,
30
+ * no interactive). Bare mode demands an explicit token from env or
31
+ * flag — designed for reproducible CI runs and "is the daemon
32
+ * broken or is my config corrupted?" triage.
33
+ * - `storeMode: 'env-only'` skips slots 5 + 6 (and the daemon
34
+ * wrapper additionally disables the auth-generation.json sidecar).
35
+ * - `storeMode: 'keychain'` skips slot 6 (file backend).
36
+ * - `storeMode: 'file'` skips slot 5 (keychain backend).
37
+ *
38
+ * Backend access uses dependency-injected callbacks
39
+ * (`readKeychainToken`, `readFileToken`, `runInteractiveLogin`) so this
40
+ * module stays free of OS-specific imports and the Sky CLI can wire its
41
+ * own backends without dragging the daemon's `keychain.ts` into
42
+ * non-daemon binaries.
43
+ */
44
+ import { type AuthHelperFailureReason } from './authHelper.js';
45
+ export type CredentialSource = 'flag' | 'env-token' | 'auth-helper' | 'fd-handoff' | 'keychain' | 'file' | 'interactive' | 'none';
46
+ /**
47
+ * Ordered slots tried by `CredentialResolver.resolve()`. Excludes
48
+ * `'none'` (only emitted when no slot produced a token). Treat this as
49
+ * the canonical published precedence — flipping the order is a public
50
+ * API change and breaks operator runbooks.
51
+ */
52
+ export declare const CREDENTIAL_PRECEDENCE: readonly ["flag", "env-token", "auth-helper", "fd-handoff", "keychain", "file", "interactive"];
53
+ export type CredentialStoreMode = 'auto' | 'keychain' | 'file' | 'env-only';
54
+ export interface CredentialResolverOptions {
55
+ /** `--token <jwt>` flag value, when present on the CLI. */
56
+ flagToken?: string;
57
+ /** `--bare` mode (or `OVERSKY_BARE_MODE=1`): skips slots 4–7. */
58
+ bareMode?: boolean;
59
+ /** `config.credentials.store` value; defaults to 'auto'. */
60
+ storeMode?: CredentialStoreMode;
61
+ /**
62
+ * Test/explicit override for `OVERSKY_TOKEN`. Pass `undefined` to read
63
+ * from `process.env`; pass `null` to force-skip the slot; pass a
64
+ * string to use that value directly.
65
+ */
66
+ envTokenOverride?: string | null;
67
+ /** Test/explicit override for `OVERSKY_AUTH_HELPER` env. Same semantics. */
68
+ authHelperPathOverride?: string | null;
69
+ /**
70
+ * Predicate consulted before invoking the interactive slot. The daemon
71
+ * wires `isHeadlessDaemon()`; the CLI may wire its own check.
72
+ */
73
+ isHeadless?: () => boolean;
74
+ /**
75
+ * Read the keychain backend. Returns the token string or null. The
76
+ * daemon wraps `keychain.readToken()` (and handles `resetAvailability`
77
+ * on misses) inside this callback.
78
+ */
79
+ readKeychainToken?: () => string | null;
80
+ /** Read the file backend (`~/.skrr/auth.json`). */
81
+ readFileToken?: () => string | null;
82
+ /** Trigger interactive login; returns the new token or null. */
83
+ runInteractiveLogin?: () => Promise<string | null>;
84
+ /**
85
+ * L6.1 — workspace-trust gate for `OVERSKY_AUTH_HELPER`.
86
+ *
87
+ * Called BEFORE the helper is invoked. The daemon wires this to the
88
+ * `daemon/src/permissions/workspace-trust.ts` checker; tests can pass a
89
+ * stub directly. When this returns `{trusted: false, ...}` for a
90
+ * project- or local-scope helper, the resolver:
91
+ * 1. Logs an `auth_helper_blocked_untrusted` event via
92
+ * `onAuthHelperBlocked` if wired.
93
+ * 2. Fails CLOSED — returns a `{token: null, source: 'auth-helper'}`
94
+ * record with `failClosed: true` so the chain does NOT fall
95
+ * through to OAuth. (See L6.1 spec point 4.)
96
+ *
97
+ * Absent → no trust check is performed (backward-compat for the CLI
98
+ * and tests that don't care about workspace-trust). Production daemon
99
+ * MUST wire this.
100
+ */
101
+ helperTrustGate?: (helperPath: string, fromEnvVar: boolean) => HelperTrustDecision;
102
+ /**
103
+ * Audit hook fired when `helperTrustGate` blocks the helper. Daemon
104
+ * wires this to the event-log so security-sensitive denials are
105
+ * persisted alongside other auth events.
106
+ */
107
+ onAuthHelperBlocked?: (info: AuthHelperBlockedInfo) => void;
108
+ /**
109
+ * Audit hook fired when L6.3 returns the cold-cache failure sentinel.
110
+ * Useful for surfacing "your helper is broken" without spamming the
111
+ * event-log on every read within the TTL.
112
+ */
113
+ onAuthHelperSentinelHit?: (info: AuthHelperSentinelInfo) => void;
114
+ }
115
+ /**
116
+ * L6.1 — return value of the workspace-trust callback.
117
+ */
118
+ export interface HelperTrustDecision {
119
+ trusted: boolean;
120
+ origin: 'user' | 'project' | 'local';
121
+ reason?: 'no-record' | 'helper-changed';
122
+ }
123
+ export interface AuthHelperBlockedInfo {
124
+ helperPath: string;
125
+ origin: 'user' | 'project' | 'local';
126
+ reason: 'no-record' | 'helper-changed';
127
+ cwd: string;
128
+ }
129
+ export interface AuthHelperSentinelInfo {
130
+ helperPath: string;
131
+ reason: AuthHelperFailureReason;
132
+ cachedAt: number;
133
+ ttlMs: number;
134
+ }
135
+ /**
136
+ * Token kind discriminator (Phase I).
137
+ *
138
+ * - 'standard' — anything that should be treated as a JWT (the default
139
+ * daemon device-code-issued access token, refresh-derived
140
+ * rotations, the user's CLI session token, etc.).
141
+ * - 'ci' — long-lived CI token with the `osk_ci_` prefix. The
142
+ * daemon uses this for the bootstrap-to-daemon-JWT flow
143
+ * (POST /api/daemons/token), MUST NOT schedule refresh,
144
+ * and on auth failure exits 75 with a CI-specific error
145
+ * rather than writing needs-reauth.json.
146
+ * - 'install' — short-lived web-minted JWT with `scope=daemon-install`.
147
+ * The daemon exchanges it through the same /api/daemons/token
148
+ * bootstrap path, persists only the returned daemon
149
+ * credential, and never treats the install JWT as a normal
150
+ * session token.
151
+ *
152
+ * Detection is local-only; no server round-trip. The JWT payload read is
153
+ * intentionally signature-less and is only used to select the client
154
+ * bootstrap path. The server remains the authority when the token is
155
+ * exchanged at /api/daemons/token.
156
+ *
157
+ * The kill switch `OVERSKY_DISABLE_CI_TOKEN_RESOLVER=1` forces every token to
158
+ * `'standard'` for triage purposes.
159
+ */
160
+ export type TokenKind = 'standard' | 'ci' | 'install';
161
+ /** CI-token plaintext prefix; mirrors api/server/services/Auth/daemonCiTokens.js. */
162
+ export declare const CI_TOKEN_PREFIX = "osk_ci_";
163
+ /**
164
+ * Classify a token by local shape. Pure function; safe to call on any string
165
+ * (including `null`-coerced empty). Returns `'standard'` for empty input
166
+ * so callers don't need to null-check before classification.
167
+ *
168
+ * Honors `OVERSKY_DISABLE_CI_TOKEN_RESOLVER=1` kill switch — when set,
169
+ * everything classifies as `'standard'` so the resolver can be used as
170
+ * a triage tool ("is the CI-token path the problem?").
171
+ */
172
+ /**
173
+ * SECURITY NOTE — this reads `scope` out of an UNVERIFIED JWT payload.
174
+ *
175
+ * That is safe here, and stays safe only under one condition: the return value
176
+ * selects a local code path and nothing else. `'install'` means "refresh this
177
+ * the install-token way"; it grants no capability, skips no check, and asserts
178
+ * no identity. The server still validates the token on every request, so a
179
+ * forged `scope: daemon-install` buys an attacker a different local branch and
180
+ * the same 401.
181
+ *
182
+ * The forgery is trivial — no signature is checked, so anyone who can write the
183
+ * credential file or set the env var can put any claim in it. That is fine
184
+ * while the only consequence is routing. It stops being fine the moment a
185
+ * caller treats `TokenKind` as a permission. Do not let this value gate a
186
+ * capability, and do not add a branch here that returns a kind meaning "more
187
+ * privileged".
188
+ *
189
+ * See `jwtUtils.ts` for the general rule.
190
+ */
191
+ export declare function classifyTokenKind(token: string | null | undefined): TokenKind;
192
+ export interface ResolvedCredential {
193
+ token: string | null;
194
+ source: CredentialSource;
195
+ /**
196
+ * Phase I — discriminator the daemon uses to branch on refresh
197
+ * scheduling, needs-reauth handling, and the bootstrap-to-daemon-JWT
198
+ * flow. Always `'standard'` when `token` is null.
199
+ */
200
+ kind: TokenKind;
201
+ }
202
+ /** @internal test-only — drop the helper cache between tests. */
203
+ export declare function __resetCredentialResolverForTest(): void;
204
+ /**
205
+ * L6.3 — typed error thrown when the cold-cache failure sentinel is in
206
+ * effect. Callers (daemon refresh path, CLI) can catch this specifically
207
+ * to render "auth helper unavailable: <reason>" without falling through
208
+ * to OAuth or device-code retry.
209
+ */
210
+ export declare class AuthHelperUnavailableError extends Error {
211
+ readonly reason: AuthHelperFailureReason;
212
+ readonly cachedAt: number;
213
+ constructor(reason: AuthHelperFailureReason, cachedAt: number);
214
+ }
215
+ /**
216
+ * L6.1 — typed error thrown when the workspace-trust gate blocks a
217
+ * project- or local-scope helper. Surfaces the actionable next step
218
+ * (`oversky trust accept`) to the user; the daemon catches and presents
219
+ * this without falling through to OAuth.
220
+ */
221
+ export declare class AuthHelperUntrustedError extends Error {
222
+ readonly origin: 'user' | 'project' | 'local';
223
+ readonly reason: 'no-record' | 'helper-changed';
224
+ readonly helperPath: string;
225
+ constructor(helperPath: string, origin: 'user' | 'project' | 'local', reason: 'no-record' | 'helper-changed');
226
+ }
227
+ /**
228
+ * H3-6 — public invalidation seam for the `OVERSKY_AUTH_HELPER` result
229
+ * cache. 401 handlers call this right after receiving a 401 from the
230
+ * server so the NEXT credential resolve re-invokes the helper to mint
231
+ * a fresh token instead of serving the stale (cached) one for up to
232
+ * `OVERSKY_AUTH_HELPER_TTL_MS`.
233
+ *
234
+ * Without this seam, a rotated secret at the helper's source (Vault,
235
+ * AWS Secrets Manager, etc.) could take up to 5 minutes to propagate
236
+ * to OverSky even when the server is actively rejecting the old
237
+ * value. The helper is the only slot in the precedence chain that
238
+ * naturally caches; keychain/file reads are already one-shot per
239
+ * resolve.
240
+ *
241
+ * L6.3 — also clears the cold-cache failure sentinel. A 401 means the
242
+ * server is rejecting whatever we sent, which usually means the helper
243
+ * needs to retry from scratch — even when the LAST helper attempt
244
+ * failed and seeded the sentinel. Without this, a transient helper
245
+ * failure during the same TTL window as a server-issued 401 would lock
246
+ * the daemon out for the full TTL.
247
+ *
248
+ * Idempotent: calling this on an already-empty cache is a no-op.
249
+ */
250
+ export declare function invalidateAuthHelperCache(): void;
251
+ export declare class CredentialResolver {
252
+ private readonly opts;
253
+ constructor(opts?: CredentialResolverOptions);
254
+ /**
255
+ * Walk the precedence chain and return the first non-null token.
256
+ * Returns `{ token: null, source: 'none' }` when nothing matches.
257
+ *
258
+ * Fail-closed exceptions from the auth-helper slot
259
+ * (`AuthHelperUntrustedError`, `AuthHelperUnavailableError`) propagate
260
+ * out — they intentionally PREVENT fall-through to OAuth. Callers
261
+ * that want soft-fall-through behavior must catch explicitly. See
262
+ * L6.1 spec point 4 and L6.3.
263
+ */
264
+ resolve(): Promise<ResolvedCredential>;
265
+ /**
266
+ * Test seam: invoke a single slot in isolation. Production code should
267
+ * always go through `resolve()` so the chain ordering is enforced.
268
+ */
269
+ tryResolveSlot(source: Exclude<CredentialSource, 'none'>): Promise<string | null>;
270
+ private shouldSkip;
271
+ private tryResolveFlag;
272
+ private tryResolveEnvToken;
273
+ private tryResolveAuthHelper;
274
+ private tryResolveFdHandoff;
275
+ private tryResolveKeychain;
276
+ private tryResolveFile;
277
+ private tryResolveInteractive;
278
+ }