@aooth/auth 0.1.10 → 0.1.12

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/authz.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_clock = require("./clock-Bl-H3eqE.cjs");
3
+ const require_auth_code_store = require("./auth-code-store-CmUhRrwG.cjs");
3
4
  let node_crypto = require("node:crypto");
4
5
  let jose = require("jose");
5
6
  //#region src/authz/authz-errors.ts
@@ -252,127 +253,16 @@ var IdTokenSigner = class {
252
253
  }
253
254
  };
254
255
  //#endregion
255
- //#region src/authz/pending-authorization-store.ts
256
- /**
257
- * Storage seam for in-flight authorizations (AUTH-SERVER.md §4.3). Short-lived
258
- * (≈ the login-session ceiling): created at `/authorize`, read+deleted at the
259
- * wf terminal. An in-memory impl ships for single-process apps + tests; a
260
- * multi-pod deployment provides a durable (e.g. Redis) impl under the same
261
- * `PENDING_AUTHORIZATION_STORE_TOKEN` (from `@aooth/auth-moost`).
262
- */
263
- var PendingAuthorizationStore = class {};
264
- const DEFAULT_PENDING_TTL_MS = 15 * 6e4;
265
- /**
266
- * In-memory {@link PendingAuthorizationStore} — the reference impl for a
267
- * single-process app + tests. `structuredClone` on read/write isolates callers.
268
- */
269
- var PendingAuthorizationStoreMemory = class extends PendingAuthorizationStore {
270
- store = /* @__PURE__ */ new Map();
271
- clock;
272
- ttlMs;
273
- constructor(opts) {
274
- super();
275
- this.clock = opts?.clock ?? require_clock.defaultClock;
276
- this.ttlMs = opts?.ttlMs ?? DEFAULT_PENDING_TTL_MS;
277
- }
278
- async create(rec) {
279
- const now = this.clock.now();
280
- const row = {
281
- handle: (0, node_crypto.randomUUID)(),
282
- redirectUri: rec.redirectUri,
283
- codeChallenge: rec.codeChallenge,
284
- tokenPolicy: structuredClone(rec.tokenPolicy),
285
- createdAt: now,
286
- expiresAt: now + this.ttlMs,
287
- ...rec.clientId !== void 0 && { clientId: rec.clientId },
288
- ...rec.clientState !== void 0 && { clientState: rec.clientState },
289
- ...rec.scope !== void 0 && { scope: rec.scope },
290
- ...rec.nonce !== void 0 && { nonce: rec.nonce },
291
- ...rec.idToken !== void 0 && { idToken: rec.idToken },
292
- ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
293
- ...rec.audience !== void 0 && { audience: rec.audience }
294
- };
295
- this.store.set(row.handle, structuredClone(row));
296
- return { handle: row.handle };
297
- }
298
- async get(handle) {
299
- const row = this.store.get(handle);
300
- if (!row) return null;
301
- if (row.expiresAt <= this.clock.now()) {
302
- this.store.delete(handle);
303
- return null;
304
- }
305
- return structuredClone(row);
306
- }
307
- async delete(handle) {
308
- return this.store.delete(handle);
309
- }
310
- };
311
- //#endregion
312
- //#region src/authz/auth-code-store.ts
313
- /**
314
- * Storage seam for issued authorization codes (AUTH-SERVER.md §4.3). Very
315
- * short-lived (≈ 30–60 s) and **single-use**: {@link consume} returns the row
316
- * AND invalidates it in one atomic step, so a concurrent double-redeem (or a
317
- * back-button replay) yields the code to exactly one caller. An in-memory impl
318
- * ships (atomic for free in single-threaded JS); a durable impl must implement
319
- * `consume` as an atomic check-and-delete (e.g. `withCas` / `@db.column.version`,
320
- * or a Redis `GETDEL`).
321
- */
322
- var AuthCodeStore = class {};
323
- const DEFAULT_CODE_TTL_MS = 6e4;
324
- /**
325
- * In-memory {@link AuthCodeStore} — the reference impl. `consume` is atomic
326
- * because the `get` + `delete` run with no intervening `await`, so a second
327
- * concurrent `consume` of the same code always misses.
328
- */
329
- var AuthCodeStoreMemory = class extends AuthCodeStore {
330
- store = /* @__PURE__ */ new Map();
331
- clock;
332
- ttlMs;
333
- constructor(opts) {
334
- super();
335
- this.clock = opts?.clock ?? require_clock.defaultClock;
336
- this.ttlMs = opts?.ttlMs ?? DEFAULT_CODE_TTL_MS;
337
- }
338
- async mint(rec) {
339
- const code = (0, node_crypto.randomUUID)();
340
- const row = {
341
- code,
342
- userId: rec.userId,
343
- codeChallenge: rec.codeChallenge,
344
- redirectUri: rec.redirectUri,
345
- tokenPolicy: structuredClone(rec.tokenPolicy),
346
- expiresAt: this.clock.now() + this.ttlMs,
347
- ...rec.clientId !== void 0 && { clientId: rec.clientId },
348
- ...rec.scope !== void 0 && { scope: rec.scope },
349
- ...rec.nonce !== void 0 && { nonce: rec.nonce },
350
- ...rec.idToken !== void 0 && { idToken: rec.idToken },
351
- ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
352
- ...rec.audience !== void 0 && { audience: rec.audience }
353
- };
354
- this.store.set(code, structuredClone(row));
355
- return { code };
356
- }
357
- async consume(code) {
358
- const row = this.store.get(code);
359
- if (!row) return null;
360
- this.store.delete(code);
361
- if (row.expiresAt <= this.clock.now()) return null;
362
- return structuredClone(row);
363
- }
364
- };
365
- //#endregion
366
- exports.AuthCodeStore = AuthCodeStore;
367
- exports.AuthCodeStoreMemory = AuthCodeStoreMemory;
256
+ exports.AuthCodeStore = require_auth_code_store.AuthCodeStore;
257
+ exports.AuthCodeStoreMemory = require_auth_code_store.AuthCodeStoreMemory;
368
258
  exports.AuthorizeError = AuthorizeError;
