@aooth/auth 0.1.9 → 0.1.11

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.
@@ -1,6 +1,140 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_clock = require("./clock-Bl-H3eqE.cjs");
2
3
  const require_payload = require("./payload-BJjvj8AH.cjs");
4
+ const require_auth_code_store = require("./auth-code-store-CmUhRrwG.cjs");
3
5
  let node_crypto = require("node:crypto");
6
+ //#region src/atscript-db/authz-stores.ts
7
+ /**
8
+ * Durable {@link PendingAuthorizationStore}. `create` inserts a row keyed by a
9
+ * fresh opaque `handle`; `get` rejects (and lazily evicts) a past-expiry row;
10
+ * `delete` drops it once consumed. PK is `handle`, so reads/deletes are O(1).
11
+ */
12
+ var PendingAuthorizationStoreAtscriptDb = class extends require_auth_code_store.PendingAuthorizationStore {
13
+ table;
14
+ clock;
15
+ ttlMs;
16
+ constructor(opts) {
17
+ super();
18
+ this.table = opts.table;
19
+ this.clock = opts.clock ?? require_clock.defaultClock;
20
+ this.ttlMs = opts.ttlMs ?? 9e5;
21
+ }
22
+ async create(rec) {
23
+ const now = this.clock.now();
24
+ const handle = (0, node_crypto.randomUUID)();
25
+ await this.table.insertOne({
26
+ handle,
27
+ redirectUri: rec.redirectUri,
28
+ codeChallenge: rec.codeChallenge,
29
+ tokenPolicy: JSON.stringify(rec.tokenPolicy),
30
+ createdAt: now,
31
+ expiresAt: now + this.ttlMs,
32
+ ...rec.clientId !== void 0 && { clientId: rec.clientId },
33
+ ...rec.clientState !== void 0 && { clientState: rec.clientState },
34
+ ...rec.scope !== void 0 && { scope: rec.scope },
35
+ ...rec.nonce !== void 0 && { nonce: rec.nonce },
36
+ ...rec.idToken !== void 0 && { idToken: rec.idToken },
37
+ ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
38
+ ...rec.audience !== void 0 && { audience: rec.audience }
39
+ });
40
+ return { handle };
41
+ }
42
+ async get(handle) {
43
+ const row = await this.table.findOne({ filter: { handle } });
44
+ if (!row) return null;
45
+ if (row.expiresAt <= this.clock.now()) {
46
+ await this.table.deleteOne(handle).catch(() => {});
47
+ return null;
48
+ }
49
+ return rowToPending(row);
50
+ }
51
+ async delete(handle) {
52
+ const { deletedCount } = await this.table.deleteOne(handle);
53
+ return deletedCount > 0;
54
+ }
55
+ };
56
+ function rowToPending(row) {
57
+ const out = {
58
+ handle: row.handle,
59
+ redirectUri: row.redirectUri,
60
+ codeChallenge: row.codeChallenge,
61
+ tokenPolicy: JSON.parse(row.tokenPolicy),
62
+ createdAt: row.createdAt,
63
+ expiresAt: row.expiresAt
64
+ };
65
+ if (row.clientId != null) out.clientId = row.clientId;
66
+ if (row.clientState != null) out.clientState = row.clientState;
67
+ if (row.scope != null) out.scope = row.scope;
68
+ if (row.nonce != null) out.nonce = row.nonce;
69
+ if (row.idToken != null) out.idToken = row.idToken;
70
+ if (row.accessToken != null) out.accessToken = row.accessToken;
71
+ if (row.audience != null) out.audience = row.audience;
72
+ return out;
73
+ }
74
+ /**
75
+ * Durable {@link AuthCodeStore}. `mint` inserts a single-use code; `consume`
76
+ * is an **atomic check-and-delete**: it reads the row, then `deleteOne(code)`
77
+ * — and only the caller whose delete reports `deletedCount === 1` wins the row.
78
+ * Because an auth-code row is immutable once minted, the value read before the
79
+ * delete is exactly what the winner returns; a concurrent double-redeem or a
80
+ * back-button replay loses the race and gets `null`. (The delete is the claim,
81
+ * so no version column is needed — `deleteOne(PK)` is atomic per the DB engine.)
82
+ */
83
+ var AuthCodeStoreAtscriptDb = class extends require_auth_code_store.AuthCodeStore {
84
+ table;
85
+ clock;
86
+ ttlMs;
87
+ constructor(opts) {
88
+ super();
89
+ this.table = opts.table;
90
+ this.clock = opts.clock ?? require_clock.defaultClock;
91
+ this.ttlMs = opts.ttlMs ?? 6e4;
92
+ }
93
+ async mint(rec) {
94
+ const code = (0, node_crypto.randomUUID)();
95
+ await this.table.insertOne({
96
+ code,
97
+ userId: rec.userId,
98
+ codeChallenge: rec.codeChallenge,
99
+ redirectUri: rec.redirectUri,
100
+ tokenPolicy: JSON.stringify(rec.tokenPolicy),
101
+ expiresAt: this.clock.now() + this.ttlMs,
102
+ ...rec.clientId !== void 0 && { clientId: rec.clientId },
103
+ ...rec.scope !== void 0 && { scope: rec.scope },
104
+ ...rec.nonce !== void 0 && { nonce: rec.nonce },
105
+ ...rec.idToken !== void 0 && { idToken: rec.idToken },
106
+ ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
107
+ ...rec.audience !== void 0 && { audience: rec.audience }
108
+ });
109
+ return { code };
110
+ }
111
+ async consume(code) {
112
+ const row = await this.table.findOne({ filter: { code } });
113
+ if (!row) return null;
114
+ const { deletedCount } = await this.table.deleteOne(code);
115
+ if (deletedCount !== 1) return null;
116
+ if (row.expiresAt <= this.clock.now()) return null;
117
+ return rowToAuthCode(row);
118
+ }
119
+ };
120
+ function rowToAuthCode(row) {
121
+ const out = {
122
+ code: row.code,
123
+ userId: row.userId,
124
+ codeChallenge: row.codeChallenge,
125
+ redirectUri: row.redirectUri,
126
+ tokenPolicy: JSON.parse(row.tokenPolicy),
127
+ expiresAt: row.expiresAt
128
+ };
129
+ if (row.clientId != null) out.clientId = row.clientId;
130
+ if (row.scope != null) out.scope = row.scope;
131
+ if (row.nonce != null) out.nonce = row.nonce;
132
+ if (row.idToken != null) out.idToken = row.idToken;
133
+ if (row.accessToken != null) out.accessToken = row.accessToken;
134
+ if (row.audience != null) out.audience = row.audience;
135
+ return out;
136
+ }
137
+ //#endregion
4
138
  //#region src/atscript-db/index.ts
