@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.
- package/README.md +21 -0
- package/dist/cjs/aead.d.ts +24 -0
- package/dist/cjs/aead.js +65 -0
- package/dist/cjs/authExpiredDetector.d.ts +65 -0
- package/dist/cjs/authExpiredDetector.js +89 -0
- package/dist/cjs/authHelper.d.ts +51 -0
- package/dist/cjs/authHelper.js +321 -0
- package/dist/cjs/credentialEnvelope.d.ts +85 -0
- package/dist/cjs/credentialEnvelope.js +230 -0
- package/dist/cjs/credentialEnvelopeBridge.d.ts +116 -0
- package/dist/cjs/credentialEnvelopeBridge.js +637 -0
- package/dist/cjs/credentialResolver.d.ts +278 -0
- package/dist/cjs/credentialResolver.js +423 -0
- package/dist/cjs/daemonPair.d.ts +172 -0
- package/dist/cjs/daemonPair.js +495 -0
- package/dist/cjs/daemonScopes.d.ts +74 -0
- package/dist/cjs/daemonScopes.js +84 -0
- package/dist/cjs/deviceIdentityBridge.d.ts +113 -0
- package/dist/cjs/deviceIdentityBridge.js +591 -0
- package/dist/cjs/deviceKey.d.ts +167 -0
- package/dist/cjs/deviceKey.js +332 -0
- package/dist/cjs/fdToken.d.ts +17 -0
- package/dist/cjs/fdToken.js +238 -0
- package/dist/cjs/harnessTrust.d.ts +72 -0
- package/dist/cjs/harnessTrust.js +103 -0
- package/dist/cjs/headers-helper.d.ts +25 -0
- package/dist/cjs/headers-helper.js +323 -0
- package/dist/cjs/index.d.ts +37 -0
- package/dist/cjs/index.js +305 -0
- package/dist/cjs/jwtUtils.d.ts +35 -0
- package/dist/cjs/jwtUtils.js +61 -0
- package/dist/cjs/kek/index.d.ts +89 -0
- package/dist/cjs/kek/index.js +208 -0
- package/dist/cjs/kek/linux.d.ts +92 -0
- package/dist/cjs/kek/linux.js +596 -0
- package/dist/cjs/kek/macos.d.ts +112 -0
- package/dist/cjs/kek/macos.js +599 -0
- package/dist/cjs/kek/types.d.ts +111 -0
- package/dist/cjs/kek/types.js +43 -0
- package/dist/cjs/kek/windows.d.ts +71 -0
- package/dist/cjs/kek/windows.js +350 -0
- package/dist/cjs/kek/zeroize-registry.d.ts +44 -0
- package/dist/cjs/kek/zeroize-registry.js +63 -0
- package/dist/cjs/legacyStatePreflight.d.ts +67 -0
- package/dist/cjs/legacyStatePreflight.js +78 -0
- package/dist/cjs/localIdentity.d.ts +54 -0
- package/dist/cjs/localIdentity.js +57 -0
- package/dist/cjs/loginLocalhost.d.ts +115 -0
- package/dist/cjs/loginLocalhost.js +368 -0
- package/dist/cjs/loginWithLocalhost.d.ts +131 -0
- package/dist/cjs/loginWithLocalhost.js +359 -0
- package/dist/cjs/machineId.d.ts +35 -0
- package/dist/cjs/machineId.js +239 -0
- package/dist/cjs/messages.d.ts +9 -0
- package/dist/cjs/messages.js +44 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pkce.d.ts +62 -0
- package/dist/cjs/pkce.js +158 -0
- package/dist/cjs/recoveryCode.d.ts +61 -0
- package/dist/cjs/recoveryCode.js +213 -0
- package/dist/cjs/refresh.d.ts +215 -0
- package/dist/cjs/refresh.js +877 -0
- package/dist/cjs/refreshClassification.d.ts +53 -0
- package/dist/cjs/refreshClassification.js +105 -0
- package/dist/cjs/refreshScheduler.d.ts +53 -0
- package/dist/cjs/refreshScheduler.js +332 -0
- package/dist/cjs/runtime.d.ts +149 -0
- package/dist/cjs/runtime.js +136 -0
- package/dist/cjs/spawnEnv.d.ts +76 -0
- package/dist/cjs/spawnEnv.js +153 -0
- package/dist/cjs/ttlParser.d.ts +54 -0
- package/dist/cjs/ttlParser.js +87 -0
- package/dist/cjs/types.d.ts +91 -0
- package/dist/cjs/types.js +26 -0
- package/dist/esm/aead.d.ts +24 -0
- package/dist/esm/aead.js +57 -0
- package/dist/esm/authExpiredDetector.d.ts +65 -0
- package/dist/esm/authExpiredDetector.js +85 -0
- package/dist/esm/authHelper.d.ts +51 -0
- package/dist/esm/authHelper.js +311 -0
- package/dist/esm/credentialEnvelope.d.ts +85 -0
- package/dist/esm/credentialEnvelope.js +213 -0
- package/dist/esm/credentialEnvelopeBridge.d.ts +116 -0
- package/dist/esm/credentialEnvelopeBridge.js +620 -0
- package/dist/esm/credentialResolver.d.ts +278 -0
- package/dist/esm/credentialResolver.js +414 -0
- package/dist/esm/daemonPair.d.ts +172 -0
- package/dist/esm/daemonPair.js +476 -0
- package/dist/esm/daemonScopes.d.ts +74 -0
- package/dist/esm/daemonScopes.js +80 -0
- package/dist/esm/deviceIdentityBridge.d.ts +113 -0
- package/dist/esm/deviceIdentityBridge.js +572 -0
- package/dist/esm/deviceKey.d.ts +167 -0
- package/dist/esm/deviceKey.js +320 -0
- package/dist/esm/fdToken.d.ts +17 -0
- package/dist/esm/fdToken.js +231 -0
- package/dist/esm/harnessTrust.d.ts +72 -0
- package/dist/esm/harnessTrust.js +97 -0
- package/dist/esm/headers-helper.d.ts +25 -0
- package/dist/esm/headers-helper.js +316 -0
- package/dist/esm/index.d.ts +37 -0
- package/dist/esm/index.js +144 -0
- package/dist/esm/jwtUtils.d.ts +35 -0
- package/dist/esm/jwtUtils.js +57 -0
- package/dist/esm/kek/index.d.ts +89 -0
- package/dist/esm/kek/index.js +162 -0
- package/dist/esm/kek/linux.d.ts +92 -0
- package/dist/esm/kek/linux.js +585 -0
- package/dist/esm/kek/macos.d.ts +112 -0
- package/dist/esm/kek/macos.js +586 -0
- package/dist/esm/kek/types.d.ts +111 -0
- package/dist/esm/kek/types.js +39 -0
- package/dist/esm/kek/windows.d.ts +71 -0
- package/dist/esm/kek/windows.js +344 -0
- package/dist/esm/kek/zeroize-registry.d.ts +44 -0
- package/dist/esm/kek/zeroize-registry.js +58 -0
- package/dist/esm/legacyStatePreflight.d.ts +67 -0
- package/dist/esm/legacyStatePreflight.js +71 -0
- package/dist/esm/localIdentity.d.ts +54 -0
- package/dist/esm/localIdentity.js +54 -0
- package/dist/esm/loginLocalhost.d.ts +115 -0
- package/dist/esm/loginLocalhost.js +355 -0
- package/dist/esm/loginWithLocalhost.d.ts +131 -0
- package/dist/esm/loginWithLocalhost.js +353 -0
- package/dist/esm/machineId.d.ts +35 -0
- package/dist/esm/machineId.js +231 -0
- package/dist/esm/messages.d.ts +9 -0
- package/dist/esm/messages.js +40 -0
- package/dist/esm/pkce.d.ts +62 -0
- package/dist/esm/pkce.js +148 -0
- package/dist/esm/recoveryCode.d.ts +61 -0
- package/dist/esm/recoveryCode.js +207 -0
- package/dist/esm/refresh.d.ts +215 -0
- package/dist/esm/refresh.js +863 -0
- package/dist/esm/refreshClassification.d.ts +53 -0
- package/dist/esm/refreshClassification.js +106 -0
- package/dist/esm/refreshScheduler.d.ts +53 -0
- package/dist/esm/refreshScheduler.js +329 -0
- package/dist/esm/runtime.d.ts +149 -0
- package/dist/esm/runtime.js +121 -0
- package/dist/esm/spawnEnv.d.ts +76 -0
- package/dist/esm/spawnEnv.js +149 -0
- package/dist/esm/ttlParser.d.ts +54 -0
- package/dist/esm/ttlParser.js +83 -0
- package/dist/esm/types.d.ts +91 -0
- package/dist/esm/types.js +21 -0
- 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 };
|