369
259
  exports.CompositeClientPolicy = CompositeClientPolicy;
370
260
  exports.IdTokenSigner = IdTokenSigner;
371
261
  exports.LoopbackClientPolicy = LoopbackClientPolicy;
372
262
  exports.NoopOidcClaimsResolver = NoopOidcClaimsResolver;
373
263
  exports.OidcClaimsResolver = OidcClaimsResolver;
374
- exports.PendingAuthorizationStore = PendingAuthorizationStore;
375
- exports.PendingAuthorizationStoreMemory = PendingAuthorizationStoreMemory;
264
+ exports.PendingAuthorizationStore = require_auth_code_store.PendingAuthorizationStore;
265
+ exports.PendingAuthorizationStoreMemory = require_auth_code_store.PendingAuthorizationStoreMemory;
376
266
  exports.RegisteredClientPolicy = RegisteredClientPolicy;
377
267
  exports.isLoopbackRedirectUri = isLoopbackRedirectUri;
378
268
  exports.scopeGrants = scopeGrants;
package/dist/authz.d.cts CHANGED
@@ -1,32 +1,7 @@
1
1
  import { t as Clock } from "./clock-BjXa0LXb.cjs";
2
+ import { a as NewAuthCode, c as PendingAuthorizationStore, d as TokenPolicy, i as AuthCodeStoreMemoryOptions, l as PendingAuthorizationStoreMemory, n as AuthCodeStore, o as NewPendingAuthorization, r as AuthCodeStoreMemory, s as PendingAuthorization, t as AuthCode, u as PendingAuthorizationStoreMemoryOptions } from "./auth-code-store-HOHlz_3z.cjs";
2
3
  import { JWK } from "jose";
3
4
 
4
- //#region src/authz/token-policy.d.ts
5
- /**
6
- * What the authorization server mints for a completed grant — forwarded verbatim
7
- * into `AuthCredential.issue(userId, …)` at the token endpoint. Decided by the
8
- * {@link import("./client-policy").ClientRedirectPolicy} (per client / scope),
9
- * never by the client request, and recorded on the pending-authorization + the
10
- * issued auth-code so the grant's authority is fixed at `/authorize` time, not
11
- * `/token` time.
12
- *
13
- * Tier 1 (CLI / loopback) → a full-authority `cli-session` (`{ kind, ttl }`, no
14
- * `payload`). A scoped service token (Tier 2) additionally sets `payload` with
15
- * the consumer's attenuation root fields.
16
- */
17
- interface TokenPolicy {
18
- /** Semantic credential kind stamped on the token (e.g. `"cli-session"`). See `IssueOptions.kind`. */
19
- kind?: string;
20
- /** Per-mint access-token lifetime in ms (forwarded to `IssueOptions.ttl`). */
21
- ttl?: number;
22
- /**
23
- * Extra root payload merged into `issue()` — e.g. `@arbac.attenuate.*` fields
24
- * for a SCOPED token. Omit for a full-authority token. MUST be JSON-safe: it
25
- * is persisted on the pending-authorization + auth-code records.
26
- */
27
- payload?: Record<string, unknown>;
28
- }
29
- //#endregion
30
5
  //#region src/authz/authz-errors.d.ts
