@aooth/auth 0.1.77 → 0.1.79
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/atscript-db.cjs +9 -1
- package/dist/atscript-db.d.cts +11 -3
- package/dist/atscript-db.d.mts +11 -3
- package/dist/atscript-db.mjs +9 -1
- package/dist/{auth-credential-C5DiSbb2.d.mts → auth-credential-CLAQgjCS.d.mts} +28 -1
- package/dist/{auth-credential-CU4vsKhu.d.cts → auth-credential-GRAK230V.d.cts} +28 -1
- package/dist/authz.d.cts +2 -2
- package/dist/authz.d.mts +2 -2
- package/dist/{clock-C0O0FCCN.d.cts → clock-CyjqzLxc.d.cts} +3 -2
- package/dist/{clock-C0O0FCCN.d.mts → clock-CyjqzLxc.d.mts} +3 -2
- package/dist/{dynamic-client-store-BCAqzG0Y.d.mts → dynamic-client-store-BF4nXMK2.d.mts} +2 -2
- package/dist/{dynamic-client-store-CafYRhI4.d.cts → dynamic-client-store-HLfcl2FJ.d.cts} +2 -2
- package/dist/index.cjs +170 -25
- package/dist/index.d.cts +23 -7
- package/dist/index.d.mts +23 -7
- package/dist/index.mjs +170 -25
- package/dist/redis.cjs +8 -4
- package/dist/redis.d.cts +3 -3
- package/dist/redis.d.mts +3 -3
- package/dist/redis.mjs +8 -4
- package/dist/{store-3YN2hfss.d.cts → store-CVD1nd2j.d.mts} +7 -4
- package/dist/{store-BUIC0iCK.d.mts → store-ClQnA8yh.d.cts} +7 -4
- package/package.json +10 -10
package/dist/atscript-db.cjs
CHANGED
|
@@ -273,7 +273,15 @@ var CredentialStoreAtscriptDb = class {
|
|
|
273
273
|
async revoke(token) {
|
|
274
274
|
await this.table.deleteOne(token);
|
|
275
275
|
}
|
|
276
|
-
async touch(token, at) {
|
|
276
|
+
async touch(token, at, state) {
|
|
277
|
+
if (state && this.table.updateOne) {
|
|
278
|
+
if (state.expiresAt <= Date.now()) return;
|
|
279
|
+
await this.table.updateOne({
|
|
280
|
+
token,
|
|
281
|
+
lastSeenAt: at
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
277
285
|
const row = await this.table.findOne({ filter: { token } });
|
|
278
286
|
if (!row) return;
|
|
279
287
|
if (row.expiresAt <= Date.now()) return;
|
package/dist/atscript-db.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as CredentialSuccessorRef, r as CredentialStore, s as CredentialState, t as Clock } from "./clock-
|
|
2
|
-
import { c as AuthCodeStore, d as NewAuthCode, h as PendingAuthorizationStore, m as PendingAuthorization, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient } from "./dynamic-client-store-
|
|
1
|
+
import { c as CredentialSuccessorRef, r as CredentialStore, s as CredentialState, t as Clock } from "./clock-CyjqzLxc.cjs";
|
|
2
|
+
import { c as AuthCodeStore, d as NewAuthCode, h as PendingAuthorizationStore, m as PendingAuthorization, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient } from "./dynamic-client-store-HLfcl2FJ.cjs";
|
|
3
3
|
//#region src/atscript-db/authz-stores.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* atscript-db-backed durable implementations of the two short-lived
|
|
@@ -255,6 +255,14 @@ interface AuthCredentialTable<TPayload extends object = object> {
|
|
|
255
255
|
deleteMany(filter: Record<string, unknown>): Promise<{
|
|
256
256
|
deletedCount: number;
|
|
257
257
|
}>;
|
|
258
|
+
/**
|
|
259
|
+
* Optional PK-addressed patch (atscript-db's `updateOne`): lets `touch`
|
|
260
|
+
* stamp `lastSeenAt` without re-reading the row. Never inserts.
|
|
261
|
+
*/
|
|
262
|
+
updateOne?(patch: {
|
|
263
|
+
token: string;
|
|
264
|
+
lastSeenAt: number;
|
|
265
|
+
}): Promise<unknown>;
|
|
258
266
|
}
|
|
259
267
|
interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
|
|
260
268
|
table: AuthCredentialTable<TPayload>;
|
|
@@ -290,7 +298,7 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
|
|
|
290
298
|
consume(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
291
299
|
update(token: string, state: CredentialState & TPayload): Promise<string>;
|
|
292
300
|
revoke(token: string): Promise<void>;
|
|
293
|
-
touch(token: string, at: number): Promise<void>;
|
|
301
|
+
touch(token: string, at: number, state?: CredentialState & TPayload): Promise<void>;
|
|
294
302
|
revokeAllForUser(userId: string): Promise<number>;
|
|
295
303
|
listForUser(userId: string): Promise<Array<CredentialState & TPayload & {
|
|
296
304
|
token: string;
|
package/dist/atscript-db.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as CredentialSuccessorRef, r as CredentialStore, s as CredentialState, t as Clock } from "./clock-
|
|
2
|
-
import { c as AuthCodeStore, d as NewAuthCode, h as PendingAuthorizationStore, m as PendingAuthorization, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient } from "./dynamic-client-store-
|
|
1
|
+
import { c as CredentialSuccessorRef, r as CredentialStore, s as CredentialState, t as Clock } from "./clock-CyjqzLxc.mjs";
|
|
2
|
+
import { c as AuthCodeStore, d as NewAuthCode, h as PendingAuthorizationStore, m as PendingAuthorization, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient } from "./dynamic-client-store-BF4nXMK2.mjs";
|
|
3
3
|
//#region src/atscript-db/authz-stores.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* atscript-db-backed durable implementations of the two short-lived
|
|
@@ -255,6 +255,14 @@ interface AuthCredentialTable<TPayload extends object = object> {
|
|
|
255
255
|
deleteMany(filter: Record<string, unknown>): Promise<{
|
|
256
256
|
deletedCount: number;
|
|
257
257
|
}>;
|
|
258
|
+
/**
|
|
259
|
+
* Optional PK-addressed patch (atscript-db's `updateOne`): lets `touch`
|
|
260
|
+
* stamp `lastSeenAt` without re-reading the row. Never inserts.
|
|
261
|
+
*/
|
|
262
|
+
updateOne?(patch: {
|
|
263
|
+
token: string;
|
|
264
|
+
lastSeenAt: number;
|
|
265
|
+
}): Promise<unknown>;
|
|
258
266
|
}
|
|
259
267
|
interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
|
|
260
268
|
table: AuthCredentialTable<TPayload>;
|
|
@@ -290,7 +298,7 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
|
|
|
290
298
|
consume(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
291
299
|
update(token: string, state: CredentialState & TPayload): Promise<string>;
|
|
292
300
|
revoke(token: string): Promise<void>;
|
|
293
|
-
touch(token: string, at: number): Promise<void>;
|
|
301
|
+
touch(token: string, at: number, state?: CredentialState & TPayload): Promise<void>;
|
|
294
302
|
revokeAllForUser(userId: string): Promise<number>;
|
|
295
303
|
listForUser(userId: string): Promise<Array<CredentialState & TPayload & {
|
|
296
304
|
token: string;
|
package/dist/atscript-db.mjs
CHANGED
|
@@ -272,7 +272,15 @@ var CredentialStoreAtscriptDb = class {
|
|
|
272
272
|
async revoke(token) {
|
|
273
273
|
await this.table.deleteOne(token);
|
|
274
274
|
}
|
|
275
|
-
async touch(token, at) {
|
|
275
|
+
async touch(token, at, state) {
|
|
276
|
+
if (state && this.table.updateOne) {
|
|
277
|
+
if (state.expiresAt <= Date.now()) return;
|
|
278
|
+
await this.table.updateOne({
|
|
279
|
+
token,
|
|
280
|
+
lastSeenAt: at
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
276
284
|
const row = await this.table.findOne({ filter: { token } });
|
|
277
285
|
if (!row) return;
|
|
278
286
|
if (row.expiresAt <= Date.now()) return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthContext, d as RefreshConfig, f as RefreshResult, i as DenylistStore, l as EnrichedSession, m as SessionInfo, o as CredentialMetadata, p as SessionEnricher, r as CredentialStore, s as CredentialState, t as Clock, u as IssueResult } from "./clock-
|
|
1
|
+
import { a as AuthContext, d as RefreshConfig, f as RefreshResult, i as DenylistStore, l as EnrichedSession, m as SessionInfo, o as CredentialMetadata, p as SessionEnricher, r as CredentialStore, s as CredentialState, t as Clock, u as IssueResult } from "./clock-CyjqzLxc.mjs";
|
|
2
2
|
//#region src/credential/auth-credential.d.ts
|
|
3
3
|
interface AuthCredentialOptions<TPayload extends object = object> {
|
|
4
4
|
/** Pluggable credential store (Memory, JWT, Encapsulated, ...). */
|
|
@@ -32,6 +32,22 @@ interface AuthCredentialOptions<TPayload extends object = object> {
|
|
|
32
32
|
* store that implements `touch`; a no-op on stores that don't.
|
|
33
33
|
*/
|
|
34
34
|
trackLastSeen?: "refresh" | "validate" | false;
|
|
35
|
+
/**
|
|
36
|
+
* OPT-IN process-local cache of validated access-token state (token →
|
|
37
|
+
* state), reused by `validate()` for `ttlMs` instead of a store / denylist
|
|
38
|
+
* round trip. Off by default. A hit still re-checks expiry and kind; this
|
|
39
|
+
* instance's `revoke`, `revokeAllForUser`, `revokeSession`,
|
|
40
|
+
* `revokeOtherSessions`, the concurrency eviction and refresh-reuse
|
|
41
|
+
* responses evict their tokens at once. A revocation made elsewhere (another
|
|
42
|
+
* process / instance, or a direct store / denylist write) is seen within
|
|
43
|
+
* `ttlMs` — keep it short (seconds).
|
|
44
|
+
*
|
|
45
|
+
* @since 0.1.78
|
|
46
|
+
*/
|
|
47
|
+
validateCache?: {
|
|
48
|
+
ttlMs: number;
|
|
49
|
+
max?: number;
|
|
50
|
+
};
|
|
35
51
|
/** Optional clock for testability. */
|
|
36
52
|
clock?: Clock;
|
|
37
53
|
}
|
|
@@ -148,9 +164,18 @@ declare class AuthCredential<TPayload extends object = object> {
|
|
|
148
164
|
* access; bounded by refresh TTL.
|
|
149
165
|
*/
|
|
150
166
|
private readonly consumedRefreshes;
|
|
167
|
+
/** Map size at which `consumedRefreshes` next sweeps its expired entries. */
|
|
168
|
+
private consumedSweepAt;
|
|
169
|
+
/** Opt-in validated-state cache (`validateCache`). */
|
|
170
|
+
private readonly validated?;
|
|
151
171
|
constructor(opts: AuthCredentialOptions<TPayload>);
|
|
152
172
|
issue(userId: string, options?: IssueOptions<TPayload>): Promise<IssueResult>;
|
|
153
173
|
validate(accessToken: string): Promise<AuthContext<TPayload> | null>;
|
|
174
|
+
/**
|
|
175
|
+
* The stored state of `accessToken` unless the orchestrator's denylist has
|
|
176
|
+
* it — both looked up concurrently (one round trip on remote stores).
|
|
177
|
+
*/
|
|
178
|
+
private retrieveUnlessDenied;
|
|
154
179
|
refresh(refreshToken: string, opts?: RefreshCallOptions<TPayload>): Promise<RefreshResult>;
|
|
155
180
|
private refreshNone;
|
|
156
181
|
/**
|
|
@@ -240,6 +265,8 @@ declare class AuthCredential<TPayload extends object = object> {
|
|
|
240
265
|
* rotated (keeping it valid through the grace window) instead of consuming it.
|
|
241
266
|
*/
|
|
242
267
|
private issueRotatedPair;
|
|
268
|
+
/** Amortized sweep of expired `consumedRefreshes` entries (each size doubling). */
|
|
269
|
+
private sweepConsumedRefreshes;
|
|
243
270
|
private lookupConsumedRefresh;
|
|
244
271
|
private fireRefreshReuseTheftResponse;
|
|
245
272
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthContext, d as RefreshConfig, f as RefreshResult, i as DenylistStore, l as EnrichedSession, m as SessionInfo, o as CredentialMetadata, p as SessionEnricher, r as CredentialStore, s as CredentialState, t as Clock, u as IssueResult } from "./clock-
|
|
1
|
+
import { a as AuthContext, d as RefreshConfig, f as RefreshResult, i as DenylistStore, l as EnrichedSession, m as SessionInfo, o as CredentialMetadata, p as SessionEnricher, r as CredentialStore, s as CredentialState, t as Clock, u as IssueResult } from "./clock-CyjqzLxc.cjs";
|
|
2
2
|
//#region src/credential/auth-credential.d.ts
|
|
3
3
|
interface AuthCredentialOptions<TPayload extends object = object> {
|
|
4
4
|
/** Pluggable credential store (Memory, JWT, Encapsulated, ...). */
|
|
@@ -32,6 +32,22 @@ interface AuthCredentialOptions<TPayload extends object = object> {
|
|
|
32
32
|
* store that implements `touch`; a no-op on stores that don't.
|
|
33
33
|
*/
|
|
34
34
|
trackLastSeen?: "refresh" | "validate" | false;
|
|
35
|
+
/**
|
|
36
|
+
* OPT-IN process-local cache of validated access-token state (token →
|
|
37
|
+
* state), reused by `validate()` for `ttlMs` instead of a store / denylist
|
|
38
|
+
* round trip. Off by default. A hit still re-checks expiry and kind; this
|
|
39
|
+
* instance's `revoke`, `revokeAllForUser`, `revokeSession`,
|
|
40
|
+
* `revokeOtherSessions`, the concurrency eviction and refresh-reuse
|
|
41
|
+
* responses evict their tokens at once. A revocation made elsewhere (another
|
|
42
|
+
* process / instance, or a direct store / denylist write) is seen within
|
|
43
|
+
* `ttlMs` — keep it short (seconds).
|
|
44
|
+
*
|
|
45
|
+
* @since 0.1.78
|
|
46
|
+
*/
|
|
47
|
+
validateCache?: {
|
|
48
|
+
ttlMs: number;
|
|
49
|
+
max?: number;
|
|
50
|
+
};
|
|
35
51
|
/** Optional clock for testability. */
|
|
36
52
|
clock?: Clock;
|
|
37
53
|
}
|
|
@@ -148,9 +164,18 @@ declare class AuthCredential<TPayload extends object = object> {
|
|
|
148
164
|
* access; bounded by refresh TTL.
|
|
149
165
|
*/
|
|
150
166
|
private readonly consumedRefreshes;
|
|
167
|
+
/** Map size at which `consumedRefreshes` next sweeps its expired entries. */
|
|
168
|
+
private consumedSweepAt;
|
|
169
|
+
/** Opt-in validated-state cache (`validateCache`). */
|
|
170
|
+
private readonly validated?;
|
|
151
171
|
constructor(opts: AuthCredentialOptions<TPayload>);
|
|
152
172
|
issue(userId: string, options?: IssueOptions<TPayload>): Promise<IssueResult>;
|
|
153
173
|
validate(accessToken: string): Promise<AuthContext<TPayload> | null>;
|
|
174
|
+
/**
|
|
175
|
+
* The stored state of `accessToken` unless the orchestrator's denylist has
|
|
176
|
+
* it — both looked up concurrently (one round trip on remote stores).
|
|
177
|
+
*/
|
|
178
|
+
private retrieveUnlessDenied;
|
|
154
179
|
refresh(refreshToken: string, opts?: RefreshCallOptions<TPayload>): Promise<RefreshResult>;
|
|
155
180
|
private refreshNone;
|
|
156
181
|
/**
|
|
@@ -240,6 +265,8 @@ declare class AuthCredential<TPayload extends object = object> {
|
|
|
240
265
|
* rotated (keeping it valid through the grace window) instead of consuming it.
|
|
241
266
|
*/
|
|
242
267
|
private issueRotatedPair;
|
|
268
|
+
/** Amortized sweep of expired `consumedRefreshes` entries (each size doubling). */
|
|
269
|
+
private sweepConsumedRefreshes;
|
|
243
270
|
private lookupConsumedRefresh;
|
|
244
271
|
private fireRefreshReuseTheftResponse;
|
|
245
272
|
/**
|
package/dist/authz.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Clock } from "./clock-
|
|
2
|
-
import { _ as PendingAuthorizationStoreMemoryOptions, a as DynamicClientStoreMemoryOptions, b as tokenPolicyToIssueOptions, c as AuthCodeStore, d as NewAuthCode, f as DEFAULT_PENDING_TTL_MS, g as PendingAuthorizationStoreMemory, h as PendingAuthorizationStore, i as DynamicClientStoreMemory, l as AuthCodeStoreMemory, m as PendingAuthorization, n as DynamicClientAuthMethod, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient, u as AuthCodeStoreMemoryOptions, v as DEFAULT_AUTHZ_REFRESH_TTL_MS, y as TokenPolicy } from "./dynamic-client-store-
|
|
1
|
+
import { t as Clock } from "./clock-CyjqzLxc.cjs";
|
|
2
|
+
import { _ as PendingAuthorizationStoreMemoryOptions, a as DynamicClientStoreMemoryOptions, b as tokenPolicyToIssueOptions, c as AuthCodeStore, d as NewAuthCode, f as DEFAULT_PENDING_TTL_MS, g as PendingAuthorizationStoreMemory, h as PendingAuthorizationStore, i as DynamicClientStoreMemory, l as AuthCodeStoreMemory, m as PendingAuthorization, n as DynamicClientAuthMethod, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient, u as AuthCodeStoreMemoryOptions, v as DEFAULT_AUTHZ_REFRESH_TTL_MS, y as TokenPolicy } from "./dynamic-client-store-HLfcl2FJ.cjs";
|
|
3
3
|
import { JWK } from "jose";
|
|
4
4
|
//#region src/authz/authz-errors.d.ts
|
|
5
5
|
/**
|
package/dist/authz.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Clock } from "./clock-
|
|
2
|
-
import { _ as PendingAuthorizationStoreMemoryOptions, a as DynamicClientStoreMemoryOptions, b as tokenPolicyToIssueOptions, c as AuthCodeStore, d as NewAuthCode, f as DEFAULT_PENDING_TTL_MS, g as PendingAuthorizationStoreMemory, h as PendingAuthorizationStore, i as DynamicClientStoreMemory, l as AuthCodeStoreMemory, m as PendingAuthorization, n as DynamicClientAuthMethod, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient, u as AuthCodeStoreMemoryOptions, v as DEFAULT_AUTHZ_REFRESH_TTL_MS, y as TokenPolicy } from "./dynamic-client-store-
|
|
1
|
+
import { t as Clock } from "./clock-CyjqzLxc.mjs";
|
|
2
|
+
import { _ as PendingAuthorizationStoreMemoryOptions, a as DynamicClientStoreMemoryOptions, b as tokenPolicyToIssueOptions, c as AuthCodeStore, d as NewAuthCode, f as DEFAULT_PENDING_TTL_MS, g as PendingAuthorizationStoreMemory, h as PendingAuthorizationStore, i as DynamicClientStoreMemory, l as AuthCodeStoreMemory, m as PendingAuthorization, n as DynamicClientAuthMethod, o as NewDynamicClient, p as NewPendingAuthorization, r as DynamicClientStore, s as AuthCode, t as DynamicClient, u as AuthCodeStoreMemoryOptions, v as DEFAULT_AUTHZ_REFRESH_TTL_MS, y as TokenPolicy } from "./dynamic-client-store-BF4nXMK2.mjs";
|
|
3
3
|
import { JWK } from "jose";
|
|
4
4
|
//#region src/authz/authz-errors.d.ts
|
|
5
5
|
/**
|
|
@@ -328,9 +328,10 @@ interface CredentialStore<TPayload extends object = object> {
|
|
|
328
328
|
* Optional last-activity stamp. Set `state.lastSeenAt = at` for the token if
|
|
329
329
|
* it exists; no-op otherwise. Backs `AuthCredential`'s
|
|
330
330
|
* `trackLastSeen: 'validate'` mode. Stateless stores omit it (the token is
|
|
331
|
-
* immutable once issued).
|
|
331
|
+
* immutable once issued). `state` (since 0.1.78) is the state `retrieve`
|
|
332
|
+
* just returned for the token — a store may use it instead of re-reading.
|
|
332
333
|
*/
|
|
333
|
-
touch?(token: string, at: number): Promise<void>;
|
|
334
|
+
touch?(token: string, at: number, state?: CredentialState & TPayload): Promise<void>;
|
|
334
335
|
/**
|
|
335
336
|
* Derive a stable, domain-separated 32-byte subkey from this store's
|
|
336
337
|
* symmetric secret via HKDF-SHA256. Used so other subsystems (e.g. the
|
|
@@ -328,9 +328,10 @@ interface CredentialStore<TPayload extends object = object> {
|
|
|
328
328
|
* Optional last-activity stamp. Set `state.lastSeenAt = at` for the token if
|
|
329
329
|
* it exists; no-op otherwise. Backs `AuthCredential`'s
|
|
330
330
|
* `trackLastSeen: 'validate'` mode. Stateless stores omit it (the token is
|
|
331
|
-
* immutable once issued).
|
|
331
|
+
* immutable once issued). `state` (since 0.1.78) is the state `retrieve`
|
|
332
|
+
* just returned for the token — a store may use it instead of re-reading.
|
|
332
333
|
*/
|
|
333
|
-
touch?(token: string, at: number): Promise<void>;
|
|
334
|
+
touch?(token: string, at: number, state?: CredentialState & TPayload): Promise<void>;
|
|
334
335
|
/**
|
|
335
336
|
* Derive a stable, domain-separated 32-byte subkey from this store's
|
|
336
337
|
* symmetric secret via HKDF-SHA256. Used so other subsystems (e.g. the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Clock } from "./clock-
|
|
2
|
-
import { r as IssueOptions } from "./auth-credential-
|
|
1
|
+
import { t as Clock } from "./clock-CyjqzLxc.mjs";
|
|
2
|
+
import { r as IssueOptions } from "./auth-credential-CLAQgjCS.mjs";
|
|
3
3
|
//#region src/authz/token-policy.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* What the authorization server mints for a completed grant — forwarded verbatim
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Clock } from "./clock-
|
|
2
|
-
import { r as IssueOptions } from "./auth-credential-
|
|
1
|
+
import { t as Clock } from "./clock-CyjqzLxc.cjs";
|
|
2
|
+
import { r as IssueOptions } from "./auth-credential-GRAK230V.cjs";
|
|
3
3
|
//#region src/authz/token-policy.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* What the authorization server mints for a completed grant — forwarded verbatim
|
package/dist/index.cjs
CHANGED
|
@@ -81,7 +81,7 @@ var CredentialStoreMemory = class {
|
|
|
81
81
|
this.states.delete(token);
|
|
82
82
|
this.indexRemove(state.userId, token);
|
|
83
83
|
}
|
|
84
|
-
async touch(token, at) {
|
|
84
|
+
async touch(token, at, _state) {
|
|
85
85
|
const state = this.states.get(token);
|
|
86
86
|
if (!state) return;
|
|
87
87
|
state.lastSeenAt = at;
|
|
@@ -143,17 +143,24 @@ var CredentialStoreMemory = class {
|
|
|
143
143
|
* In-memory denylist for stateless revocation.
|
|
144
144
|
*
|
|
145
145
|
* Stores a map from JTI (or any opaque identifier) to absolute expiry timestamp.
|
|
146
|
-
* `has` returns false for entries whose TTL has elapsed and lazily removes them
|
|
147
|
-
* `
|
|
148
|
-
*
|
|
146
|
+
* `has` returns false for entries whose TTL has elapsed and lazily removes them;
|
|
147
|
+
* `add` sweeps expired entries as the map grows (amortized, each time it
|
|
148
|
+
* doubles). `cleanup` performs a sweep on demand and returns the number of
|
|
149
|
+
* entries removed.
|
|
149
150
|
*/
|
|
150
151
|
var DenylistStoreMemory = class {
|
|
151
152
|
entries = /* @__PURE__ */ new Map();
|
|
152
153
|
clock;
|
|
154
|
+
/** Map size at which `add` next sweeps expired entries (amortized; doubles). */
|
|
155
|
+
sweepAt = MIN_SWEEP_SIZE$2;
|
|
153
156
|
constructor(opts) {
|
|
154
157
|
this.clock = opts?.clock ?? require_clock.defaultClock;
|
|
155
158
|
}
|
|
156
159
|
async add(jti, expiresAt) {
|
|
160
|
+
if (this.entries.size >= this.sweepAt && !this.entries.has(jti)) {
|
|
161
|
+
this.sweep(this.clock.now());
|
|
162
|
+
this.sweepAt = Math.max(MIN_SWEEP_SIZE$2, this.entries.size * 2);
|
|
163
|
+
}
|
|
157
164
|
this.entries.set(jti, expiresAt);
|
|
158
165
|
}
|
|
159
166
|
async has(jti) {
|
|
@@ -166,7 +173,9 @@ var DenylistStoreMemory = class {
|
|
|
166
173
|
return true;
|
|
167
174
|
}
|
|
168
175
|
async cleanup() {
|
|
169
|
-
|
|
176
|
+
return this.sweep(this.clock.now());
|
|
177
|
+
}
|
|
178
|
+
sweep(now) {
|
|
170
179
|
let removed = 0;
|
|
171
180
|
for (const [jti, expiresAt] of this.entries) if (expiresAt <= now) {
|
|
172
181
|
this.entries.delete(jti);
|
|
@@ -175,6 +184,8 @@ var DenylistStoreMemory = class {
|
|
|
175
184
|
return removed;
|
|
176
185
|
}
|
|
177
186
|
};
|
|
187
|
+
/** Smallest map size an amortized expiry sweep runs at. */
|
|
188
|
+
const MIN_SWEEP_SIZE$2 = 1024;
|
|
178
189
|
//#endregion
|
|
179
190
|
//#region src/stores/derive-subkey.ts
|
|
180
191
|
const SUBKEY_SALT = Buffer.from("aooth/credential-store/subkey-v1");
|
|
@@ -201,6 +212,12 @@ var CredentialStoreJwt = class {
|
|
|
201
212
|
algorithm;
|
|
202
213
|
signingKey;
|
|
203
214
|
verifyingKey;
|
|
215
|
+
/**
|
|
216
|
+
* HS* with raw secret bytes: the HMAC `CryptoKey` imported once (lazily) and
|
|
217
|
+
* reused for every sign / verify — jose would otherwise re-import the raw
|
|
218
|
+
* bytes per call. `signingKey` keeps the bytes for `deriveSubkey`.
|
|
219
|
+
*/
|
|
220
|
+
hmacKey;
|
|
204
221
|
issuer;
|
|
205
222
|
audience;
|
|
206
223
|
denylist;
|
|
@@ -249,7 +266,7 @@ var CredentialStoreJwt = class {
|
|
|
249
266
|
const builder = new jose.SignJWT({ state: stateClaim }).setProtectedHeader({ alg: this.algorithm }).setSubject(state.userId).setIssuedAt(Math.floor(state.issuedAt / 1e3)).setExpirationTime(Math.floor(expiresAtMs / 1e3)).setJti(jti);
|
|
250
267
|
if (this.issuer) builder.setIssuer(this.issuer);
|
|
251
268
|
if (this.audience) builder.setAudience(this.audience);
|
|
252
|
-
return builder.sign(this.signingKey);
|
|
269
|
+
return builder.sign(await this.keyFor(this.signingKey));
|
|
253
270
|
}
|
|
254
271
|
async retrieve(token) {
|
|
255
272
|
const verified = await this.verify(token);
|
|
@@ -315,7 +332,7 @@ var CredentialStoreJwt = class {
|
|
|
315
332
|
}
|
|
316
333
|
async verify(token) {
|
|
317
334
|
try {
|
|
318
|
-
return { payload: (await (0, jose.jwtVerify)(token, this.verifyingKey, {
|
|
335
|
+
return { payload: (await (0, jose.jwtVerify)(token, await this.keyFor(this.verifyingKey), {
|
|
319
336
|
algorithms: [this.algorithm],
|
|
320
337
|
issuer: this.issuer,
|
|
321
338
|
audience: this.audience,
|
|
@@ -325,6 +342,19 @@ var CredentialStoreJwt = class {
|
|
|
325
342
|
return null;
|
|
326
343
|
}
|
|
327
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* The key jose gets: raw HS* secret bytes as their HMAC `CryptoKey`
|
|
347
|
+
* (imported once — usable for both sign and verify, its hash pinned to the
|
|
348
|
+
* configured algorithm), anything else as given.
|
|
349
|
+
*/
|
|
350
|
+
keyFor(key) {
|
|
351
|
+
if (!(key instanceof Uint8Array) || !HS_ALGS.has(this.algorithm)) return key;
|
|
352
|
+
this.hmacKey ??= crypto.subtle.importKey("raw", key, {
|
|
353
|
+
name: "HMAC",
|
|
354
|
+
hash: `SHA-${this.algorithm.slice(2)}`
|
|
355
|
+
}, false, ["sign", "verify"]);
|
|
356
|
+
return this.hmacKey;
|
|
357
|
+
}
|
|
328
358
|
payloadToState(payload) {
|
|
329
359
|
if (typeof payload.sub !== "string") return null;
|
|
330
360
|
if (typeof payload.iat !== "number" || typeof payload.exp !== "number") return null;
|
|
@@ -511,6 +541,63 @@ function fingerprint(token) {
|
|
|
511
541
|
return (0, node_crypto.createHash)("sha256").update(token).digest("hex");
|
|
512
542
|
}
|
|
513
543
|
/**
|
|
544
|
+
* Turn `ctx.credentialId` (already in place, so key order is kept) into a
|
|
545
|
+
* lazily computed, memoized, still writable property.
|
|
546
|
+
*/
|
|
547
|
+
function lazyCredentialId(ctx, token) {
|
|
548
|
+
let value;
|
|
549
|
+
Object.defineProperty(ctx, "credentialId", {
|
|
550
|
+
enumerable: true,
|
|
551
|
+
configurable: true,
|
|
552
|
+
get: () => value ??= fingerprint(token),
|
|
553
|
+
set: (next) => {
|
|
554
|
+
value = next;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/** Smallest map size an amortized expiry sweep runs at. */
|
|
559
|
+
const MIN_SWEEP_SIZE$1 = 1024;
|
|
560
|
+
/**
|
|
561
|
+
* The opt-in `validateCache`: token → validated state for `ttlMs`, bounded
|
|
562
|
+
* (oldest dropped first). `epoch` moves on every eviction so a validation
|
|
563
|
+
* racing a revocation never re-caches the revoked token.
|
|
564
|
+
*/
|
|
565
|
+
var ValidateCache = class {
|
|
566
|
+
ttlMs;
|
|
567
|
+
max;
|
|
568
|
+
entries = /* @__PURE__ */ new Map();
|
|
569
|
+
epoch = 0;
|
|
570
|
+
constructor(ttlMs, max) {
|
|
571
|
+
this.ttlMs = ttlMs;
|
|
572
|
+
this.max = max;
|
|
573
|
+
}
|
|
574
|
+
get(token, now) {
|
|
575
|
+
const entry = this.entries.get(token);
|
|
576
|
+
if (!entry) return void 0;
|
|
577
|
+
if (entry.until > now) return entry.state;
|
|
578
|
+
this.entries.delete(token);
|
|
579
|
+
}
|
|
580
|
+
set(token, state, now) {
|
|
581
|
+
this.entries.delete(token);
|
|
582
|
+
if (this.entries.size >= this.max) {
|
|
583
|
+
const oldest = this.entries.keys().next();
|
|
584
|
+
if (!oldest.done) this.entries.delete(oldest.value);
|
|
585
|
+
}
|
|
586
|
+
this.entries.set(token, {
|
|
587
|
+
state,
|
|
588
|
+
until: now + this.ttlMs
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
delete(token) {
|
|
592
|
+
this.epoch++;
|
|
593
|
+
this.entries.delete(token);
|
|
594
|
+
}
|
|
595
|
+
deleteUser(userId) {
|
|
596
|
+
this.epoch++;
|
|
597
|
+
for (const [token, entry] of this.entries) if (entry.state.userId === userId) this.entries.delete(token);
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
514
601
|
* Merge a credential's typed payload under the fixed {@link CredentialState}
|
|
515
602
|
* envelope, asserting the `CredentialState & TPayload` shape in ONE place. The
|
|
516
603
|
* payload is spread first so an envelope field always wins a name clash. Every
|
|
@@ -571,6 +658,10 @@ var AuthCredential = class {
|
|
|
571
658
|
* access; bounded by refresh TTL.
|
|
572
659
|
*/
|
|
573
660
|
consumedRefreshes = /* @__PURE__ */ new Map();
|
|
661
|
+
/** Map size at which `consumedRefreshes` next sweeps its expired entries. */
|
|
662
|
+
consumedSweepAt = MIN_SWEEP_SIZE$1;
|
|
663
|
+
/** Opt-in validated-state cache (`validateCache`). */
|
|
664
|
+
validated;
|
|
574
665
|
constructor(opts) {
|
|
575
666
|
this.store = opts.store;
|
|
576
667
|
this.method = opts.method ?? "token";
|
|
@@ -581,6 +672,10 @@ var AuthCredential = class {
|
|
|
581
672
|
this.onLimit = opts.onLimit ?? "reject";
|
|
582
673
|
this.trackLastSeen = opts.trackLastSeen ?? false;
|
|
583
674
|
this.clock = opts.clock ?? require_clock.defaultClock;
|
|
675
|
+
if (opts.validateCache) {
|
|
676
|
+
if (!(opts.validateCache.ttlMs > 0)) throw new AuthError("INVALID_CONFIG", `validateCache.ttlMs must be > 0 (got ${opts.validateCache.ttlMs})`);
|
|
677
|
+
this.validated = new ValidateCache(opts.validateCache.ttlMs, opts.validateCache.max ?? 1e4);
|
|
678
|
+
}
|
|
584
679
|
if (this.accessTtl <= 0) throw new AuthError("INVALID_CONFIG", `accessTtl must be > 0 (got ${this.accessTtl})`);
|
|
585
680
|
if (this.refreshConfig && this.refreshConfig.ttl <= 0) throw new AuthError("INVALID_CONFIG", `refresh.ttl must be > 0 (got ${this.refreshConfig.ttl})`);
|
|
586
681
|
const graceMs = this.refreshConfig?.rotationGraceMs;
|
|
@@ -643,20 +738,42 @@ var AuthCredential = class {
|
|
|
643
738
|
};
|
|
644
739
|
}
|
|
645
740
|
async validate(accessToken) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (!state)
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
741
|
+
let state = this.validated?.get(accessToken, this.clock.now());
|
|
742
|
+
let fresh = false;
|
|
743
|
+
if (!state) {
|
|
744
|
+
fresh = true;
|
|
745
|
+
const epoch = this.validated?.epoch;
|
|
746
|
+
state = await this.retrieveUnlessDenied(accessToken);
|
|
747
|
+
if (!state) return null;
|
|
748
|
+
if (state.expiresAt <= this.clock.now()) return null;
|
|
749
|
+
if (state.kind === "refresh") return null;
|
|
750
|
+
if (this.validated && this.validated.epoch === epoch) this.validated.set(accessToken, state, this.clock.now());
|
|
751
|
+
} else if (state.expiresAt <= this.clock.now()) {
|
|
752
|
+
this.validated.delete(accessToken);
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
if (this.trackLastSeen === "validate" && this.store.touch) await this.store.touch(accessToken, this.clock.now(), fresh ? state : void 0);
|
|
756
|
+
const ctx = {
|
|
653
757
|
...require_payload.credentialPayloadOf(state),
|
|
654
758
|
userId: state.userId,
|
|
655
759
|
method: this.method,
|
|
656
|
-
credentialId:
|
|
760
|
+
credentialId: "",
|
|
657
761
|
sessionId: this.sessionIdOf(state.sessionId, accessToken),
|
|
658
762
|
expiresAt: state.expiresAt
|
|
659
763
|
};
|
|
764
|
+
lazyCredentialId(ctx, accessToken);
|
|
765
|
+
return ctx;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* The stored state of `accessToken` unless the orchestrator's denylist has
|
|
769
|
+
* it — both looked up concurrently (one round trip on remote stores).
|
|
770
|
+
*/
|
|
771
|
+
async retrieveUnlessDenied(accessToken) {
|
|
772
|
+
const retrieving = this.store.retrieve(accessToken);
|
|
773
|
+
if (!this.denylist) return await retrieving;
|
|
774
|
+
retrieving.catch(() => {});
|
|
775
|
+
if (await this.denylist.has(accessToken)) return null;
|
|
776
|
+
return await retrieving;
|
|
660
777
|
}
|
|
661
778
|
async refresh(refreshToken, opts) {
|
|
662
779
|
const now = this.clock.now();
|
|
@@ -707,6 +824,7 @@ var AuthCredential = class {
|
|
|
707
824
|
async refreshAlways(oldState, refreshToken, now) {
|
|
708
825
|
if (!this.store.listForUser) {
|
|
709
826
|
await this.store.consume(refreshToken);
|
|
827
|
+
this.sweepConsumedRefreshes();
|
|
710
828
|
this.consumedRefreshes.set(refreshToken, {
|
|
711
829
|
userId: oldState.userId,
|
|
712
830
|
iat: oldState.issuedAt,
|
|
@@ -763,10 +881,20 @@ var AuthCredential = class {
|
|
|
763
881
|
};
|
|
764
882
|
}
|
|
765
883
|
async revoke(token) {
|
|
766
|
-
|
|
884
|
+
this.validated?.delete(token);
|
|
885
|
+
try {
|
|
886
|
+
await this.store.revoke(token);
|
|
887
|
+
} finally {
|
|
888
|
+
this.validated?.delete(token);
|
|
889
|
+
}
|
|
767
890
|
}
|
|
768
891
|
async revokeAllForUser(userId) {
|
|
769
|
-
|
|
892
|
+
this.validated?.deleteUser(userId);
|
|
893
|
+
try {
|
|
894
|
+
return await this.store.revokeAllForUser(userId);
|
|
895
|
+
} finally {
|
|
896
|
+
this.validated?.deleteUser(userId);
|
|
897
|
+
}
|
|
770
898
|
}
|
|
771
899
|
async listForUser(userId) {
|
|
772
900
|
if (!this.store.listForUser) return [];
|
|
@@ -850,7 +978,7 @@ var AuthCredential = class {
|
|
|
850
978
|
async revokeSession(userId, sessionId) {
|
|
851
979
|
if (!this.store.listForUser) return;
|
|
852
980
|
const all = await this.store.listForUser(userId);
|
|
853
|
-
await Promise.all(all.filter((entry) => this.sessionKeyOf(entry) === sessionId).map((entry) => this.
|
|
981
|
+
await Promise.all(all.filter((entry) => this.sessionKeyOf(entry) === sessionId).map((entry) => this.revoke(entry.token)));
|
|
854
982
|
}
|
|
855
983
|
/**
|
|
856
984
|
* Revoke every session for the user EXCEPT `keepSessionId` ("log out
|
|
@@ -866,7 +994,7 @@ var AuthCredential = class {
|
|
|
866
994
|
const key = this.sessionKeyOf(entry);
|
|
867
995
|
if (key === keepSessionId) continue;
|
|
868
996
|
revokedSessions.add(key);
|
|
869
|
-
revokes.push(this.
|
|
997
|
+
revokes.push(this.revoke(entry.token));
|
|
870
998
|
}
|
|
871
999
|
await Promise.all(revokes);
|
|
872
1000
|
return revokedSessions.size;
|
|
@@ -893,7 +1021,7 @@ var AuthCredential = class {
|
|
|
893
1021
|
});
|
|
894
1022
|
const sorted = accessOnly.toSorted((a, b) => a.issuedAt - b.issuedAt);
|
|
895
1023
|
const toEvict = accessOnly.length - this.maxConcurrent + 1;
|
|
896
|
-
for (let i = 0; i < toEvict && i < sorted.length; i++) await this.
|
|
1024
|
+
for (let i = 0; i < toEvict && i < sorted.length; i++) await this.revoke(sorted[i].token);
|
|
897
1025
|
}
|
|
898
1026
|
async issueAccessFromRefresh(refreshState, now) {
|
|
899
1027
|
const accessTtl = refreshState.metadata?.accessTtl ?? this.accessTtl;
|
|
@@ -956,6 +1084,13 @@ var AuthCredential = class {
|
|
|
956
1084
|
userId: oldRefreshState.userId
|
|
957
1085
|
};
|
|
958
1086
|
}
|
|
1087
|
+
/** Amortized sweep of expired `consumedRefreshes` entries (each size doubling). */
|
|
1088
|
+
sweepConsumedRefreshes() {
|
|
1089
|
+
if (this.consumedRefreshes.size < this.consumedSweepAt) return;
|
|
1090
|
+
const now = this.clock.now();
|
|
1091
|
+
for (const [token, entry] of this.consumedRefreshes) if (entry.exp <= now) this.consumedRefreshes.delete(token);
|
|
1092
|
+
this.consumedSweepAt = Math.max(MIN_SWEEP_SIZE$1, this.consumedRefreshes.size * 2);
|
|
1093
|
+
}
|
|
959
1094
|
lookupConsumedRefresh(token) {
|
|
960
1095
|
const entry = this.consumedRefreshes.get(token);
|
|
961
1096
|
if (!entry) return null;
|
|
@@ -992,7 +1127,7 @@ var AuthCredential = class {
|
|
|
992
1127
|
...reuse.rotatedAt !== void 0 && { rotatedAt: reuse.rotatedAt }
|
|
993
1128
|
});
|
|
994
1129
|
if ((this.refreshConfig?.reuseResponse ?? "session") === "session" && reuse.sessionId !== void 0 && this.store.listForUser) await this.revokeSession(reuse.userId, reuse.sessionId);
|
|
995
|
-
else await this.
|
|
1130
|
+
else await this.revokeAllForUser(reuse.userId);
|
|
996
1131
|
throw new AuthError("REFRESH_REUSE_DETECTED", void 0, {
|
|
997
1132
|
userId: reuse.userId,
|
|
998
1133
|
...reuse.sessionId !== void 0 && { sessionId: reuse.sessionId },
|
|
@@ -1082,13 +1217,15 @@ function renderRateLimitMessage(template, rule, retryAfterMs) {
|
|
|
1082
1217
|
//#region src/rate-limit/store.ts
|
|
1083
1218
|
/**
|
|
1084
1219
|
* In-memory `RateLimitStore` — the zero-config default (single process only).
|
|
1085
|
-
* Lazy-evicts expired counters on access
|
|
1086
|
-
*
|
|
1087
|
-
* `DenylistStoreMemory`).
|
|
1220
|
+
* Lazy-evicts expired counters on access, and sweeps expired counters itself
|
|
1221
|
+
* as the map grows (amortized, each time it doubles); `cleanup()` sweeps the
|
|
1222
|
+
* whole map on demand (same pattern as `DenylistStoreMemory`).
|
|
1088
1223
|
*/
|
|
1089
1224
|
var RateLimitStoreMemory = class {
|
|
1090
1225
|
entries = /* @__PURE__ */ new Map();
|
|
1091
1226
|
clock;
|
|
1227
|
+
/** Map size at which `hit` next sweeps expired counters (amortized; doubles). */
|
|
1228
|
+
sweepAt = MIN_SWEEP_SIZE;
|
|
1092
1229
|
constructor(opts) {
|
|
1093
1230
|
this.clock = opts?.clock ?? require_clock.defaultClock;
|
|
1094
1231
|
}
|
|
@@ -1096,6 +1233,10 @@ var RateLimitStoreMemory = class {
|
|
|
1096
1233
|
const now = this.clock.now();
|
|
1097
1234
|
const entry = this.entries.get(key);
|
|
1098
1235
|
if (!entry || entry.expiresAt <= now) {
|
|
1236
|
+
if (!entry && this.entries.size >= this.sweepAt) {
|
|
1237
|
+
this.sweep(now);
|
|
1238
|
+
this.sweepAt = Math.max(MIN_SWEEP_SIZE, this.entries.size * 2);
|
|
1239
|
+
}
|
|
1099
1240
|
this.entries.set(key, {
|
|
1100
1241
|
count: 1,
|
|
1101
1242
|
expiresAt: now + ttlMs
|
|
@@ -1113,7 +1254,9 @@ var RateLimitStoreMemory = class {
|
|
|
1113
1254
|
this.entries.clear();
|
|
1114
1255
|
}
|
|
1115
1256
|
async cleanup() {
|
|
1116
|
-
|
|
1257
|
+
return this.sweep(this.clock.now());
|
|
1258
|
+
}
|
|
1259
|
+
sweep(now) {
|
|
1117
1260
|
let removed = 0;
|
|
1118
1261
|
for (const [key, entry] of this.entries) if (entry.expiresAt <= now) {
|
|
1119
1262
|
this.entries.delete(key);
|
|
@@ -1122,6 +1265,8 @@ var RateLimitStoreMemory = class {
|
|
|
1122
1265
|
return removed;
|
|
1123
1266
|
}
|
|
1124
1267
|
};
|
|
1268
|
+
/** Smallest map size an amortized expiry sweep runs at. */
|
|
1269
|
+
const MIN_SWEEP_SIZE = 1024;
|
|
1125
1270
|
//#endregion
|
|
1126
1271
|
//#region src/rate-limit/rate-limiter.ts
|
|
1127
1272
|
/**
|