5
139
  /**
6
140
  * atscript-db-backed `CredentialStore`.
@@ -121,4 +255,6 @@ function rowToState(row) {
121
255
  return state;
122
256
  }
123
257
  //#endregion
258
+ exports.AuthCodeStoreAtscriptDb = AuthCodeStoreAtscriptDb;
124
259
  exports.CredentialStoreAtscriptDb = CredentialStoreAtscriptDb;
260
+ exports.PendingAuthorizationStoreAtscriptDb = PendingAuthorizationStoreAtscriptDb;
@@ -1,5 +1,133 @@
1
1
  import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.cjs";
2
+ import { t as Clock } from "./clock-BjXa0LXb.cjs";
3
+ import { a as NewAuthCode, c as PendingAuthorizationStore, n as AuthCodeStore, o as NewPendingAuthorization, s as PendingAuthorization, t as AuthCode } from "./auth-code-store-HOHlz_3z.cjs";
2
4
 
5
+ //#region src/atscript-db/authz-stores.d.ts
6
+ /**
7
+ * atscript-db-backed durable implementations of the two short-lived
8
+ * authorization-server stores (AUTH-SERVER.md §4.3) — the multi-pod replacement
9
+ * for the in-memory reference impls. They slot under the same DI tokens
10
+ * (`PENDING_AUTHORIZATION_STORE_TOKEN` / `AUTH_CODE_STORE_TOKEN`) so a deployment
11
+ * swaps memory → durable with zero controller change.
12
+ *
13
+ * Row types are re-declared as plain TS (mirroring the `.as` models
14
+ * `AoothPendingAuthorization` / `AoothAuthCode`), so `@aooth/auth` needs no
15
+ * build-time `@atscript/db` dependency. `tokenPolicy` is persisted as a JSON
16
+ * STRING — its `payload` is an open `Record<string, unknown>` that a closed
17
+ * `@db.json` schema would reject — and (de)serialized at this boundary.
18
+ */
19
+ /** Persisted row — mirrors `AoothPendingAuthorization` (`pending-authorization.as`). */
20
+ interface PendingAuthorizationRow {
21
+ handle: string;
22
+ redirectUri: string;
23
+ codeChallenge: string;
24
+ clientId?: string;
25
+ clientState?: string;
26
+ scope?: string;
27
+ nonce?: string;
28
+ idToken?: boolean;
29
+ accessToken?: boolean;
30
+ audience?: string;
31
+ /** `JSON.stringify(TokenPolicy)`. */
32
+ tokenPolicy: string;
33
+ createdAt: number;
34
+ expiresAt: number;
35
+ }
36
+ /**
37
+ * Structural surface of `AtscriptDbTable` covering exactly the methods this
38
+ * adapter calls — kept loose so `@atscript/db` types stay out of the public
39
+ * surface (consumers pass `db.getTable(AoothPendingAuthorization)` and TS matches
40
+ * by-shape, same seam as `AuthCredentialTable`).
41
+ */
42
+ interface PendingAuthorizationTable {
43
+ insertOne(row: PendingAuthorizationRow): Promise<{
44
+ insertedId: unknown;
45
+ }>;
46
+ findOne(query: {
47
+ filter: Record<string, unknown>;
48
+ }): Promise<PendingAuthorizationRow | null>;
49
+ deleteOne(idOrPk: unknown): Promise<{
50
+ deletedCount: number;
51
+ }>;
52
+ }
53
+ interface PendingAuthorizationStoreAtscriptDbOptions {
54
+ table: PendingAuthorizationTable;
55
+ /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
56
+ clock?: Clock;
57
+ /** How long a pending authorization stays valid. Default 15 min. */
58
+ ttlMs?: number;
59
+ }
60
+ /**
61
+ * Durable {@link PendingAuthorizationStore}. `create` inserts a row keyed by a
62
+ * fresh opaque `handle`; `get` rejects (and lazily evicts) a past-expiry row;
63
+ * `delete` drops it once consumed. PK is `handle`, so reads/deletes are O(1).
64
+ */
65
+ declare class PendingAuthorizationStoreAtscriptDb extends PendingAuthorizationStore {
66
+ private readonly table;
67
+ private readonly clock;
68
+ private readonly ttlMs;
69
+ constructor(opts: PendingAuthorizationStoreAtscriptDbOptions);
70
+ create(rec: NewPendingAuthorization): Promise<{
71
+ handle: string;
72
+ }>;
73
+ get(handle: string): Promise<PendingAuthorization | null>;
74
+ delete(handle: string): Promise<boolean>;
75
+ }
76
+ /** Persisted row — mirrors `AoothAuthCode` (`auth-code.as`). */
77
+ interface AuthCodeRow {
78
+ code: string;
79
+ userId: string;
80
+ codeChallenge: string;
81
+ redirectUri: string;
82
+ clientId?: string;
83
+ scope?: string;
84
+ nonce?: string;
85
+ idToken?: boolean;
86
+ accessToken?: boolean;
87
+ audience?: string;
88
+ /** `JSON.stringify(TokenPolicy)`. */
89
+ tokenPolicy: string;
90
+ expiresAt: number;
91
+ }
92
+ /** Structural surface of `AtscriptDbTable` for the auth-code adapter. */
93
+ interface AuthCodeTable {
94
+ insertOne(row: AuthCodeRow): Promise<{
95
+ insertedId: unknown;
96
+ }>;
97
+ findOne(query: {
98
+ filter: Record<string, unknown>;
99
+ }): Promise<AuthCodeRow | null>;
100
+ deleteOne(idOrPk: unknown): Promise<{
101
+ deletedCount: number;
102
+ }>;
103
+ }
104
+ interface AuthCodeStoreAtscriptDbOptions {
105
+ table: AuthCodeTable;
106
+ /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
107
+ clock?: Clock;
108
+ /** Code lifetime. Default 60 s. */
109
+ ttlMs?: number;
110
+ }
111
+ /**
112
+ * Durable {@link AuthCodeStore}. `mint` inserts a single-use code; `consume`
113
+ * is an **atomic check-and-delete**: it reads the row, then `deleteOne(code)`
114
+ * — and only the caller whose delete reports `deletedCount === 1` wins the row.
115
+ * Because an auth-code row is immutable once minted, the value read before the
116
+ * delete is exactly what the winner returns; a concurrent double-redeem or a
117
+ * back-button replay loses the race and gets `null`. (The delete is the claim,
118
+ * so no version column is needed — `deleteOne(PK)` is atomic per the DB engine.)
119
+ */
120
+ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
121
+ private readonly table;
122
+ private readonly clock;
123
+ private readonly ttlMs;
124
+ constructor(opts: AuthCodeStoreAtscriptDbOptions);
125
+ mint(rec: NewAuthCode): Promise<{
126
+ code: string;
127
+ }>;
128
+ consume(code: string): Promise<AuthCode | null>;
129
+ }
130
+ //#endregion
3
131
  //#region src/atscript-db/index.d.ts