31
6
  /**
32
7
  * Failure taxonomy for the authorization-server endpoints (AUTH-SERVER.md §7).
@@ -329,174 +304,4 @@ declare class NoopOidcClaimsResolver extends OidcClaimsResolver {
329
304
  /** `true` when `scope` (space-joined) grants `claim` — `"email"`/`"profile"` etc. */
330
305
  declare function scopeGrants(scope: string | undefined, claim: string): boolean;
331
306
  //#endregion
332
- //#region src/authz/pending-authorization-store.d.ts
333
- /**
334
- * One in-flight authorization request, recorded at `GET /auth/authorize` and
335
- * read once at the login-workflow terminal that mints the auth code. Keyed by an
336
- * opaque `handle` that rides the login-wf ctx (and, across a "Continue with
337
- * Google" detour, the federated signed `state`) — so nothing secret leaves the
338
- * server.
339
- */
340
- interface PendingAuthorization {
341
- /** Opaque server-side handle (the only thing that rides the URL / wf state). */
342
- handle: string;
343
- /** Registered client id (Tier 2), absent for a public/loopback client. */
344
- clientId?: string;
345
- /** The client's validated `redirect_uri` — where the code is delivered. */
346
- redirectUri: string;
347
- /** PKCE S256 challenge (client-generated); verified against the verifier at `/token`. */
348
- codeChallenge: string;
349
- /** The client's `state`, echoed back on the redirect so the client can correlate. */
350
- clientState?: string;
351
- /** Granted scope (space-joined) — `requested ∩ allowed`; drives the `id_token` profile claims. */
352
- scope?: string;
353
- /** OIDC `nonce` from the authorize request — echoed into the `id_token` (Tier 2). */
354
- nonce?: string;
355
- /** Mint an `id_token` at `/token` (Tier 2). */
356
- idToken?: boolean;
357
- /** Mint an access token at `/token`. Omitted ⇒ minted (Tier-1 loopback). */
358
- accessToken?: boolean;
359
- /** The `id_token` `aud` (the registered `client_id`). */
360
- audience?: string;
361
- /** What the grant will mint (fixed at authorize time). */
362
- tokenPolicy: TokenPolicy;
363
- createdAt: number;
364
- expiresAt: number;
365
- }
366
- /** Input to {@link PendingAuthorizationStore.create} — `handle`/timestamps are store-assigned. */
367
- interface NewPendingAuthorization {
368
- clientId?: string;
369
- redirectUri: string;
370
- codeChallenge: string;
371
- clientState?: string;
372
- scope?: string;
373
- nonce?: string;
374
- idToken?: boolean;
375
- accessToken?: boolean;
376
- audience?: string;
377
- tokenPolicy: TokenPolicy;
378
- }
379
- /**
380
- * Storage seam for in-flight authorizations (AUTH-SERVER.md §4.3). Short-lived
381
- * (≈ the login-session ceiling): created at `/authorize`, read+deleted at the
382
- * wf terminal. An in-memory impl ships for single-process apps + tests; a
383
- * multi-pod deployment provides a durable (e.g. Redis) impl under the same
384
- * `PENDING_AUTHORIZATION_STORE_TOKEN` (from `@aooth/auth-moost`).
385
- */
386
- declare abstract class PendingAuthorizationStore {
387
- /** Record a new pending authorization; returns its opaque `handle`. */
388
- abstract create(rec: NewPendingAuthorization): Promise<{
389
- handle: string;
390
- }>;
391
- /** Fetch by handle, or `null` when unknown/expired. */
392
- abstract get(handle: string): Promise<PendingAuthorization | null>;
393
- /** Drop a handle once consumed. Returns `true` when a row was removed. */
394
- abstract delete(handle: string): Promise<boolean>;
395
- }
396
- interface PendingAuthorizationStoreMemoryOptions {
397
- /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
398
- clock?: Clock;
399
- /** How long a pending authorization stays valid. Default 15 min. */
400
- ttlMs?: number;
401
- }
402
- /**
403
- * In-memory {@link PendingAuthorizationStore} — the reference impl for a
404
- * single-process app + tests. `structuredClone` on read/write isolates callers.
405
- */
406
- declare class PendingAuthorizationStoreMemory extends PendingAuthorizationStore {
407
- private store;
408
- private clock;
409
- private ttlMs;
410
- constructor(opts?: PendingAuthorizationStoreMemoryOptions);
411
- create(rec: NewPendingAuthorization): Promise<{
412
- handle: string;
413
- }>;
414
- get(handle: string): Promise<PendingAuthorization | null>;
415
- delete(handle: string): Promise<boolean>;
416
- }
417
- //#endregion
418
- //#region src/authz/auth-code-store.d.ts
419
- /**
420
- * A minted, single-use authorization code, bound to the user the login resolved
421
- * plus the PKCE challenge / redirect / token policy recorded at `/authorize`.
422
- * Consumed atomically at `POST /auth/token` — the token is minted there, off the
423
- * browser, so nothing long-lived ever rides a redirect URL.
424
- */
425
- interface AuthCode {
426
- /** Opaque single-use code (delivered to the client in the redirect query). */
427
- code: string;
428
- /** The user the login workflow authenticated. */
429
- userId: string;
430
- /** PKCE S256 challenge from the originating authorize request. */
431
- codeChallenge: string;
432
- /** The client's `redirect_uri` (bound; the code is meaningless elsewhere). */
433
- redirectUri: string;
434
- /** Registered client id (Tier 2), absent for a public/loopback client. */
435
- clientId?: string;
436
- /** Granted scope (space-joined) — drives the `id_token` profile claims. */
437
- scope?: string;
438
- /** OIDC `nonce` from the authorize request — echoed into the `id_token` (Tier 2). */
439
- nonce?: string;
440
- /** Mint an `id_token` at `/token` (Tier 2). */
441
- idToken?: boolean;
442
- /** Mint an access token at `/token`. Omitted ⇒ minted (Tier-1 loopback). */
443
- accessToken?: boolean;
444
- /** The `id_token` `aud` (the registered `client_id`). */
445
- audience?: string;
446
- /** What `/token` mints when this code is redeemed. */
447
- tokenPolicy: TokenPolicy;
448
- expiresAt: number;
449
- }
450
- /** Input to {@link AuthCodeStore.mint} — `code`/`expiresAt` are store-assigned. */
451
- interface NewAuthCode {
452
- userId: string;
453
- codeChallenge: string;
454
- redirectUri: string;
455
- clientId?: string;
456
- scope?: string;
457
- nonce?: string;
458
- idToken?: boolean;
459
- accessToken?: boolean;
460
- audience?: string;
461
- tokenPolicy: TokenPolicy;
462
- }
463
- /**
464
- * Storage seam for issued authorization codes (AUTH-SERVER.md §4.3). Very
465
- * short-lived (≈ 30–60 s) and **single-use**: {@link consume} returns the row
466
- * AND invalidates it in one atomic step, so a concurrent double-redeem (or a
467
- * back-button replay) yields the code to exactly one caller. An in-memory impl
468
- * ships (atomic for free in single-threaded JS); a durable impl must implement
469
- * `consume` as an atomic check-and-delete (e.g. `withCas` / `@db.column.version`,
470
- * or a Redis `GETDEL`).
471
- */
472
- declare abstract class AuthCodeStore {
473
- /** Mint + store a single-use code; returns the opaque code string. */
474
- abstract mint(rec: NewAuthCode): Promise<{
475
- code: string;
476
- }>;
477
- /** Atomically claim + return the code's row, or `null` on miss / reuse / expiry. */
478
- abstract consume(code: string): Promise<AuthCode | null>;
479
- }
480
- interface AuthCodeStoreMemoryOptions {
481
- /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
482
- clock?: Clock;
483
- /** Code lifetime. Default 60 s. */
484
- ttlMs?: number;
485
- }
486
- /**
487
- * In-memory {@link AuthCodeStore} — the reference impl. `consume` is atomic
488
- * because the `get` + `delete` run with no intervening `await`, so a second
489
- * concurrent `consume` of the same code always misses.
490
- */
491
- declare class AuthCodeStoreMemory extends AuthCodeStore {
492
- private store;
493
- private clock;
494
- private ttlMs;
495
- constructor(opts?: AuthCodeStoreMemoryOptions);
496
- mint(rec: NewAuthCode): Promise<{
497
- code: string;
498
- }>;
499
- consume(code: string): Promise<AuthCode | null>;
500
- }
501
- //#endregion
502
307
  export { type AuthCode, AuthCodeStore, AuthCodeStoreMemory, type AuthCodeStoreMemoryOptions, AuthorizeError, type AuthorizeErrorCode, type ClientRedirectPolicy, CompositeClientPolicy, type CompositeClientPolicyOptions, type IdTokenAlg, type IdTokenClaims, IdTokenSigner, type IdTokenSignerOptions, LoopbackClientPolicy, type LoopbackClientPolicyOptions, type NewAuthCode, type NewPendingAuthorization, NoopOidcClaimsResolver, OidcClaimsResolver, type PendingAuthorization, PendingAuthorizationStore, PendingAuthorizationStoreMemory, type PendingAuthorizationStoreMemoryOptions, type RegisteredClient, RegisteredClientPolicy, type RegisteredClientPolicyOptions, type ResolvedClient, type TokenPolicy, isLoopbackRedirectUri, scopeGrants };
