@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,215 @@
1
+ import { type ReauthState, type TokenPair } from './types.js';
2
+ declare function authLockPath(): string;
3
+ declare function reauthFlagPath(): string;
4
+ /**
5
+ * P1-6 — Register best-effort lock-release handlers on process exit
6
+ * signals so a crash or SIGTERM during a refresh leaves the lock file
7
+ * in a released state. Without this, a sibling process must wait up to
8
+ * `LOCK_STALE_MS` (5 s) for `proper-lockfile` to declare the lock stale.
9
+ *
10
+ * Guards:
11
+ * - `_exitHandlersInstalled` (module-level): safe to call multiple
12
+ * times (e.g., from unit-test re-imports) — handlers are registered
13
+ * exactly once per process.
14
+ * - Every handler swallows errors: `lockfile.unlockSync` throws when
15
+ * the lock file doesn't exist or was never acquired; we must not
16
+ * crash the process during shutdown.
17
+ *
18
+ * `lockfile.unlockSync` is available in `proper-lockfile` ≥ 4.1.0 and
19
+ * removes the `.lock` companion directory that `proper-lockfile` creates
20
+ * next to `auth.lock`. It is synchronous so it runs safely in POSIX
21
+ * signal handlers and `process.on('exit')`.
22
+ */
23
+ export declare function installLockExitHandlers(): void;
24
+ /**
25
+ * Acquire the cross-process auth lock, run the body, release on `finally`.
26
+ * Throws if the lock can't be acquired within
27
+ * `LOCK_MAX_RETRIES × LOCK_RETRY_MS`.
28
+ *
29
+ * We lock a sentinel file (auth.lock) that we create/touch if missing —
30
+ * locking the real auth.json would race with atomic-rename writes.
31
+ *
32
+ * @param daemonId When supplied, lock-lifecycle telemetry events are emitted:
33
+ * refresh_lock_acquiring, refresh_lock_acquired, refresh_lock_released.
34
+ */
35
+ export declare function withAuthLock<T>(body: () => Promise<T>, daemonId?: string): Promise<T>;
36
+ /**
37
+ * Consecutive unknown-shape-401 budget per daemonId.
38
+ *
39
+ * A 401 from `/token/refresh` whose body `code` is NOT one of the four
40
+ * credential-death codes (REFRESH_INVALID / REFRESH_REUSED /
41
+ * REFRESH_EXPIRED / SESSION_REVOKED) — an unknown code, a missing code,
42
+ * or an unparseable body — is most likely an artifact of infra OverSky
43
+ * does not fully control: a proxy/middleware regression, a clock-skew
44
+ * replay rejection (DEVICE_PROOF_REPLAYED), or a half-rolled-out server
45
+ * route. By construction it is NOT a daemon's actual refresh-family burn
46
+ * (those arrive as one of the four known codes).
47
+ *
48
+ * Pre-fix every unknown-shape 401 latched `PermanentAuthFailure('UNKNOWN_401')`
49
+ * + `persistReauthState` on the FIRST occurrence — forcing a manual
50
+ * reconnect even when the next refresh would have succeeded. Now the
51
+ * first `UNKNOWN_401_BUDGET` unknown-401s accumulated on a given daemonId
52
+ * (within `UNKNOWN_401_WINDOW_MS`, see the reset/window rules below) throw
53
+ * `TransientAuthFailure` instead, so the daemon's
54
+ * `performRefresh → return false` path lets the refreshScheduler /
55
+ * reactive retry+backoff ride out a transient regression WITHOUT latching
56
+ * needs-reauth. Only after the budget is exhausted do we fall back to the
57
+ * original permanent latch — preserving the iron rule that the interactive
58
+ * re-auth modal remains the final fallback.
59
+ *
60
+ * The four KNOWN codes are genuinely permanent and are NEVER routed
61
+ * through this budget — they latch immediately as before.
62
+ *
63
+ * Counter semantics:
64
+ * - Incremented on each consecutive unknown-shape 401 for a daemonId.
65
+ * - Reset to zero ONLY on a DEFINITIVE outcome that tells us about the
66
+ * credential's true state: a 200 success (credential alive) OR a
67
+ * KNOWN-permanent 401 (credential dead, recognized). It is DELIBERATELY
68
+ * NOT reset on a transient non-401 outcome (network error, 429, 5xx).
69
+ *
70
+ * RESET SEMANTICS (Finding 4 follow-up — deliberate). Originally the
71
+ * streak reset on ANY non-unknown-401 outcome, including transient ones.
72
+ * That created a retry-forever hole: a dead-but-unknown-code credential
73
+ * whose endpoint INTERMITTENTLY returns transient errors (a flaky proxy
74
+ * that alternates 503 ↔ unknown-401, say) could never accumulate
75
+ * `UNKNOWN_401_BUDGET` consecutive unknown-401s — every transient blip
76
+ * wiped the streak — so it would never latch and the daemon would retry
77
+ * forever, never surfacing the re-auth affordance. By treating only
78
+ * success / known-permanent-401 as streak-resetting, a flaky-but-dead
79
+ * unknown credential still converges to the permanent latch: the
80
+ * unknown-401s accumulate ACROSS interleaved transient noise (subject to
81
+ * the window below) and eventually exhaust the budget.
82
+ * - Bounded window: if the previous unknown-401 for this daemonId is
83
+ * older than `UNKNOWN_401_WINDOW_MS`, the run is considered stale and
84
+ * the counter restarts at 1. This bounds how far apart interleaved
85
+ * unknown-401s can be and still count toward the same latch, so two
86
+ * genuinely unrelated incidents hours apart don't sum into a premature
87
+ * latch — while a flaky-but-dead credential, which keeps producing
88
+ * unknown-401s well within the window, still converges.
89
+ */
90
+ /**
91
+ * BOOT-BUDGET COUPLING (Finding 4 follow-up). Exported so the daemon's
92
+ * startup-recovery loop can statically assert
93
+ * `STARTUP_RECOVERY_MAX_ATTEMPTS >= UNKNOWN_401_BUDGET`.
94
+ *
95
+ * Why the coupling is load-bearing: the boot path
96
+ * (`recoverAccessTokenAtStartup` in `daemon/src/auth.ts`) retries
97
+ * `transient_error` results up to `STARTUP_RECOVERY_MAX_ATTEMPTS` times,
98
+ * then exits `EX_TEMPFAIL` so the supervisor restarts the process. Each
99
+ * in-budget unknown-shape 401 is classified `transient_error`. If the
100
+ * boot loop's max attempts were LESS than this budget, a
101
+ * genuinely-dead-but-unknown-code credential would make the boot loop
102
+ * give up (EX_TEMPFAIL → supervisor restart) BEFORE the in-process
103
+ * budget ever exhausts and latches `PermanentAuthFailure('UNKNOWN_401')`
104
+ * + persists needs-reauth. The supervisor would then restart-loop
105
+ * forever and the user would NEVER see the re-auth affordance — a
106
+ * strand. Keeping `STARTUP_RECOVERY_MAX_ATTEMPTS >= UNKNOWN_401_BUDGET`
107
+ * guarantees the in-process budget exhausts and latches within a single
108
+ * boot, on the final boot attempt, before control returns to the
109
+ * supervisor. The daemon enforces this with a module-load static
110
+ * assertion (`assertStartupRecoveryCoversUnknown401Budget`) plus a unit
111
+ * test; see `daemon/src/auth.ts`.
112
+ */
113
+ export declare const UNKNOWN_401_BUDGET = 3;
114
+ /** Test-only: clear all per-daemonId unknown-401 streak state. */
115
+ export declare function __resetUnknown401StreaksForTest(): void;
116
+ /**
117
+ * H1 — Test-only diagnostic counters that prove every outgoing refresh
118
+ * request to `/refresh` is paired with a freshly-built proof.
119
+ *
120
+ * - `requestAttemptCount`: incremented each time `buildRequestInit()`
121
+ * prepares an outgoing fetch.
122
+ * - `proofAttemptCount`: incremented each time `signRequestProof` is
123
+ * invoked (success, null return, OR thrown — they all count as
124
+ * "an attempt was made").
125
+ *
126
+ * Invariant under test: when `signRequestProof` is supplied,
127
+ * `proofAttemptCount === requestAttemptCount` after each call. A future
128
+ * retry that forgets to re-call `buildRequestInit()` (or that hoists
129
+ * the proof outside the loop) breaks this invariant immediately.
130
+ *
131
+ * Exposed only for tests — no production code reads it. Callers can
132
+ * snapshot + reset between asserts.
133
+ */
134
+ export declare const __refreshDiagnostics: {
135
+ proofAttemptCount: number;
136
+ requestAttemptCount: number;
137
+ reset(): void;
138
+ };
139
+ /**
140
+ * Refresh the daemon access+refresh token pair against the server.
141
+ *
142
+ * Single-flight per daemonId: if a refresh is already in flight for this
143
+ * daemon, the caller awaits the same promise. Without this N parallel HTTP
144
+ * calls would fire N refreshes, the second rotation would revoke the first,
145
+ * and the server's family-reuse detector would log everyone out.
146
+ *
147
+ * Does NOT itself persist the new pair — that's the caller's job, so the
148
+ * same lock can span "refresh + write to keychain" atomically.
149
+ */
150
+ /**
151
+ * Optional callback invoked just before each refresh HTTP request to
152
+ * produce an L11 device-identity JWS proof. Daemon callers wire this
153
+ * to `signServerRequest({method, url, refreshToken})` from
154
+ * `daemon/src/deviceIdentity.ts`. Returning `null` / `undefined` (or
155
+ * throwing) leaves the request without a proof header — the daemon's
156
+ * device identity may legitimately not be initialized yet (Phase 1
157
+ * binary running against a Phase 1 server-side, etc.).
158
+ *
159
+ * The callback is invoked TWICE on the rotation path — once for the
160
+ * primary refresh attempt and once for the rotation-resilience retry —
161
+ * because each request gets its own fresh `iat` + `jti` to avoid the
162
+ * server's anti-replay cache.
163
+ */
164
+ export type DeviceProofSigner = (params: {
165
+ method: string;
166
+ url: string;
167
+ refreshToken: string;
168
+ }) => string | null | undefined;
169
+ export declare function refreshDaemonToken(serverUrl: string, currentRefreshToken: string, daemonId: string, options?: {
170
+ machineUuid?: string;
171
+ origin?: string;
172
+ /**
173
+ * L11 Phase 3 — when supplied, every outgoing refresh request is
174
+ * accompanied by an `OverSky-DPoP` header carrying the JWS produced
175
+ * by this callback. A null return is treated as "no proof
176
+ * available" and silently skipped — the server-side middleware in
177
+ * `off` / `warn` mode will continue to accept the request.
178
+ */
179
+ signRequestProof?: DeviceProofSigner;
180
+ }): Promise<TokenPair>;
181
+ /**
182
+ * Best-effort server-side revocation of a refresh session. Idempotent on
183
+ * the server — never reveals whether the presented token existed. Returns
184
+ * silently on transport failure; callers still proceed with local cleanup.
185
+ *
186
+ * Kept tight: 5-second timeout so a hung server doesn't stall logout.
187
+ *
188
+ * H1 (HIGH) — wire contract: the body carries BOTH `refreshTokenHash`
189
+ * (preferred — SHA-256 hex digest of the plaintext refresh token, the
190
+ * exact value the server stores in `refreshHash`) AND the legacy
191
+ * `refreshToken` plaintext during a migration window. Newer servers
192
+ * short-circuit on `refreshTokenHash` and never read the plaintext;
193
+ * older servers ignore the unknown field and fall through to the
194
+ * legacy `refreshToken` path. Once the next API release ships the
195
+ * hash-aware revoke route to the entire fleet, the legacy plaintext
196
+ * field can be dropped from this body.
197
+ *
198
+ * Why both during migration: any intermediary that logs request
199
+ * bodies (CDN, WAF, API gateway, corporate proxy) would otherwise
200
+ * capture a usable refresh token in plaintext. The refresh path
201
+ * already only sends what the server hashes server-side; the revoke
202
+ * path's contract previously leaked the token in transit. Sending
203
+ * the hash makes the body ineligible as a credential for any future
204
+ * replay attempt while keeping back-compat with un-upgraded servers.
205
+ */
206
+ export declare function revokeDaemonRefreshSession(serverUrl: string, refreshToken: string): Promise<void>;
207
+ export declare function persistReauthState(state: ReauthState): void;
208
+ export declare function needsReauthState(): {
209
+ needed: boolean;
210
+ reason?: string;
211
+ since?: number;
212
+ };
213
+ export declare function clearReauthState(): void;
214
+ /** Path getters exposed for tests + the daemon's connection.ts handler. */
215
+ export { authLockPath as AUTH_LOCK_PATH_FN, reauthFlagPath as REAUTH_FLAG_PATH_FN };