4
132
  /**
5
133
  * Persisted row shape — mirrors `AoothAuthCredential` from
@@ -88,4 +216,4 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
88
216
  }>>;
89
217
  }
90
218
  //#endregion
91
- export { AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb };
219
+ export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };
@@ -1,5 +1,133 @@
1
1
  import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.mjs";
2
+ import { t as Clock } from "./clock-BjXa0LXb.mjs";
3
+ import { a as NewAuthCode, c as PendingAuthorizationStore, n as AuthCodeStore, o as NewPendingAuthorization, s as PendingAuthorization, t as AuthCode } from "./auth-code-store-NqEDfqrf.mjs";
2
4
 
5
+ //#region src/atscript-db/authz-stores.d.ts
6
+ /**
7
+ * atscript-db-backed durable implementations of the two short-lived
8
+ * authorization-server stores (AUTH-SERVER.md §4.3) — the multi-pod replacement
9
+ * for the in-memory reference impls. They slot under the same DI tokens
10
+ * (`PENDING_AUTHORIZATION_STORE_TOKEN` / `AUTH_CODE_STORE_TOKEN`) so a deployment
11
+ * swaps memory → durable with zero controller change.
12
+ *
13
+ * Row types are re-declared as plain TS (mirroring the `.as` models
14
+ * `AoothPendingAuthorization` / `AoothAuthCode`), so `@aooth/auth` needs no
15
+ * build-time `@atscript/db` dependency. `tokenPolicy` is persisted as a JSON
16
+ * STRING — its `payload` is an open `Record<string, unknown>` that a closed
17
+ * `@db.json` schema would reject — and (de)serialized at this boundary.
18
+ */
19
+ /** Persisted row — mirrors `AoothPendingAuthorization` (`pending-authorization.as`). */
20
+ interface PendingAuthorizationRow {
21
+ handle: string;
22
+ redirectUri: string;
23
+ codeChallenge: string;
24
+ clientId?: string;
25
+ clientState?: string;
26
+ scope?: string;
27
+ nonce?: string;
28
+ idToken?: boolean;
29
+ accessToken?: boolean;
30
+ audience?: string;
31
+ /** `JSON.stringify(TokenPolicy)`. */
32
+ tokenPolicy: string;
33
+ createdAt: number;
34
+ expiresAt: number;
35
+ }
36
+ /**
37
+ * Structural surface of `AtscriptDbTable` covering exactly the methods this
38
+ * adapter calls — kept loose so `@atscript/db` types stay out of the public
39
+ * surface (consumers pass `db.getTable(AoothPendingAuthorization)` and TS matches
40
+ * by-shape, same seam as `AuthCredentialTable`).
41
+ */
42
+ interface PendingAuthorizationTable {
43
+ insertOne(row: PendingAuthorizationRow): Promise<{
44
+ insertedId: unknown;
45
+ }>;
46
+ findOne(query: {
47
+ filter: Record<string, unknown>;
48
+ }): Promise<PendingAuthorizationRow | null>;
49
+ deleteOne(idOrPk: unknown): Promise<{
50
+ deletedCount: number;
51
+ }>;
52
+ }
53
+ interface PendingAuthorizationStoreAtscriptDbOptions {
54
+ table: PendingAuthorizationTable;
55
+ /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
56
+ clock?: Clock;
57
+ /** How long a pending authorization stays valid. Default 15 min. */
58
+ ttlMs?: number;
59
+ }
60
+ /**
61
+ * Durable {@link PendingAuthorizationStore}. `create` inserts a row keyed by a
62
+ * fresh opaque `handle`; `get` rejects (and lazily evicts) a past-expiry row;
63
+ * `delete` drops it once consumed. PK is `handle`, so reads/deletes are O(1).
64
+ */
65
+ declare class PendingAuthorizationStoreAtscriptDb extends PendingAuthorizationStore {
66
+ private readonly table;
67
+ private readonly clock;
68
+ private readonly ttlMs;
69
+ constructor(opts: PendingAuthorizationStoreAtscriptDbOptions);
70
+ create(rec: NewPendingAuthorization): Promise<{
71
+ handle: string;
72
+ }>;
73
+ get(handle: string): Promise<PendingAuthorization | null>;
74
+ delete(handle: string): Promise<boolean>;
75
+ }
76
+ /** Persisted row — mirrors `AoothAuthCode` (`auth-code.as`). */
77
+ interface AuthCodeRow {
78
+ code: string;
79
+ userId: string;
80
+ codeChallenge: string;
81
+ redirectUri: string;
82
+ clientId?: string;
83
+ scope?: string;
84
+ nonce?: string;
85
+ idToken?: boolean;
86
+ accessToken?: boolean;
87
+ audience?: string;
88
+ /** `JSON.stringify(TokenPolicy)`. */
89
+ tokenPolicy: string;
90
+ expiresAt: number;
91
+ }
92
+ /** Structural surface of `AtscriptDbTable` for the auth-code adapter. */
93
+ interface AuthCodeTable {
94
+ insertOne(row: AuthCodeRow): Promise<{
95
+ insertedId: unknown;
96
+ }>;
97
+ findOne(query: {
98
+ filter: Record<string, unknown>;
99
+ }): Promise<AuthCodeRow | null>;
100
+ deleteOne(idOrPk: unknown): Promise<{
101
+ deletedCount: number;
102
+ }>;
103
+ }
104
+ interface AuthCodeStoreAtscriptDbOptions {
105
+ table: AuthCodeTable;
106
+ /** Injectable clock for deterministic expiry. Defaults to {@link defaultClock}. */
107
+ clock?: Clock;
108
+ /** Code lifetime. Default 60 s. */
109
+ ttlMs?: number;
110
+ }
111
+ /**
112
+ * Durable {@link AuthCodeStore}. `mint` inserts a single-use code; `consume`
113
+ * is an **atomic check-and-delete**: it reads the row, then `deleteOne(code)`
114
+ * — and only the caller whose delete reports `deletedCount === 1` wins the row.
115
+ * Because an auth-code row is immutable once minted, the value read before the
116
+ * delete is exactly what the winner returns; a concurrent double-redeem or a
117
+ * back-button replay loses the race and gets `null`. (The delete is the claim,
118
+ * so no version column is needed — `deleteOne(PK)` is atomic per the DB engine.)
119
+ */
120
+ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
121
+ private readonly table;
122
+ private readonly clock;
123
+ private readonly ttlMs;
124
+ constructor(opts: AuthCodeStoreAtscriptDbOptions);
125
+ mint(rec: NewAuthCode): Promise<{
126
+ code: string;
127
+ }>;
128
+ consume(code: string): Promise<AuthCode | null>;
129
+ }
130
+ //#endregion
3
131
  //#region src/atscript-db/index.d.ts