package/dist/authz.d.mts CHANGED
@@ -1,32 +1,7 @@
1
1
  import { t as Clock } from "./clock-BjXa0LXb.mjs";
2
+ import { a as NewAuthCode, c as PendingAuthorizationStore, d as TokenPolicy, i as AuthCodeStoreMemoryOptions, l as PendingAuthorizationStoreMemory, n as AuthCodeStore, o as NewPendingAuthorization, r as AuthCodeStoreMemory, s as PendingAuthorization, t as AuthCode, u as PendingAuthorizationStoreMemoryOptions } from "./auth-code-store-NqEDfqrf.mjs";
2
3
  import { JWK } from "jose";
3
4
 
4
- //#region src/authz/token-policy.d.ts
5
- /**
6
- * What the authorization server mints for a completed grant — forwarded verbatim
7
- * into `AuthCredential.issue(userId, …)` at the token endpoint. Decided by the
8
- * {@link import("./client-policy").ClientRedirectPolicy} (per client / scope),
9
- * never by the client request, and recorded on the pending-authorization + the
10
- * issued auth-code so the grant's authority is fixed at `/authorize` time, not
11
- * `/token` time.
12
- *
13
- * Tier 1 (CLI / loopback) → a full-authority `cli-session` (`{ kind, ttl }`, no
14
- * `payload`). A scoped service token (Tier 2) additionally sets `payload` with
15
- * the consumer's attenuation root fields.
16
- */
17
- interface TokenPolicy {
18
- /** Semantic credential kind stamped on the token (e.g. `"cli-session"`). See `IssueOptions.kind`. */
19
- kind?: string;
20
- /** Per-mint access-token lifetime in ms (forwarded to `IssueOptions.ttl`). */
21
- ttl?: number;
22
- /**
23
- * Extra root payload merged into `issue()` — e.g. `@arbac.attenuate.*` fields
24
- * for a SCOPED token. Omit for a full-authority token. MUST be JSON-safe: it
25
- * is persisted on the pending-authorization + auth-code records.
26
- */
27
- payload?: Record<string, unknown>;
28
- }
29
- //#endregion
30
5
  //#region src/authz/authz-errors.d.ts
