@julr/sesame 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +470 -64
  3. package/build/authorize_controller-sCZIjnc-.js +251 -0
  4. package/build/{client_info_controller-BucHGx4u.js → client_info_controller-B5A3tpbk.js} +11 -4
  5. package/build/commands/sesame_client.d.ts +20 -0
  6. package/build/commands/sesame_key.d.ts +12 -0
  7. package/build/commands/sesame_purge.d.ts +0 -2
  8. package/build/commands/sesame_purge.js +12 -3
  9. package/build/configure-t9q3KonY.js +31 -0
  10. package/build/configure.js +1 -24
  11. package/build/consent_controller-ChBboaSd.js +96 -0
  12. package/build/consent_retry-DMkfhmk9.js +41 -0
  13. package/build/{decorate-BKZEjPRg.js → decorate-RQD1h28J.js} +3 -9
  14. package/build/id_token_service-BtBUiL_n.js +54 -0
  15. package/build/index.d.ts +2 -8
  16. package/build/index.js +59 -10
  17. package/build/{introspect_controller-6bRt9sZt.js → introspect_controller-LVYGWhMj.js} +28 -10
  18. package/build/issue_authorization_code-Dr4-bbMp.js +39 -0
  19. package/build/jwks_controller-4HUVS_HC.js +22 -0
  20. package/build/{main-EbeMS5S9.js → main-Dx9kZv07.js} +35 -43
  21. package/build/{metadata_controller-DeaMRnUr.js → metadata_controller-hwfmxlF7.js} +81 -6
  22. package/build/oauth_error-CvxtZvjp.js +189 -0
  23. package/build/providers/sesame_provider.d.ts +1 -1
  24. package/build/providers/sesame_provider.js +17 -6
  25. package/build/{register_controller-sIJ1rxdM.js → register_controller-Cdk3c32j.js} +44 -8
  26. package/build/revoke_controller-Daz4Yqfs.js +48 -0
  27. package/build/services/main.js +3 -3
  28. package/build/sesame_manager-CeEmlzba.js +515 -0
  29. package/build/sesame_manager-DQxvsTEq.js +2 -0
  30. package/build/src/actions/authorize.d.ts +46 -0
  31. package/build/src/actions/exchange_authorization_code.d.ts +34 -0
  32. package/build/src/actions/exchange_client_credentials.d.ts +28 -0
  33. package/build/src/actions/exchange_refresh_token.d.ts +59 -0
  34. package/build/src/actions/issue_authorization_code.d.ts +26 -0
  35. package/build/src/controllers/authorize_controller.d.ts +18 -17
  36. package/build/src/controllers/consent_controller.d.ts +5 -0
  37. package/build/src/controllers/jwks_controller.d.ts +14 -0
  38. package/build/src/controllers/metadata_controller.d.ts +10 -3
  39. package/build/src/controllers/register_controller.d.ts +30 -30
  40. package/build/src/controllers/token_controller.d.ts +8 -5
  41. package/build/src/controllers/userinfo_controller.d.ts +14 -0
  42. package/build/src/guard/guard.d.ts +1 -1
  43. package/build/src/guard/lucid.d.ts +8 -0
  44. package/build/src/guard/lucid.js +46 -0
  45. package/build/src/guard/main.d.ts +2 -9
  46. package/build/src/guard/main.js +2 -6
  47. package/build/src/guard/types.d.ts +0 -9
  48. package/build/src/guard/user_provider.d.ts +9 -1
  49. package/build/src/middleware/any_scope_middleware.js +10 -1
  50. package/build/src/middleware/scope_middleware.js +10 -1
  51. package/build/src/models/oauth_authorization_code.d.ts +1 -0
  52. package/build/src/models/oauth_pending_authorization_request.d.ts +1 -0
  53. package/build/src/oauth_error.d.ts +74 -74
  54. package/build/src/routes.d.ts +3 -1
  55. package/build/src/services/client_service.d.ts +5 -2
  56. package/build/src/services/id_token_service.d.ts +30 -0
  57. package/build/src/services/key_service.d.ts +20 -0
  58. package/build/src/sesame_manager.d.ts +56 -4
  59. package/build/src/storage/consent_retry.d.ts +4 -0
  60. package/build/src/storage/drivers/kysely.d.ts +166 -0
  61. package/build/src/storage/drivers/kysely.js +480 -0
  62. package/build/src/storage/drivers/lucid.d.ts +162 -0
  63. package/build/src/storage/drivers/lucid.js +549 -0
  64. package/build/src/storage/migrations/kysely.d.ts +6 -0
  65. package/build/src/storage/types.d.ts +197 -0
  66. package/build/src/storage/types.js +1 -0
  67. package/build/src/stores.d.ts +21 -0
  68. package/build/src/types.d.ts +120 -0
  69. package/build/src/types.js +2 -0
  70. package/build/stubs/config/sesame.stub +4 -1
  71. package/build/stubs/config/sesame_kysely.stub +35 -0
  72. package/build/stubs/main.ts +5 -0
  73. package/build/stubs/migrations/create_oauth_authorization_codes_table.stub +1 -0
  74. package/build/stubs/migrations/create_oauth_pending_authorization_requests_table.stub +1 -0
  75. package/build/stubs/migrations/create_oauth_refresh_tokens_table.stub +1 -1
  76. package/build/stubs/migrations/kysely/create_oauth_tables.stub +151 -0
  77. package/build/taze.config.d.ts +2 -0
  78. package/build/token_controller-DRatCQNT.js +492 -0
  79. package/build/token_service-DDQ3Dxaj.js +59 -0
  80. package/build/types-DEtn2Zdb.js +75 -0
  81. package/build/userinfo_controller-YwN26wK-.js +37 -0
  82. package/build/vite.config.d.ts +2 -0
  83. package/package.json +55 -54
  84. package/build/authorize_controller-YUfAy-R2.js +0 -138
  85. package/build/client_service-WTNMqWzY.js +0 -65
  86. package/build/consent_controller-Dprwd1ed.js +0 -85
  87. package/build/oauth_access_token-bsoM5KeU.js +0 -18
  88. package/build/oauth_client-BIoY5jBR.js +0 -24
  89. package/build/oauth_error-CnJ3L8tf.js +0 -94
  90. package/build/revoke_controller-D6isoQCi.js +0 -41
  91. package/build/sesame_manager-Bu4MHqZV.js +0 -4
  92. package/build/sesame_manager-DwDZy5Vy.js +0 -167
  93. package/build/src/grants/authorization_code_grant.d.ts +0 -23
  94. package/build/src/grants/client_credentials_grant.d.ts +0 -23
  95. package/build/src/grants/refresh_token_grant.d.ts +0 -27
  96. package/build/token_controller-DzcrLMyS.js +0 -194
  97. package/build/token_service-fhoA4slP.js +0 -31