4
132
  /**
5
133
  * Persisted row shape — mirrors `AoothAuthCredential` from
@@ -88,4 +216,4 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
88
216
  }>>;
89
217
  }
90
218
  //#endregion
91
- export { AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb };
219
+ export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };
@@ -1,5 +1,139 @@
1
+ import { t as defaultClock } from "./clock-Bdsep_1j.mjs";
1
2
  import { t as credentialPayloadOf } from "./payload-D-DzH5-J.mjs";
3
+ import { a as PendingAuthorizationStore, t as AuthCodeStore } from "./auth-code-store-uaoL7dvU.mjs";
2
4
  import { randomUUID } from "node:crypto";
5
+ //#region src/atscript-db/authz-stores.ts
6
+ /**
7
+ * Durable {@link PendingAuthorizationStore}. `create` inserts a row keyed by a
8
+ * fresh opaque `handle`; `get` rejects (and lazily evicts) a past-expiry row;
9
+ * `delete` drops it once consumed. PK is `handle`, so reads/deletes are O(1).
10
+ */
11
+ var PendingAuthorizationStoreAtscriptDb = class extends PendingAuthorizationStore {
12
+ table;
13
+ clock;
14
+ ttlMs;
15
+ constructor(opts) {
16
+ super();
17
+ this.table = opts.table;
18
+ this.clock = opts.clock ?? defaultClock;
19
+ this.ttlMs = opts.ttlMs ?? 9e5;
20
+ }
21
+ async create(rec) {
22
+ const now = this.clock.now();
23
+ const handle = randomUUID();
24
+ await this.table.insertOne({
25
+ handle,
26
+ redirectUri: rec.redirectUri,
27
+ codeChallenge: rec.codeChallenge,
28
+ tokenPolicy: JSON.stringify(rec.tokenPolicy),
29
+ createdAt: now,
30
+ expiresAt: now + this.ttlMs,
31
+ ...rec.clientId !== void 0 && { clientId: rec.clientId },
32
+ ...rec.clientState !== void 0 && { clientState: rec.clientState },
33
+ ...rec.scope !== void 0 && { scope: rec.scope },
34
+ ...rec.nonce !== void 0 && { nonce: rec.nonce },
35
+ ...rec.idToken !== void 0 && { idToken: rec.idToken },
36
+ ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
37
+ ...rec.audience !== void 0 && { audience: rec.audience }
38
+ });
39
+ return { handle };
40
+ }
41
+ async get(handle) {
42
+ const row = await this.table.findOne({ filter: { handle } });
43
+ if (!row) return null;
44
+ if (row.expiresAt <= this.clock.now()) {
45
+ await this.table.deleteOne(handle).catch(() => {});
46
+ return null;
47
+ }
48
+ return rowToPending(row);
49
+ }
50
+ async delete(handle) {
51
+ const { deletedCount } = await this.table.deleteOne(handle);
52
+ return deletedCount > 0;
53
+ }
54
+ };
55
+ function rowToPending(row) {
56
+ const out = {
57
+ handle: row.handle,
58
+ redirectUri: row.redirectUri,
59
+ codeChallenge: row.codeChallenge,
60
+ tokenPolicy: JSON.parse(row.tokenPolicy),
61
+ createdAt: row.createdAt,
62
+ expiresAt: row.expiresAt
63
+ };
64
+ if (row.clientId != null) out.clientId = row.clientId;
65
+ if (row.clientState != null) out.clientState = row.clientState;
66
+ if (row.scope != null) out.scope = row.scope;
67
+ if (row.nonce != null) out.nonce = row.nonce;
68
+ if (row.idToken != null) out.idToken = row.idToken;
69
+ if (row.accessToken != null) out.accessToken = row.accessToken;
70
+ if (row.audience != null) out.audience = row.audience;
71
+ return out;
72
+ }
73
+ /**
74
+ * Durable {@link AuthCodeStore}. `mint` inserts a single-use code; `consume`
75
+ * is an **atomic check-and-delete**: it reads the row, then `deleteOne(code)`
76
+ * — and only the caller whose delete reports `deletedCount === 1` wins the row.
77
+ * Because an auth-code row is immutable once minted, the value read before the
78
+ * delete is exactly what the winner returns; a concurrent double-redeem or a
79
+ * back-button replay loses the race and gets `null`. (The delete is the claim,
80
+ * so no version column is needed — `deleteOne(PK)` is atomic per the DB engine.)
81
+ */
82
+ var AuthCodeStoreAtscriptDb = class extends AuthCodeStore {
83
+ table;
84
+ clock;
85
+ ttlMs;
86
+ constructor(opts) {
87
+ super();
88
+ this.table = opts.table;
89
+ this.clock = opts.clock ?? defaultClock;
90
+ this.ttlMs = opts.ttlMs ?? 6e4;
91
+ }
92
+ async mint(rec) {
93
+ const code = randomUUID();
94
+ await this.table.insertOne({
95
+ code,
96
+ userId: rec.userId,
97
+ codeChallenge: rec.codeChallenge,
98
+ redirectUri: rec.redirectUri,
99
+ tokenPolicy: JSON.stringify(rec.tokenPolicy),
100
+ expiresAt: this.clock.now() + this.ttlMs,
101
+ ...rec.clientId !== void 0 && { clientId: rec.clientId },
102
+ ...rec.scope !== void 0 && { scope: rec.scope },
103
+ ...rec.nonce !== void 0 && { nonce: rec.nonce },
104
+ ...rec.idToken !== void 0 && { idToken: rec.idToken },
105
+ ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
106
+ ...rec.audience !== void 0 && { audience: rec.audience }
107
+ });
108
+ return { code };
109
+ }
110
+ async consume(code) {
111
+ const row = await this.table.findOne({ filter: { code } });
112
+ if (!row) return null;
113
+ const { deletedCount } = await this.table.deleteOne(code);
114
+ if (deletedCount !== 1) return null;
115
+ if (row.expiresAt <= this.clock.now()) return null;
116
+ return rowToAuthCode(row);
117
+ }
118
+ };
119
+ function rowToAuthCode(row) {
120
+ const out = {
121
+ code: row.code,
122
+ userId: row.userId,
123
+ codeChallenge: row.codeChallenge,
124
+ redirectUri: row.redirectUri,
125
+ tokenPolicy: JSON.parse(row.tokenPolicy),
126
+ expiresAt: row.expiresAt
127
+ };
128
+ if (row.clientId != null) out.clientId = row.clientId;
129
+ if (row.scope != null) out.scope = row.scope;
130
+ if (row.nonce != null) out.nonce = row.nonce;
131
+ if (row.idToken != null) out.idToken = row.idToken;
132
+ if (row.accessToken != null) out.accessToken = row.accessToken;
133
+ if (row.audience != null) out.audience = row.audience;
134
+ return out;
135
+ }
136
+ //#endregion
3
137
  //#region src/atscript-db/index.ts
4
138
  /**
5
139
  * atscript-db-backed `CredentialStore`.
@@ -120,4 +254,4 @@ function rowToState(row) {
120
254
  return state;
121
255
  }
122
256
  //#endregion
123
- export { CredentialStoreAtscriptDb };
257
+ export { AuthCodeStoreAtscriptDb, CredentialStoreAtscriptDb, PendingAuthorizationStoreAtscriptDb };