31
6
  /**
32
7
  * Failure taxonomy for the authorization-server endpoints (AUTH-SERVER.md §7).
@@ -329,174 +304,4 @@ declare class NoopOidcClaimsResolver extends OidcClaimsResolver {
329
304
  /** `true` when `scope` (space-joined) grants `claim` — `"email"`/`"profile"` etc. */
330
305
  declare function scopeGrants(scope: string | undefined, claim: string): boolean;
331
306
  //#endregion
332
- //#region src/authz/pending-authorization-store.d.ts
333
- /**
334
- * One in-flight authorization request, recorded at `GET /auth/authorize` and
335
- * read once at the login-workflow terminal that mints the auth code. Keyed by an
336
- * opaque `handle` that rides the login-wf ctx (and, across a "Continue with
337
- * Google" detour, the federated signed `state`) — so nothing secret leaves the
338
- * server.
339
- */
340
- interface PendingAuthorization {
341
- /** Opaque server-side handle (the only thing that rides the URL / wf state). */
342
- handle: string;
343
- /** Registered client id (Tier 2), absent for a public/loopback client. */
344
- clientId?: string;
345
- /** The client's validated `redirect_uri` — where the code is delivered. */
346
- redirectUri: string;
347
- /** PKCE S256 challenge (client-generated); verified against the verifier at `/token`. */
348
- codeChallenge: string;
349
- /** The client's `state`, echoed back on the redirect so the client can correlate. */
350
- clientState?: string;
351
- /** Granted scope (space-joined) — `requested ∩ allowed`; drives the `id_token` profile claims. */
352
- scope?: string;
353
- /** OIDC `nonce` from the authorize request — echoed into the `id_token` (Tier 2). */
354
- nonce?: string;
355
- /** Mint an `id_token` at `/token` (Tier 2). */
356
- idToken?: boolean;
357
- /** Mint an access token at `/token`. Omitted ⇒ minted (Tier-1 loopback). */
358
- accessToken?: boolean;
359
- /** The `id_token` `aud` (the registered `client_id`). */
360
- audience?: string;
361
- /** What the grant will mint (fixed at authorize time). */
362
- tokenPolicy: TokenPolicy;
363
- createdAt: number;
364
- expiresAt: number;
365
- }
366
- /** Input to {@link PendingAuthorizationStore.create} — `handle`/timestamps are store-assigned. */
367
- interface NewPendingAuthorization {
368
- clientId?: string;
369
- redirectUri: string;
370
- codeChallenge: string;
371
- clientState?: string;
372
- scope?: string;
373
- nonce?: string;
374
- idToken?: boolean;
375
- accessToken?: boolean;
376
- audience?: string;
377
- tokenPolicy: TokenPolicy;
378
- }
379
- /**
380
- * Storage seam for in-flight authorizations (AUTH-SERVER.md §4.3). Short-lived
381
- * (≈ the login-session ceiling): created at `/authorize`, read+deleted at the
382
- * wf terminal. An in-memory impl ships for single-process apps + tests; a
383
- * multi-pod deployment provides a durable (e.g. Redis) impl under the same
384
- * `PENDING_AUTHORIZATION_STORE_TOKEN` (from `@aooth/auth-moost`).
385
- */
386
- declare abstract class PendingAuthorizationStore {
387
- /** Record a new pending authorization; returns its opaque `handle`. */
388
- abstract create(rec: NewPendingAuthorization): Promise<{
389
- handle: string;
390
- }>;
391
- /** Fetch by handle, or `null` when unknown/expired. */
392
- abstract get(handle: string): Promise<PendingAuthorization | null>;
393
- /** Drop a handle once consumed. Returns `true` when a row was removed. */
394
- abstract delete(handle: string): Promise<boolean>;
395
- }
396
- interface PendingAuthorizationStoreMemoryOptions {
397
- /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
398
- clock?: Clock;
399
- /** How long a pending authorization stays valid. Default 15 min. */
400
- ttlMs?: number;
401
- }
402
- /**
403
- * In-memory {@link PendingAuthorizationStore} — the reference impl for a
404
- * single-process app + tests. `structuredClone` on read/write isolates callers.
405
- */
406
- declare class PendingAuthorizationStoreMemory extends PendingAuthorizationStore {
407
- private store;
408
- private clock;
409
- private ttlMs;
410
- constructor(opts?: PendingAuthorizationStoreMemoryOptions);
411
- create(rec: NewPendingAuthorization): Promise<{
412
- handle: string;
413
- }>;
414
- get(handle: string): Promise<PendingAuthorization | null>;
415
- delete(handle: string): Promise<boolean>;
416
- }
417
- //#endregion
418
- //#region src/authz/auth-code-store.d.ts
419
- /**
420
- * A minted, single-use authorization code, bound to the user the login resolved
421
- * plus the PKCE challenge / redirect / token policy recorded at `/authorize`.
422
- * Consumed atomically at `POST /auth/token` — the token is minted there, off the
423
- * browser, so nothing long-lived ever rides a redirect URL.
424
- */
425
- interface AuthCode {
426
- /** Opaque single-use code (delivered to the client in the redirect query). */
427
- code: string;
428
- /** The user the login workflow authenticated. */
429
- userId: string;
430
- /** PKCE S256 challenge from the originating authorize request. */
431
- codeChallenge: string;
432
- /** The client's `redirect_uri` (bound; the code is meaningless elsewhere). */
433
- redirectUri: string;
434
- /** Registered client id (Tier 2), absent for a public/loopback client. */
435
- clientId?: string;
436
- /** Granted scope (space-joined) — drives the `id_token` profile claims. */
437
- scope?: string;
438
- /** OIDC `nonce` from the authorize request — echoed into the `id_token` (Tier 2). */
439
- nonce?: string;
440
- /** Mint an `id_token` at `/token` (Tier 2). */
441
- idToken?: boolean;
442
- /** Mint an access token at `/token`. Omitted ⇒ minted (Tier-1 loopback). */
443
- accessToken?: boolean;
444
- /** The `id_token` `aud` (the registered `client_id`). */
445
- audience?: string;
446
- /** What `/token` mints when this code is redeemed. */
447
- tokenPolicy: TokenPolicy;
448
- expiresAt: number;
449
- }
450
- /** Input to {@link AuthCodeStore.mint} — `code`/`expiresAt` are store-assigned. */
451
- interface NewAuthCode {
452
- userId: string;
453
- codeChallenge: string;
454
- redirectUri: string;
455
- clientId?: string;
456
- scope?: string;
457
- nonce?: string;
458
- idToken?: boolean;
459
- accessToken?: boolean;
460
- audience?: string;
461
- tokenPolicy: TokenPolicy;
462
- }
463
- /**
464
- * Storage seam for issued authorization codes (AUTH-SERVER.md §4.3). Very
465
- * short-lived (≈ 30–60 s) and **single-use**: {@link consume} returns the row
466
- * AND invalidates it in one atomic step, so a concurrent double-redeem (or a
467
- * back-button replay) yields the code to exactly one caller. An in-memory impl
468
- * ships (atomic for free in single-threaded JS); a durable impl must implement
469
- * `consume` as an atomic check-and-delete (e.g. `withCas` / `@db.column.version`,
470
- * or a Redis `GETDEL`).
471
- */
472
- declare abstract class AuthCodeStore {
473
- /** Mint + store a single-use code; returns the opaque code string. */
474
- abstract mint(rec: NewAuthCode): Promise<{
475
- code: string;
476
- }>;
477
- /** Atomically claim + return the code's row, or `null` on miss / reuse / expiry. */
478
- abstract consume(code: string): Promise<AuthCode | null>;
479
- }
480
- interface AuthCodeStoreMemoryOptions {
481
- /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
482
- clock?: Clock;
483
- /** Code lifetime. Default 60 s. */
484
- ttlMs?: number;
485
- }
486
- /**
487
- * In-memory {@link AuthCodeStore} — the reference impl. `consume` is atomic
488
- * because the `get` + `delete` run with no intervening `await`, so a second
489
- * concurrent `consume` of the same code always misses.
490
- */
491
- declare class AuthCodeStoreMemory extends AuthCodeStore {
492
- private store;
493
- private clock;
494
- private ttlMs;
495
- constructor(opts?: AuthCodeStoreMemoryOptions);
496
- mint(rec: NewAuthCode): Promise<{
497
- code: string;
498
- }>;
499
- consume(code: string): Promise<AuthCode | null>;
500
- }
501
- //#endregion
502
307
  export { type AuthCode, AuthCodeStore, AuthCodeStoreMemory, type AuthCodeStoreMemoryOptions, AuthorizeError, type AuthorizeErrorCode, type ClientRedirectPolicy, CompositeClientPolicy, type CompositeClientPolicyOptions, type IdTokenAlg, type IdTokenClaims, IdTokenSigner, type IdTokenSignerOptions, LoopbackClientPolicy, type LoopbackClientPolicyOptions, type NewAuthCode, type NewPendingAuthorization, NoopOidcClaimsResolver, OidcClaimsResolver, type PendingAuthorization, PendingAuthorizationStore, PendingAuthorizationStoreMemory, type PendingAuthorizationStoreMemoryOptions, type RegisteredClient, RegisteredClientPolicy, type RegisteredClientPolicyOptions, type ResolvedClient, type TokenPolicy, isLoopbackRedirectUri, scopeGrants };