@@ -0,0 +1,549 @@
1
+ import { t as retryConsentConflict } from "../../../consent_retry-DMkfhmk9.js";
2
+ import { t as __decorate } from "../../../decorate-RQD1h28J.js";
3
+ import { DateTime } from "luxon";
4
+ import { BaseModel, column } from "@adonisjs/lucid/orm";
5
+ //#region src/decorators.ts
6
+ /**
7
+ * Column decorator for JSON values (arrays, objects).
8
+ *
9
+ * Lucid ORM doesn't handle JSON serialization automatically —
10
+ * `prepare` stringifies before INSERT/UPDATE, `consume` parses
11
+ * after SELECT. The typeof check in consume handles both drivers
12
+ * that return strings (SQLite, MySQL) and parsed objects (PostgreSQL JSONB).
13
+ */
14
+ function json(options) {
15
+ return column({
16
+ ...options,
17
+ prepare: (value) => value == null ? null : JSON.stringify(value),
18
+ consume: (value) => value == null ? null : typeof value === "string" ? JSON.parse(value) : value
19
+ });
20
+ }
21
+ //#endregion
22
+ //#region src/models/oauth_access_token.ts
23
+ /**
24
+ * Database record for an issued OAuth 2.0 access token.
25
+ *
26
+ * Access tokens are opaque random values. Only the SHA-256 hash
27
+ * is stored so raw tokens cannot be reconstructed from a DB leak.
28
+ * A token is considered revoked when `revokedAt` is set.
29
+ *
30
+ * @see https://datatracker.ietf.org/doc/html/rfc7662
31
+ */
32
+ var OAuthAccessToken = class extends BaseModel {
33
+ static table = "oauth_access_tokens";
34
+ };
35
+ __decorate([column({ isPrimary: true })], OAuthAccessToken.prototype, "id", void 0);
36
+ __decorate([column()], OAuthAccessToken.prototype, "tokenHash", void 0);
37
+ __decorate([column()], OAuthAccessToken.prototype, "clientId", void 0);
38
+ __decorate([column()], OAuthAccessToken.prototype, "userId", void 0);
39
+ __decorate([json()], OAuthAccessToken.prototype, "scopes", void 0);
40
+ __decorate([column.dateTime()], OAuthAccessToken.prototype, "expiresAt", void 0);
41
+ __decorate([column.dateTime()], OAuthAccessToken.prototype, "revokedAt", void 0);
42
+ __decorate([column.dateTime({ autoCreate: true })], OAuthAccessToken.prototype, "createdAt", void 0);
43
+ __decorate([column.dateTime({
44
+ autoCreate: true,
45
+ autoUpdate: true
46
+ })], OAuthAccessToken.prototype, "updatedAt", void 0);
47
+ //#endregion
48
+ //#region src/models/oauth_authorization_code.ts
49
+ /**
50
+ * Database record for an OAuth 2.0 authorization code (RFC 6749 §4.1.2).
51
+ *
52
+ * Authorization codes are short-lived, single-use tokens issued
53
+ * during the authorization flow. The `code` column stores the
54
+ * SHA-256 hash of the raw code value sent to the client.
55
+ *
56
+ * When PKCE (RFC 7636) is used, the `codeChallenge` and
57
+ * `codeChallengeMethod` (always S256) are stored alongside the
58
+ * code for verification at the token endpoint.
59
+ *
60
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
61
+ * @see https://datatracker.ietf.org/doc/html/rfc7636
62
+ */
63
+ var OAuthAuthorizationCode = class extends BaseModel {
64
+ static table = "oauth_authorization_codes";
65
+ };
66
+ __decorate([column({ isPrimary: true })], OAuthAuthorizationCode.prototype, "id", void 0);
67
+ __decorate([column()], OAuthAuthorizationCode.prototype, "code", void 0);
68
+ __decorate([column()], OAuthAuthorizationCode.prototype, "clientId", void 0);
69
+ __decorate([column()], OAuthAuthorizationCode.prototype, "userId", void 0);
70
+ __decorate([json()], OAuthAuthorizationCode.prototype, "scopes", void 0);
71
+ __decorate([column()], OAuthAuthorizationCode.prototype, "redirectUri", void 0);
72
+ __decorate([column()], OAuthAuthorizationCode.prototype, "codeChallenge", void 0);
73
+ __decorate([column()], OAuthAuthorizationCode.prototype, "codeChallengeMethod", void 0);
74
+ __decorate([column()], OAuthAuthorizationCode.prototype, "nonce", void 0);
75
+ __decorate([column.dateTime()], OAuthAuthorizationCode.prototype, "expiresAt", void 0);
76
+ __decorate([column.dateTime({ autoCreate: true })], OAuthAuthorizationCode.prototype, "createdAt", void 0);
77
+ __decorate([column.dateTime({
78
+ autoCreate: true,
79
+ autoUpdate: true
80
+ })], OAuthAuthorizationCode.prototype, "updatedAt", void 0);
81
+ //#endregion
82
+ //#region src/models/oauth_client.ts
83
+ /**
84
+ * Represents a registered OAuth 2.0 client (RFC 6749 §2).
85
+ *
86
+ * Clients can be either confidential (with a hashed secret) or
87
+ * public (no secret, e.g. SPAs, native apps). Public clients must
88
+ * use PKCE (RFC 7636) for authorization code exchanges.
89
+ *
90
+ * Clients may be created manually or via dynamic client registration
91
+ * (RFC 7591) through the `/oauth/register` endpoint.
92
+ *
93
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-2
94
+ * @see https://datatracker.ietf.org/doc/html/rfc7591
95
+ */
96
+ var OAuthClient = class extends BaseModel {
97
+ static table = "oauth_clients";
98
+ };
99
+ __decorate([column({ isPrimary: true })], OAuthClient.prototype, "id", void 0);
100
+ __decorate([column()], OAuthClient.prototype, "clientId", void 0);
101
+ __decorate([column({ serializeAs: null })], OAuthClient.prototype, "clientSecret", void 0);
102
+ __decorate([column()], OAuthClient.prototype, "name", void 0);
103
+ __decorate([json()], OAuthClient.prototype, "redirectUris", void 0);
104
+ __decorate([json()], OAuthClient.prototype, "scopes", void 0);
105
+ __decorate([json()], OAuthClient.prototype, "grantTypes", void 0);
106
+ __decorate([column()], OAuthClient.prototype, "isPublic", void 0);
107
+ __decorate([column()], OAuthClient.prototype, "isDisabled", void 0);
108
+ __decorate([column()], OAuthClient.prototype, "requirePkce", void 0);
109
+ __decorate([column()], OAuthClient.prototype, "type", void 0);
110
+ __decorate([json()], OAuthClient.prototype, "metadata", void 0);
111
+ __decorate([column()], OAuthClient.prototype, "userId", void 0);
112
+ __decorate([column.dateTime({ autoCreate: true })], OAuthClient.prototype, "createdAt", void 0);
113
+ __decorate([column.dateTime({
114
+ autoCreate: true,
115
+ autoUpdate: true
116
+ })], OAuthClient.prototype, "updatedAt", void 0);
117
+ //#endregion
118
+ //#region src/models/oauth_consent.ts
119
+ /**
120
+ * Tracks which scopes a user has approved for a given client.
121
+ *
122
+ * When a user grants consent during the authorization flow,
123
+ * the approved scopes are persisted here. On subsequent
124
+ * authorization requests, if the requested scopes are a subset
125
+ * of previously approved scopes, the user is not prompted again.
126
+ *
127
+ * New scopes are merged into the existing record when the user
128
+ * approves additional permissions.
129
+ */
130
+ var OAuthConsent = class extends BaseModel {
131
+ static table = "oauth_consents";
132
+ };
133
+ __decorate([column({ isPrimary: true })], OAuthConsent.prototype, "id", void 0);
134
+ __decorate([column()], OAuthConsent.prototype, "clientId", void 0);
135
+ __decorate([column()], OAuthConsent.prototype, "userId", void 0);
136
+ __decorate([json()], OAuthConsent.prototype, "scopes", void 0);
137
+ __decorate([column.dateTime({ autoCreate: true })], OAuthConsent.prototype, "createdAt", void 0);
138
+ __decorate([column.dateTime({
139
+ autoCreate: true,
140
+ autoUpdate: true
141
+ })], OAuthConsent.prototype, "updatedAt", void 0);
142
+ //#endregion
143
+ //#region src/models/oauth_pending_authorization_request.ts
144
+ /**
145
+ * Temporary server-side record for an in-flight OAuth authorization
146
+ * request, created when the user is redirected to the consent page
147
+ * and consumed (deleted) when the user approves or denies.
148
+ *
149
+ * Stored in the database instead of the HTTP session to avoid
150
+ * last-write-wins race conditions when concurrent SPA requests
151
+ * overwrite session data.
152
+ *
153
+ * The `token` column stores a SHA-256 hash of the raw auth_token
154
+ * sent to the consent page, consistent with how authorization
155
+ * codes are stored.
156
+ */
157
+ var OAuthPendingAuthorizationRequest = class extends BaseModel {
158
+ static table = "oauth_pending_authorization_requests";
159
+ };
160
+ __decorate([column({ isPrimary: true })], OAuthPendingAuthorizationRequest.prototype, "id", void 0);
161
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "token", void 0);
162
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "userId", void 0);
163
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "clientId", void 0);
164
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "redirectUri", void 0);
165
+ __decorate([json()], OAuthPendingAuthorizationRequest.prototype, "scopes", void 0);
166
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "state", void 0);
167
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "codeChallenge", void 0);
168
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "codeChallengeMethod", void 0);
169
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "nonce", void 0);
170
+ __decorate([column.dateTime()], OAuthPendingAuthorizationRequest.prototype, "expiresAt", void 0);
171
+ __decorate([column.dateTime({ autoCreate: true })], OAuthPendingAuthorizationRequest.prototype, "createdAt", void 0);
172
+ //#endregion
173
+ //#region src/models/oauth_refresh_token.ts
174
+ /**
175
+ * Database record for an OAuth 2.0 refresh token (RFC 6749 §6).
176
+ *
177
+ * The `token` column stores the SHA-256 hash of the raw refresh
178
+ * token value. Refresh token rotation is enforced: each use
179
+ * produces a new token and revokes the old one.
180
+ *
181
+ * Replay detection is implemented by checking `revokedAt` —
182
+ * if a revoked token is presented, all tokens for that
183
+ * client+user pair are deleted as a security measure.
184
+ *
185
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-6
186
+ * @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.14.2
187
+ */
188
+ var OAuthRefreshToken = class extends BaseModel {
189
+ static table = "oauth_refresh_tokens";
190
+ };
191
+ __decorate([column({ isPrimary: true })], OAuthRefreshToken.prototype, "id", void 0);
192
+ __decorate([column({ serializeAs: null })], OAuthRefreshToken.prototype, "token", void 0);
193
+ __decorate([column()], OAuthRefreshToken.prototype, "accessTokenId", void 0);
194
+ __decorate([column()], OAuthRefreshToken.prototype, "clientId", void 0);
195
+ __decorate([column()], OAuthRefreshToken.prototype, "userId", void 0);
196
+ __decorate([json()], OAuthRefreshToken.prototype, "scopes", void 0);
197
+ __decorate([column.dateTime()], OAuthRefreshToken.prototype, "expiresAt", void 0);
198
+ __decorate([column.dateTime()], OAuthRefreshToken.prototype, "revokedAt", void 0);
199
+ __decorate([column.dateTime({ autoCreate: true })], OAuthRefreshToken.prototype, "createdAt", void 0);
200
+ __decorate([column.dateTime({
201
+ autoCreate: true,
202
+ autoUpdate: true
203
+ })], OAuthRefreshToken.prototype, "updatedAt", void 0);
204
+ //#endregion
205
+ //#region src/storage/drivers/lucid.ts
206
+ /**
207
+ * Persist OAuth records through the application's Lucid connection.
208
+ */
209
+ var LucidStore = class LucidStore {
210
+ #client;
211
+ /**
212
+ * Bind this store to a Lucid transaction when one is supplied.
213
+ */
214
+ constructor(client) {
215
+ this.#client = client;
216
+ }
217
+ /**
218
+ * Normalize affected-row results across Lucid's SQL drivers.
219
+ */
220
+ #affectedRows(result) {
221
+ if (Array.isArray(result)) return Number(result[0] ?? 0);
222
+ return Number(result);
223
+ }
224
+ /**
225
+ * Bind a model query to this store's transaction when present.
226
+ */
227
+ #query(model) {
228
+ return model.query(this.#client ? { client: this.#client } : void 0);
229
+ }
230
+ /**
231
+ * Return a plain record with omitted nullable columns normalized.
232
+ */
233
+ #record(model, nullableFields = []) {
234
+ const attributes = { ...model.$attributes };
235
+ for (const field of nullableFields) if (attributes[field] === void 0) attributes[field] = null;
236
+ return attributes;
237
+ }
238
+ /**
239
+ * Normalize Lucid client values to the store's plain-record contract.
240
+ */
241
+ #clientRecord(client) {
242
+ const record = this.#record(client, [
243
+ "clientSecret",
244
+ "metadata",
245
+ "type",
246
+ "userId"
247
+ ]);
248
+ return {
249
+ ...record,
250
+ isPublic: Boolean(record.isPublic),
251
+ isDisabled: Boolean(record.isDisabled),
252
+ requirePkce: Boolean(record.requirePkce)
253
+ };
254
+ }
255
+ /**
256
+ * Execute related writes in one real database transaction.
257
+ */
258
+ async #transaction(callback) {
259
+ return OAuthClient.transaction(async (client) => callback(new LucidStore(client)));
260
+ }
261
+ /**
262
+ * Find a registered client by its public identifier.
263
+ */
264
+ async findClient(clientId) {
265
+ const client = await this.#query(OAuthClient).where("clientId", clientId).first();
266
+ return client ? this.#clientRecord(client) : null;
267
+ }
268
+ /**
269
+ * List clients newest first, optionally for a single owner.
270
+ */
271
+ async listClients(options) {
272
+ const query = this.#query(OAuthClient).orderBy("createdAt", "desc");
273
+ if (options?.userId) query.where("userId", options.userId);
274
+ return (await query).map((client) => this.#clientRecord(client));
275
+ }
276
+ /**
277
+ * Insert a client and return its stored record.
278
+ */
279
+ async createClient(data) {
280
+ await OAuthClient.create(data, this.#client ? { client: this.#client } : void 0);
281
+ const client = await this.findClient(data.clientId);
282
+ if (!client) throw new Error("Failed to reload inserted OAuth client");
283
+ return client;
284
+ }
285
+ /**
286
+ * Update only the supplied public client fields.
287
+ */
288
+ async updateClient(options) {
289
+ const values = {};
290
+ const { data } = options;
291
+ if (data.name !== void 0) values.name = data.name;
292
+ if (data.redirectUris !== void 0) values.redirectUris = JSON.stringify(data.redirectUris);
293
+ if (data.scopes !== void 0) values.scopes = JSON.stringify(data.scopes);
294
+ if (data.grantTypes !== void 0) values.grantTypes = JSON.stringify(data.grantTypes);
295
+ if (data.isDisabled !== void 0) values.isDisabled = data.isDisabled;
296
+ if (data.requirePkce !== void 0) values.requirePkce = data.requirePkce;
297
+ if (data.metadata !== void 0) values.metadata = data.metadata === null ? null : JSON.stringify(data.metadata);
298
+ if (Object.keys(values).length === 0) return;
299
+ values.updatedAt = DateTime.now().toSQL();
300
+ await this.#query(OAuthClient).where("id", options.id).update(values);
301
+ }
302
+ /**
303
+ * Replace a confidential client's stored secret hash.
304
+ */
305
+ async updateClientSecret(options) {
306
+ await this.#query(OAuthClient).where("id", options.id).update({
307
+ clientSecret: options.secret,
308
+ updatedAt: DateTime.now().toSQL()
309
+ });
310
+ }
311
+ /**
312
+ * Delete a client and all its associated OAuth records atomically.
313
+ */
314
+ async deleteClient(clientId) {
315
+ return this.#transaction(async (store) => {
316
+ const client = await store.findClient(clientId);
317
+ if (!client) return false;
318
+ await store.#query(OAuthRefreshToken).where("clientId", clientId).delete();
319
+ await store.#query(OAuthAccessToken).where("clientId", clientId).delete();
320
+ await store.#query(OAuthAuthorizationCode).where("clientId", clientId).delete();
321
+ await store.#query(OAuthPendingAuthorizationRequest).where("clientId", clientId).delete();
322
+ await store.#query(OAuthConsent).where("clientId", clientId).delete();
323
+ return this.#affectedRows(await store.#query(OAuthClient).where("id", client.id).delete()) === 1;
324
+ });
325
+ }
326
+ /**
327
+ * Look up an access-token hash, optionally limited to one client.
328
+ */
329
+ async findAccessToken(options) {
330
+ const query = this.#query(OAuthAccessToken).where("tokenHash", options.hash);
331
+ if (options.clientId) query.where("clientId", options.clientId);
332
+ const token = await query.first();
333
+ return token ? this.#record(token, ["userId", "revokedAt"]) : null;
334
+ }
335
+ /**
336
+ * Insert a hashed access token.
337
+ */
338
+ async createAccessToken(data) {
339
+ await OAuthAccessToken.create(data, this.#client ? { client: this.#client } : void 0);
340
+ }
341
+ /**
342
+ * Revoke an active access token owned by the requesting client.
343
+ */
344
+ async revokeAccessToken(options) {
345
+ const result = await this.#query(OAuthAccessToken).where("tokenHash", options.hash).where("clientId", options.clientId).whereNull("revokedAt").update({
346
+ revokedAt: options.now.toSQL(),
347
+ updatedAt: options.now.toSQL()
348
+ });
349
+ return this.#affectedRows(result) === 1;
350
+ }
351
+ /**
352
+ * Look up a refresh-token hash owned by a client.
353
+ */
354
+ async findRefreshToken(options) {
355
+ const token = await this.#query(OAuthRefreshToken).where("token", options.hash).where("clientId", options.clientId).first();
356
+ return token ? this.#record(token, ["revokedAt"]) : null;
357
+ }
358
+ /**
359
+ * Revoke a refresh token and its associated active access token together.
360
+ */
361
+ async revokeRefreshToken(options) {
362
+ await this.#transaction(async (store) => {
363
+ const token = await store.findRefreshToken(options);
364
+ if (!token) return;
365
+ const result = await store.#query(OAuthRefreshToken).where("id", token.id).whereNull("revokedAt").update({
366
+ revokedAt: options.now.toSQL(),
367
+ updatedAt: options.now.toSQL()
368
+ });
369
+ if (this.#affectedRows(result) !== 1) return;
370
+ await store.#query(OAuthAccessToken).where("id", token.accessTokenId).whereNull("revokedAt").update({
371
+ revokedAt: options.now.toSQL(),
372
+ updatedAt: options.now.toSQL()
373
+ });
374
+ });
375
+ }
376
+ /**
377
+ * Invalidate a client's token family after refresh-token replay.
378
+ */
379
+ async revokeTokenFamily(options) {
380
+ await this.#transaction(async (store) => {
381
+ await store.#query(OAuthRefreshToken).where("clientId", options.clientId).where("userId", options.userId).delete();
382
+ await store.#query(OAuthAccessToken).where("clientId", options.clientId).where("userId", options.userId).whereNull("revokedAt").update({
383
+ revokedAt: options.now.toSQL(),
384
+ updatedAt: options.now.toSQL()
385
+ });
386
+ });
387
+ }
388
+ /**
389
+ * Find an authorization-code hash issued to a client.
390
+ */
391
+ async findAuthorizationCode(options) {
392
+ const code = await this.#query(OAuthAuthorizationCode).where("code", options.code).where("clientId", options.clientId).first();
393
+ return code ? this.#record(code, [
394
+ "codeChallenge",
395
+ "codeChallengeMethod",
396
+ "nonce"
397
+ ]) : null;
398
+ }
399
+ /**
400
+ * Store a short-lived authorization-code hash.
401
+ */
402
+ async createAuthorizationCode(data) {
403
+ await OAuthAuthorizationCode.create(data, this.#client ? { client: this.#client } : void 0);
404
+ }
405
+ /**
406
+ * Discard an expired or invalid authorization code.
407
+ */
408
+ async deleteAuthorizationCode(id) {
409
+ await this.#query(OAuthAuthorizationCode).where("id", id).delete();
410
+ }
411
+ /**
412
+ * Consume a code exactly once and issue its tokens in one transaction.
413
+ */
414
+ async exchangeAuthorizationCode(options) {
415
+ return this.#transaction(async (store) => {
416
+ if (this.#affectedRows(await store.#query(OAuthAuthorizationCode).where("id", options.codeId).delete()) !== 1) return false;
417
+ await store.createAccessToken(options.accessToken);
418
+ if (options.refreshToken) await OAuthRefreshToken.create(options.refreshToken, { client: store.#client });
419
+ return true;
420
+ });
421
+ }
422
+ /**
423
+ * Find a user's current consent for a client.
424
+ */
425
+ async findConsent(options) {
426
+ const consent = await this.#query(OAuthConsent).where("clientId", options.clientId).where("userId", options.userId).first();
427
+ return consent ? this.#record(consent) : null;
428
+ }
429
+ /**
430
+ * Merge newly approved scopes into the user's consent.
431
+ */
432
+ async grantConsent(options) {
433
+ await retryConsentConflict(() => this.#transaction(async (store) => {
434
+ const row = await store.#query(OAuthConsent).where("clientId", options.clientId).where("userId", options.userId).forUpdate().first();
435
+ if (row) {
436
+ const existing = this.#record(row);
437
+ const scopes = [.../* @__PURE__ */ new Set([...existing.scopes, ...options.scopes])];
438
+ await store.#query(OAuthConsent).where("id", existing.id).update({
439
+ scopes: JSON.stringify(scopes),
440
+ updatedAt: DateTime.now().toSQL()
441
+ });
442
+ return;
443
+ }
444
+ await OAuthConsent.create({
445
+ id: crypto.randomUUID(),
446
+ clientId: options.clientId,
447
+ userId: options.userId,
448
+ scopes: options.scopes
449
+ }, { client: store.#client });
450
+ }));
451
+ }
452
+ /**
453
+ * Persist an authorization request awaiting user consent.
454
+ */
455
+ async createPendingAuthorizationRequest(data) {
456
+ await OAuthPendingAuthorizationRequest.create(data, this.#client ? { client: this.#client } : void 0);
457
+ }
458
+ /**
459
+ * Return a valid pending request only to the process that deletes it.
460
+ */
461
+ async consumePendingAuthorizationRequest(options) {
462
+ const row = await this.#query(OAuthPendingAuthorizationRequest).where("token", options.token).where("userId", options.userId).where("expiresAt", ">", options.now.toSQL()).first();
463
+ if (!row) return null;
464
+ if (this.#affectedRows(await this.#query(OAuthPendingAuthorizationRequest).where("id", row.$attributes.id).delete()) !== 1) return null;
465
+ return this.#record(row, [
466
+ "state",
467
+ "codeChallenge",
468
+ "codeChallengeMethod",
469
+ "nonce"
470
+ ]);
471
+ }
472
+ /**
473
+ * Persist a new access/refresh pair atomically during the grace period.
474
+ */
475
+ async issueTokenPair(options) {
476
+ await this.#transaction(async (store) => {
477
+ await store.createAccessToken(options.accessToken);
478
+ await OAuthRefreshToken.create(options.refreshToken, { client: store.#client });
479
+ });
480
+ }
481
+ /**
482
+ * Conditionally consume a refresh token and persist its replacement pair.
483
+ */
484
+ async rotateRefreshToken(options) {
485
+ return this.#transaction(async (store) => {
486
+ if (this.#affectedRows(await store.#query(OAuthRefreshToken).where("id", options.oldRefreshTokenId).whereNull("revokedAt").update({
487
+ revokedAt: options.revokedAt.toSQL(),
488
+ updatedAt: options.revokedAt.toSQL()
489
+ })) !== 1) return false;
490
+ await store.#query(OAuthAccessToken).where("id", options.oldAccessTokenId).whereNull("revokedAt").update({
491
+ revokedAt: options.revokedAt.toSQL(),
492
+ updatedAt: options.revokedAt.toSQL()
493
+ });
494
+ await store.createAccessToken(options.accessToken);
495
+ await OAuthRefreshToken.create(options.refreshToken, { client: store.#client });
496
+ return true;
497
+ });
498
+ }
499
+ /**
500
+ * Revoke a user's tokens and remove their short-lived OAuth records.
501
+ */
502
+ async revokeAllForUser(options) {
503
+ await this.#transaction(async (store) => {
504
+ await store.#query(OAuthAccessToken).where("userId", options.userId).whereNull("revokedAt").update({
505
+ revokedAt: options.now.toSQL(),
506
+ updatedAt: options.now.toSQL()
507
+ });
508
+ await store.#query(OAuthRefreshToken).where("userId", options.userId).whereNull("revokedAt").update({
509
+ revokedAt: options.now.toSQL(),
510
+ updatedAt: options.now.toSQL()
511
+ });
512
+ await store.#query(OAuthAuthorizationCode).where("userId", options.userId).delete();
513
+ await store.#query(OAuthPendingAuthorizationRequest).where("userId", options.userId).delete();
514
+ await store.#query(OAuthConsent).where("userId", options.userId).delete();
515
+ });
516
+ }
517
+ /**
518
+ * Remove revoked and aged OAuth records and return per-table counts.
519
+ */
520
+ async purgeTokens(options) {
521
+ return this.#transaction(async (store) => {
522
+ const counts = {
523
+ accessTokens: 0,
524
+ refreshTokens: 0,
525
+ authorizationCodes: 0,
526
+ pendingRequests: 0
527
+ };
528
+ if (options.purgeRevoked) {
529
+ counts.accessTokens += this.#affectedRows(await store.#query(OAuthAccessToken).whereNotNull("revokedAt").delete());
530
+ counts.refreshTokens += this.#affectedRows(await store.#query(OAuthRefreshToken).whereNotNull("revokedAt").delete());
531
+ }
532
+ if (options.purgeExpired) {
533
+ counts.accessTokens += this.#affectedRows(await store.#query(OAuthAccessToken).where("expiresAt", "<", options.cutoff.toSQL()).whereNull("revokedAt").delete());
534
+ counts.refreshTokens += this.#affectedRows(await store.#query(OAuthRefreshToken).where("expiresAt", "<", options.cutoff.toSQL()).whereNull("revokedAt").delete());
535
+ counts.authorizationCodes += this.#affectedRows(await store.#query(OAuthAuthorizationCode).where("expiresAt", "<", options.cutoff.toSQL()).delete());
536
+ }
537
+ counts.pendingRequests += this.#affectedRows(await store.#query(OAuthPendingAuthorizationRequest).where("expiresAt", "<", options.now.toSQL()).delete());
538
+ return counts;
539
+ });
540
+ }
541
+ };
542
+ /**
543
+ * Create a Sesame store backed by the application's Lucid connection.
544
+ */
545
+ function lucidStore() {
546
+ return new LucidStore();
547
+ }
548
+ //#endregion
549
+ export { LucidStore, OAuthAccessToken, OAuthAuthorizationCode, OAuthClient, OAuthConsent, OAuthPendingAuthorizationRequest, OAuthRefreshToken, lucidStore };
@@ -0,0 +1,6 @@
1
+ import type { Kysely } from 'kysely';
2
+ /**
3
+ * Kysely migration for Sesame's OAuth tables.
4
+ */
5
+ export declare function up(db: Kysely<any>): Promise<void>;
6
+ export declare function down(db: Kysely<any>): Promise<void>;