package/dist/authz.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { t as defaultClock } from "./clock-Bdsep_1j.mjs";
2
- import { randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { a as PendingAuthorizationStore, n as AuthCodeStoreMemory, o as PendingAuthorizationStoreMemory, t as AuthCodeStore } from "./auth-code-store-uaoL7dvU.mjs";
3
+ import { timingSafeEqual } from "node:crypto";
3
4
  import { SignJWT, exportJWK, importPKCS8, importSPKI } from "jose";
4
5
  //#region src/authz/authz-errors.ts
5
6
  /** A typed authorization-server failure. */
@@ -251,115 +252,4 @@ var IdTokenSigner = class {
251
252
  }
252
253
  };
253
254
  //#endregion
254
- //#region src/authz/pending-authorization-store.ts
255
- /**
256
- * Storage seam for in-flight authorizations (AUTH-SERVER.md §4.3). Short-lived
257
- * (≈ the login-session ceiling): created at `/authorize`, read+deleted at the
258
- * wf terminal. An in-memory impl ships for single-process apps + tests; a
259
- * multi-pod deployment provides a durable (e.g. Redis) impl under the same
260
- * `PENDING_AUTHORIZATION_STORE_TOKEN` (from `@aooth/auth-moost`).
261
- */
262
- var PendingAuthorizationStore = class {};
263
- const DEFAULT_PENDING_TTL_MS = 15 * 6e4;
264
- /**
265
- * In-memory {@link PendingAuthorizationStore} — the reference impl for a
266
- * single-process app + tests. `structuredClone` on read/write isolates callers.
267
- */
268
- var PendingAuthorizationStoreMemory = class extends PendingAuthorizationStore {
269
- store = /* @__PURE__ */ new Map();
270
- clock;
271
- ttlMs;
272
- constructor(opts) {
273
- super();
274
- this.clock = opts?.clock ?? defaultClock;
275
- this.ttlMs = opts?.ttlMs ?? DEFAULT_PENDING_TTL_MS;
276
- }
277
- async create(rec) {
278
- const now = this.clock.now();
279
- const row = {
280
- handle: randomUUID(),
281
- redirectUri: rec.redirectUri,
282
- codeChallenge: rec.codeChallenge,
283
- tokenPolicy: structuredClone(rec.tokenPolicy),
284
- createdAt: now,
285
- expiresAt: now + this.ttlMs,
286
- ...rec.clientId !== void 0 && { clientId: rec.clientId },
287
- ...rec.clientState !== void 0 && { clientState: rec.clientState },
288
- ...rec.scope !== void 0 && { scope: rec.scope },
289
- ...rec.nonce !== void 0 && { nonce: rec.nonce },
290
- ...rec.idToken !== void 0 && { idToken: rec.idToken },
291
- ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
292
- ...rec.audience !== void 0 && { audience: rec.audience }
293
- };
294
- this.store.set(row.handle, structuredClone(row));
295
- return { handle: row.handle };
296
- }
297
- async get(handle) {
298
- const row = this.store.get(handle);
299
- if (!row) return null;
300
- if (row.expiresAt <= this.clock.now()) {
301
- this.store.delete(handle);
302
- return null;
303
- }
304
- return structuredClone(row);
305
- }
306
- async delete(handle) {
307
- return this.store.delete(handle);
308
- }
309
- };
310
- //#endregion
311
- //#region src/authz/auth-code-store.ts
312
- /**
313
- * Storage seam for issued authorization codes (AUTH-SERVER.md §4.3). Very
314
- * short-lived (≈ 30–60 s) and **single-use**: {@link consume} returns the row
315
- * AND invalidates it in one atomic step, so a concurrent double-redeem (or a
316
- * back-button replay) yields the code to exactly one caller. An in-memory impl
317
- * ships (atomic for free in single-threaded JS); a durable impl must implement
318
- * `consume` as an atomic check-and-delete (e.g. `withCas` / `@db.column.version`,
319
- * or a Redis `GETDEL`).
320
- */
321
- var AuthCodeStore = class {};
322
- const DEFAULT_CODE_TTL_MS = 6e4;
323
- /**
324
- * In-memory {@link AuthCodeStore} — the reference impl. `consume` is atomic
325
- * because the `get` + `delete` run with no intervening `await`, so a second
326
- * concurrent `consume` of the same code always misses.
327
- */
328
- var AuthCodeStoreMemory = class extends AuthCodeStore {
329
- store = /* @__PURE__ */ new Map();
330
- clock;
331
- ttlMs;
332
- constructor(opts) {
333
- super();
334
- this.clock = opts?.clock ?? defaultClock;
335
- this.ttlMs = opts?.ttlMs ?? DEFAULT_CODE_TTL_MS;
336
- }
337
- async mint(rec) {
338
- const code = randomUUID();
339
- const row = {
340
- code,
341
- userId: rec.userId,
342
- codeChallenge: rec.codeChallenge,
343
- redirectUri: rec.redirectUri,
344
- tokenPolicy: structuredClone(rec.tokenPolicy),
345
- expiresAt: this.clock.now() + this.ttlMs,
346
- ...rec.clientId !== void 0 && { clientId: rec.clientId },
347
- ...rec.scope !== void 0 && { scope: rec.scope },
348
- ...rec.nonce !== void 0 && { nonce: rec.nonce },
349
- ...rec.idToken !== void 0 && { idToken: rec.idToken },
350
- ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
351
- ...rec.audience !== void 0 && { audience: rec.audience }
352
- };
353
- this.store.set(code, structuredClone(row));
354
- return { code };
355
- }
356
- async consume(code) {
357
- const row = this.store.get(code);
358
- if (!row) return null;
359
- this.store.delete(code);
360
- if (row.expiresAt <= this.clock.now()) return null;
361
- return structuredClone(row);
362
- }
363
- };
364
- //#endregion
365
255
  export { AuthCodeStore, AuthCodeStoreMemory, AuthorizeError, CompositeClientPolicy, IdTokenSigner, LoopbackClientPolicy, NoopOidcClaimsResolver, OidcClaimsResolver, PendingAuthorizationStore, PendingAuthorizationStoreMemory, RegisteredClientPolicy, isLoopbackRedirectUri, scopeGrants };