@julr/sesame 0.6.0 → 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 (70) hide show
  1. package/README.md +69 -6
  2. package/build/{authorize_controller-BiycO4be.js → authorize_controller-sCZIjnc-.js} +10 -10
  3. package/build/{client_info_controller-AcOG8lWu.js → client_info_controller-B5A3tpbk.js} +3 -4
  4. package/build/commands/sesame_key.d.ts +1 -1
  5. package/build/commands/sesame_purge.js +2 -5
  6. package/build/{configure-DkDkIlt8.js → configure-t9q3KonY.js} +7 -3
  7. package/build/configure.js +1 -2
  8. package/build/{consent_controller-Dsdhv6-f.js → consent_controller-ChBboaSd.js} +17 -29
  9. package/build/consent_retry-DMkfhmk9.js +41 -0
  10. package/build/decorate-RQD1h28J.js +9 -0
  11. package/build/{id_token_service-CpTzOUDe.js → id_token_service-BtBUiL_n.js} +1 -1
  12. package/build/index.d.ts +2 -8
  13. package/build/index.js +38 -9
  14. package/build/{introspect_controller-DvOp9scr.js → introspect_controller-LVYGWhMj.js} +13 -9
  15. package/build/{issue_authorization_code-B9ERu1uO.js → issue_authorization_code-Dr4-bbMp.js} +2 -3
  16. package/build/{jwks_controller-keo4kBZc.js → jwks_controller-4HUVS_HC.js} +1 -5
  17. package/build/{main-DGBJhq3E.js → main-Dx9kZv07.js} +14 -52
  18. package/build/{metadata_controller-BVsTo0Gp.js → metadata_controller-hwfmxlF7.js} +4 -6
  19. package/build/providers/sesame_provider.d.ts +1 -1
  20. package/build/providers/sesame_provider.js +8 -8
  21. package/build/{register_controller-gbq7p8a5.js → register_controller-Cdk3c32j.js} +3 -6
  22. package/build/{revoke_controller-z_ghrEB7.js → revoke_controller-Daz4Yqfs.js} +15 -21
  23. package/build/services/main.js +1 -5
  24. package/build/{sesame_manager-DYUSZ0NC.js → sesame_manager-CeEmlzba.js} +78 -256
  25. package/build/sesame_manager-DQxvsTEq.js +2 -0
  26. package/build/src/actions/exchange_authorization_code.d.ts +5 -5
  27. package/build/src/actions/exchange_client_credentials.d.ts +3 -3
  28. package/build/src/actions/exchange_refresh_token.d.ts +4 -4
  29. package/build/src/actions/issue_authorization_code.d.ts +2 -2
  30. package/build/src/controllers/authorize_controller.d.ts +11 -11
  31. package/build/src/controllers/metadata_controller.d.ts +7 -7
  32. package/build/src/controllers/register_controller.d.ts +30 -30
  33. package/build/src/guard/guard.d.ts +1 -1
  34. package/build/src/guard/lucid.d.ts +8 -0
  35. package/build/src/guard/lucid.js +46 -0
  36. package/build/src/guard/main.d.ts +2 -9
  37. package/build/src/guard/main.js +2 -6
  38. package/build/src/guard/types.d.ts +0 -9
  39. package/build/src/guard/user_provider.d.ts +9 -1
  40. package/build/src/middleware/any_scope_middleware.js +1 -2
  41. package/build/src/middleware/scope_middleware.js +1 -2
  42. package/build/src/oauth_error.d.ts +74 -74
  43. package/build/src/services/client_service.d.ts +5 -2
  44. package/build/src/sesame_manager.d.ts +6 -5
  45. package/build/src/storage/consent_retry.d.ts +4 -0
  46. package/build/src/storage/drivers/kysely.d.ts +166 -0
  47. package/build/src/storage/drivers/kysely.js +480 -0
  48. package/build/src/storage/drivers/lucid.d.ts +162 -0
  49. package/build/src/storage/drivers/lucid.js +549 -0
  50. package/build/src/storage/migrations/kysely.d.ts +6 -0
  51. package/build/src/storage/types.d.ts +197 -0
  52. package/build/src/storage/types.js +1 -0
  53. package/build/src/stores.d.ts +21 -0
  54. package/build/src/types.d.ts +9 -1
  55. package/build/src/types.js +2 -0
  56. package/build/stubs/config/sesame.stub +4 -1
  57. package/build/stubs/config/sesame_kysely.stub +35 -0
  58. package/build/stubs/migrations/kysely/create_oauth_tables.stub +151 -0
  59. package/build/taze.config.d.ts +2 -0
  60. package/build/{token_controller-DyI7oy-U.js → token_controller-DRatCQNT.js} +74 -63
  61. package/build/types-DEtn2Zdb.js +75 -0
  62. package/build/{userinfo_controller-RLk8cN_o.js → userinfo_controller-YwN26wK-.js} +5 -8
  63. package/build/vite.config.d.ts +1 -1
  64. package/package.json +33 -17
  65. package/build/chunk-DF48asd8.js +0 -9
  66. package/build/oauth_access_token-Cz_5gNBx.js +0 -29
  67. package/build/oauth_client-BSanvSql.js +0 -63
  68. package/build/sesame_manager-B1Jgq1v2.js +0 -6
  69. /package/build/{oauth_error-C7UhDb2q.js → oauth_error-CvxtZvjp.js} +0 -0
  70. /package/build/{token_service-DwnfAR9F.js → token_service-DDQ3Dxaj.js} +0 -0
package/README.md CHANGED
@@ -20,28 +20,88 @@ When you need identity claims on top of authorization, Sésame supports OpenID C
20
20
 
21
21
  ## Installation
22
22
 
23
+ `@adonisjs/auth` is required, including when you use Kysely without Lucid. If your application does not already have it, run `pnpm add @adonisjs/auth` before installing Sésame.
24
+
23
25
  ```bash
24
26
  node ace add @julr/sesame
25
27
  ```
26
28
 
27
- This will publish the configuration file to `config/sesame.ts`, create six database migration files, and register the service provider and commands. Then run the migrations:
29
+ By default, this publishes `config/sesame.ts`, creates six Lucid migrations, and registers the service provider and commands. Make sure `@adonisjs/lucid` is installed in your application, then run the migrations:
28
30
 
29
31
  ```bash
30
32
  node ace migration:run
31
33
  ```
32
34
 
35
+ ### Kysely installation
36
+
37
+ You can use Sésame in a Kysely application without installing Lucid. The installer publishes a configuration file and a migration, but it does not connect to the database or create tables automatically.
38
+
39
+ ```bash title="Terminal"
40
+ pnpm add kysely
41
+ node ace add @julr/sesame --store=kysely
42
+ ```
43
+
44
+ The generated `config/sesame.ts` selects the Kysely driver. Its `#services/kysely` import is a placeholder. Replace it with the module that exports your application's existing Kysely connection. For example, if that module exports `appDb` from `#services/database`, configure the store like this:
45
+
46
+ ```ts title="config/sesame.ts"
47
+ import env from '#start/env'
48
+ import { defineConfig, stores } from '@julr/sesame'
49
+ import type { InferScopes } from '@julr/sesame/types'
50
+
51
+ const sesameConfig = defineConfig({
52
+ issuer: env.get('APP_URL'),
53
+ store: stores.kysely({
54
+ connection: async () => {
55
+ const { appDb } = await import('#services/database')
56
+ return appDb
57
+ },
58
+ }),
59
+ scopes: {},
60
+ defaultScopes: [],
61
+ grantTypes: ['authorization_code', 'refresh_token'],
62
+ accessTokenTtl: '1h',
63
+ refreshTokenTtl: '30d',
64
+ authorizationCodeTtl: '10m',
65
+ loginPage: '/login',
66
+ consentPage: '/oauth/consent',
67
+ allowDynamicRegistration: false,
68
+ allowPublicRegistration: false,
69
+ })
70
+
71
+ export default sesameConfig
72
+
73
+ declare module '@julr/sesame/types' {
74
+ interface SesameScopes extends InferScopes<typeof sesameConfig> {}
75
+ }
76
+ ```
77
+
78
+ > [!WARNING]
79
+ > The generated config reads `APP_URL`. Your `start/env.ts` must validate that variable, and its value must be the public URL of the OAuth server, not the frontend URL or a `0.0.0.0` bind address. If your application already validates another public API URL, use that variable for `issuer` instead. Without a validated value, `env.get('APP_URL')` can be `undefined` and TypeScript will reject the config.
80
+
81
+ The installer writes the complete migration to `database/kysely_migrations/create_oauth_tables.ts`. Keep that generated file in your application as a schema snapshot. When you already have a Kysely migrator, move it into that migrator's `migrationFolder`, give it a filename that sorts after your existing migrations, and run your usual migration command. For example, an application whose migrator reads `database/migrations` should put the file there. Do not pass it to Lucid's `node ace migration:run`.
82
+
83
+ If Sésame uses its own migration folder, configure a separate [Kysely migrator](https://kysely-org.github.io/kysely-apidoc/classes/migration.Migrator.html) for `database/kysely_migrations` and call `migrateToLatest()` explicitly. Give that migrator unique `migrationTableName` and `migrationLockTableName` values when your application already has another Kysely migrator on the same database. Commit the generated migration so future Sésame upgrades cannot alter a migration you already applied.
84
+
85
+ The Kysely driver supports SQLite, PostgreSQL, and MySQL. The default tests cover SQLite; `pnpm test:sql` runs the PostgreSQL and MySQL integration suite in temporary Docker containers. For a custom adapter backed by one of these databases, pass its `dialect` explicitly to `stores.kysely()`.
86
+
87
+ Sésame configures one `store` at a time. It must be an AdonisJS `ConfigProvider<SesameStore>`; the service provider resolves it with the application before creating the manager. Choose `stores.lucid()`, `stores.kysely()`, or a custom driver. To add a custom driver, implement `SesameStore` from `@julr/sesame/types` and wrap it with `configProvider.create()`. The store interface exposes OAuth operations rather than generic CRUD queries. Its `exchangeAuthorizationCode`, `rotateRefreshToken`, and `issueTokenPair` methods must use real database transactions; the first two return `false` when another request consumed the credential first.
88
+
89
+ Upgrading an existing Lucid application from 0.6.0 requires config and import changes. Follow the [0.6.0 to 0.7.0 migration guide](docs/migration-0.6-to-0.7.md).
90
+
33
91
  ## Configuration
34
92
 
35
93
  The configuration file lives at `config/sesame.ts`. You define your issuer URL, available scopes, grant types, token lifetimes, and page redirects for the authorization flow.
36
94
 
37
95
  ```ts title="config/sesame.ts"
38
96
  import env from '#start/env'
39
- import { defineConfig } from '@julr/sesame'
97
+ import { defineConfig, stores } from '@julr/sesame'
40
98
  import type { InferScopes } from '@julr/sesame/types'
41
99
 
42
100
  const sesameConfig = defineConfig({
43
101
  issuer: env.get('APP_URL'),
44
102
 
103
+ store: stores.lucid(),
104
+
45
105
  scopes: {
46
106
  read: 'Read access',
47
107
  write: 'Write access',
@@ -128,7 +188,8 @@ Sésame provides an OAuth guard for `@adonisjs/auth` that verifies opaque Bearer
128
188
 
129
189
  ```ts title="config/auth.ts"
130
190
  import { defineConfig } from '@adonisjs/auth'
131
- import { oauthGuard, oauthUserProvider } from '@julr/sesame/guard'
191
+ import { oauthGuard } from '@julr/sesame/guard'
192
+ import { oauthUserProvider } from '@julr/sesame/guard/lucid'
132
193
 
133
194
  const authConfig = defineConfig({
134
195
  default: 'web',
@@ -259,12 +320,14 @@ Pass the JWK and a user provider to `defineConfig`. The `oidcProvider` uses the
259
320
 
260
321
  ```ts title="config/sesame.ts"
261
322
  import env from '#start/env'
262
- import { defineConfig } from '@julr/sesame'
263
- import { oauthUserProvider } from '@julr/sesame/guard'
323
+ import { defineConfig, stores } from '@julr/sesame'
324
+ import { oauthUserProvider } from '@julr/sesame/guard/lucid'
264
325
 
265
326
  const sesameConfig = defineConfig({
266
327
  issuer: env.get('APP_URL'),
267
328
 
329
+ store: stores.lucid(),
330
+
268
331
  scopes: {
269
332
  read: 'Read access',
270
333
  write: 'Write access',
@@ -429,7 +492,7 @@ const { client: spa } = await sesame.createClient({
429
492
  })
430
493
  ```
431
494
 
432
- `createClient` returns the client model and the raw secret. The secret is only available at creation time.
495
+ `createClient` returns a plain client record and the raw secret. The secret is only available at creation time. Client records returned by the public management methods do not serialize the stored `clientSecret` hash.
433
496
 
434
497
  To find, list, update, or delete clients:
435
498
 
@@ -1,10 +1,7 @@
1
- import "./chunk-DF48asd8.js";
2
- import { n as OAuthPendingAuthorizationRequest, o as ClientService, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DYUSZ0NC.js";
3
- import { t as OAuthClient } from "./oauth_client-BSanvSql.js";
4
- import { d as E_UNSUPPORTED_RESPONSE_TYPE, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-C7UhDb2q.js";
5
- import "./oauth_access_token-Cz_5gNBx.js";
6
- import { t as TokenService } from "./token_service-DwnfAR9F.js";
7
- import { t as IssueAuthorizationCodeAction } from "./issue_authorization_code-B9ERu1uO.js";
1
+ import { d as E_UNSUPPORTED_RESPONSE_TYPE, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-CvxtZvjp.js";
2
+ import { n as ClientService, t as SesameManager } from "./sesame_manager-CeEmlzba.js";
3
+ import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
4
+ import { t as IssueAuthorizationCodeAction } from "./issue_authorization_code-Dr4-bbMp.js";
8
5
  import { DateTime } from "luxon";
9
6
  import string from "@adonisjs/core/helpers/string";
10
7
  import vine from "@vinejs/vine";
@@ -28,7 +25,7 @@ var AuthorizeAction = class {
28
25
  */
29
26
  async execute(manager, input) {
30
27
  if (input.responseType !== "code") throw new E_UNSUPPORTED_RESPONSE_TYPE("Only \"code\" is supported");
31
- const client = await OAuthClient.query().where("clientId", input.clientId).first();
28
+ const client = await manager.store.findClient(input.clientId);
32
29
  if (!client) throw new E_INVALID_CLIENT("Client not found");
33
30
  if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
34
31
  if (!client.redirectUris.includes(input.redirectUri)) throw new E_INVALID_REQUEST("Invalid redirect_uri");
@@ -99,7 +96,10 @@ var AuthorizeAction = class {
99
96
  * a pending authorization request for the consent page.
100
97
  */
101
98
  async #resolveConsent(manager, input, client, scopes) {
102
- const existingConsent = await OAuthConsent.query().where("clientId", client.clientId).where("userId", input.userId).first();
99
+ const existingConsent = await manager.store.findConsent({
100
+ clientId: client.clientId,
101
+ userId: input.userId
102
+ });
103
103
  if (existingConsent) {
104
104
  const consentedSet = new Set(existingConsent.scopes);
105
105
  if (scopes.every((s) => consentedSet.has(s))) return {
@@ -129,7 +129,7 @@ var AuthorizeAction = class {
129
129
  const tokenService = new TokenService(manager);
130
130
  const rawToken = tokenService.generateOpaqueToken();
131
131
  const ttl = string.seconds.parse(manager.config.authorizationRequestTtl);
132
- await OAuthPendingAuthorizationRequest.create({
132
+ await manager.store.createPendingAuthorizationRequest({
133
133
  id: crypto.randomUUID(),
134
134
  token: tokenService.hashToken(rawToken),
135
135
  userId: input.userId,
@@ -1,6 +1,5 @@
1
- import "./chunk-DF48asd8.js";
2
- import { t as OAuthClient } from "./oauth_client-BSanvSql.js";
3
- import { o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-C7UhDb2q.js";
1
+ import { o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-CvxtZvjp.js";
2
+ import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
4
3
  import vine from "@vinejs/vine";
5
4
  //#region src/controllers/client_info_controller.ts
6
5
  /**
@@ -14,7 +13,7 @@ var ClientInfoController = class ClientInfoController {
14
13
  async handle(ctx) {
15
14
  const [error, query] = await ClientInfoController.validator.tryValidate(ctx.request.qs());
16
15
  if (error) throw new E_INVALID_REQUEST("Missing client_id");
17
- const client = await OAuthClient.query().where("clientId", query.client_id).first();
16
+ const client = await (await ctx.containerResolver.make(SesameManager)).store.findClient(query.client_id);
18
17
  if (!client) throw new E_INVALID_CLIENT("Client not found");
19
18
  return {
20
19
  client_id: client.clientId,
@@ -8,5 +8,5 @@ export default class SesameKey extends BaseCommand {
8
8
  static description: string;
9
9
  raw: boolean;
10
10
  writeEnv: boolean;
11
- run(): Promise<boolean | void>;
11
+ run(): Promise<void | boolean>;
12
12
  }
@@ -1,8 +1,5 @@
1
- import "../chunk-DF48asd8.js";
2
- import { t as SesameManager } from "../sesame_manager-DYUSZ0NC.js";
3
- import { n as __decorate } from "../oauth_client-BSanvSql.js";
4
- import "../oauth_error-C7UhDb2q.js";
5
- import "../oauth_access_token-Cz_5gNBx.js";
1
+ import { t as SesameManager } from "../sesame_manager-CeEmlzba.js";
2
+ import { t as __decorate } from "../decorate-RQD1h28J.js";
6
3
  import { BaseCommand, flags } from "@adonisjs/core/ace";
7
4
  //#region commands/sesame_purge.ts
8
5
  /**
@@ -3,15 +3,19 @@ import { join } from "node:path";
3
3
  async function configure(command) {
4
4
  const codemods = await command.createCodemods();
5
5
  const stubsRoot = join(import.meta.dirname, "stubs");
6
- await codemods.makeUsingStub(stubsRoot, "config/sesame.stub", {});
7
- for (const stub of [
6
+ const store = command.parsedFlags.store ?? "lucid";
7
+ if (store !== "lucid" && store !== "kysely") throw new Error("Invalid Sesame store. Use --store=lucid or --store=kysely");
8
+ const configStub = store === "kysely" ? "config/sesame_kysely.stub" : "config/sesame.stub";
9
+ await codemods.makeUsingStub(stubsRoot, configStub, {});
10
+ const migrationStubs = store === "kysely" ? ["migrations/kysely/create_oauth_tables.stub"] : [
8
11
  "migrations/create_oauth_clients_table.stub",
9
12
  "migrations/create_oauth_authorization_codes_table.stub",
10
13
  "migrations/create_oauth_access_tokens_table.stub",
11
14
  "migrations/create_oauth_refresh_tokens_table.stub",
12
15
  "migrations/create_oauth_consents_table.stub",
13
16
  "migrations/create_oauth_pending_authorization_requests_table.stub"
14
- ]) await codemods.makeUsingStub(stubsRoot, stub, {});
17
+ ];
18
+ for (const stub of migrationStubs) await codemods.makeUsingStub(stubsRoot, stub, {});
15
19
  await codemods.updateRcFile((rcFile) => {
16
20
  rcFile.addProvider("@julr/sesame/sesame_provider").addCommand("@julr/sesame/commands");
17
21
  });
@@ -1,3 +1,2 @@
1
- import "./chunk-DF48asd8.js";
2
- import { t as configure } from "./configure-DkDkIlt8.js";
1
+ import { t as configure } from "./configure-t9q3KonY.js";
3
2
  export { configure };
@@ -1,10 +1,7 @@
1
- import "./chunk-DF48asd8.js";
2
- import { n as OAuthPendingAuthorizationRequest, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DYUSZ0NC.js";
3
- import { t as OAuthClient } from "./oauth_client-BSanvSql.js";
4
- import { a as E_INVALID_GRANT, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-C7UhDb2q.js";
5
- import "./oauth_access_token-Cz_5gNBx.js";
6
- import { t as TokenService } from "./token_service-DwnfAR9F.js";
7
- import { t as IssueAuthorizationCodeAction } from "./issue_authorization_code-B9ERu1uO.js";
1
+ import { a as E_INVALID_GRANT, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-CvxtZvjp.js";
2
+ import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
3
+ import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
4
+ import { t as IssueAuthorizationCodeAction } from "./issue_authorization_code-Dr4-bbMp.js";
8
5
  import { DateTime } from "luxon";
9
6
  import vine from "@vinejs/vine";
10
7
  //#region src/controllers/consent_controller.ts
@@ -37,14 +34,14 @@ var ConsentController = class ConsentController {
37
34
  if (error) throw new E_INVALID_REQUEST("Missing required parameter: auth_token");
38
35
  const userId = String(user.id);
39
36
  const hashedToken = new TokenService(manager).hashToken(body.auth_token);
40
- const pendingRequest = await this.#consumePendingRequest(hashedToken, userId);
37
+ const pendingRequest = await this.#consumePendingRequest(manager, hashedToken, userId);
41
38
  if (!pendingRequest) throw new E_INVALID_GRANT("Authorization request not found or expired");
42
- const client = await OAuthClient.query().where("clientId", pendingRequest.clientId).first();
39
+ const client = await manager.store.findClient(pendingRequest.clientId);
43
40
  if (!client) throw new E_INVALID_CLIENT("Client not found");
44
41
  if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
45
42
  if (!client.redirectUris.includes(pendingRequest.redirectUri)) throw new E_INVALID_REQUEST("Invalid redirect_uri");
46
43
  if (!ctx.request.body().accept) return this.#redirectWithDenied(ctx, manager, pendingRequest);
47
- await this.#persistConsent(client.clientId, userId, pendingRequest.scopes);
44
+ await this.#persistConsent(manager, client.clientId, userId, pendingRequest.scopes);
48
45
  const code = await new IssueAuthorizationCodeAction().execute(manager, {
49
46
  client,
50
47
  userId,
@@ -61,31 +58,22 @@ var ConsentController = class ConsentController {
61
58
  return ctx.response.redirect().toPath(url.toString());
62
59
  }
63
60
  /**
64
- * Atomically consume a pending authorization request.
65
- * Uses DELETE-by-PK with affected-row check to prevent
66
- * concurrent consent submissions from producing two
67
- * authorization codes.
61
+ * Atomically consume a pending authorization request so concurrent
62
+ * consent submissions cannot produce two authorization codes.
68
63
  */
69
- async #consumePendingRequest(hashedToken, userId) {
70
- const row = await OAuthPendingAuthorizationRequest.query().where("token", hashedToken).where("userId", userId).where("expiresAt", ">", DateTime.now().toSQL()).first();
71
- if (!row) return null;
72
- const deleted = await OAuthPendingAuthorizationRequest.query().where("id", row.id).delete();
73
- if ((Array.isArray(deleted) ? Number(deleted[0]) : Number(deleted)) === 0) return null;
74
- return row;
64
+ async #consumePendingRequest(manager, hashedToken, userId) {
65
+ return manager.store.consumePendingAuthorizationRequest({
66
+ token: hashedToken,
67
+ userId,
68
+ now: DateTime.now()
69
+ });
75
70
  }
76
71
  /**
77
72
  * Persist or merge consent so future authorization requests
78
73
  * for the same client skip the consent screen.
79
74
  */
80
- async #persistConsent(clientId, userId, scopes) {
81
- const existingConsent = await OAuthConsent.query().where("clientId", clientId).where("userId", userId).first();
82
- if (existingConsent) {
83
- existingConsent.scopes = [...new Set([...existingConsent.scopes, ...scopes])];
84
- await existingConsent.save();
85
- return;
86
- }
87
- await OAuthConsent.create({
88
- id: crypto.randomUUID(),
75
+ async #persistConsent(manager, clientId, userId, scopes) {
76
+ await manager.store.grantConsent({
89
77
  clientId,
90
78
  userId,
91
79
  scopes
@@ -0,0 +1,41 @@
1
+ //#region src/storage/consent_retry.ts
2
+ const RETRYABLE_CODES = /* @__PURE__ */ new Set([
3
+ "23505",
4
+ "40001",
5
+ "40P01",
6
+ "ER_DUP_ENTRY",
7
+ "ER_LOCK_DEADLOCK",
8
+ "ER_LOCK_WAIT_TIMEOUT",
9
+ "SQLITE_BUSY",
10
+ "SQLITE_BUSY_SNAPSHOT",
11
+ "SQLITE_CONSTRAINT_UNIQUE"
12
+ ]);
13
+ const RETRYABLE_ERRNOS = /* @__PURE__ */ new Set([
14
+ 1062,
15
+ 1205,
16
+ 1213
17
+ ]);
18
+ /**
19
+ * Identify database conflicts that can be retried in a fresh transaction.
20
+ */
21
+ function isRetryableConsentConflict(error) {
22
+ if (!error || typeof error !== "object") return false;
23
+ const candidate = error;
24
+ if (typeof candidate.code === "string" && RETRYABLE_CODES.has(candidate.code)) return true;
25
+ if (typeof candidate.errno === "number" && RETRYABLE_ERRNOS.has(candidate.errno)) return true;
26
+ return candidate.cause ? isRetryableConsentConflict(candidate.cause) : false;
27
+ }
28
+ /**
29
+ * Retry consent writes after uniqueness, serialization, or lock conflicts.
30
+ */
31
+ async function retryConsentConflict(operation) {
32
+ for (let attempt = 1; attempt <= 3; attempt++) try {
33
+ return await operation();
34
+ } catch (error) {
35
+ if (attempt === 3 || !isRetryableConsentConflict(error)) throw error;
36
+ await new Promise((resolve) => setTimeout(resolve, attempt * 5));
37
+ }
38
+ throw new Error("Consent retry limit exceeded");
39
+ }
40
+ //#endregion
41
+ export { retryConsentConflict as t };
@@ -0,0 +1,9 @@
1
+ //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
2
+ function __decorate(decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ }
8
+ //#endregion
9
+ export { __decorate as t };
@@ -1,4 +1,4 @@
1
- import { l as RESERVED_OIDC_CLAIMS } from "./sesame_manager-DYUSZ0NC.js";
1
+ import { r as RESERVED_OIDC_CLAIMS } from "./types-DEtn2Zdb.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import string from "@adonisjs/core/helpers/string";
4
4
  //#region src/services/id_token_service.ts
package/build/index.d.ts CHANGED
@@ -1,12 +1,6 @@
1
1
  export { configure } from './configure.ts';
2
2
  export { defineConfig } from './src/define_config.ts';
3
+ export { stores } from './src/stores.ts';
3
4
  export { SesameManager } from './src/sesame_manager.ts';
4
- export type { CreateClientOptions, CreateClientResult, UpdateClientOptions } from './src/types.ts';
5
- export { OAuthClient } from './src/models/oauth_client.ts';
6
- export { OAuthAccessToken } from './src/models/oauth_access_token.ts';
7
- export { OAuthRefreshToken } from './src/models/oauth_refresh_token.ts';
8
- export { OAuthAuthorizationCode } from './src/models/oauth_authorization_code.ts';
9
- export { OAuthConsent } from './src/models/oauth_consent.ts';
10
5
  export { OAuthGuard } from './src/guard/guard.ts';
11
- export { OAuthLucidUserProvider } from './src/guard/user_provider.ts';
12
- export { oauthGuard, oauthUserProvider } from './src/guard/main.ts';
6
+ export { oauthGuard } from './src/guard/main.ts';
package/build/index.js CHANGED
@@ -1,11 +1,8 @@
1
- import "./chunk-DF48asd8.js";
2
- import { t as configure } from "./configure-DkDkIlt8.js";
3
- import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DYUSZ0NC.js";
4
- import { t as OAuthClient } from "./oauth_client-BSanvSql.js";
5
- import "./oauth_error-C7UhDb2q.js";
6
- import { t as OAuthAccessToken } from "./oauth_access_token-Cz_5gNBx.js";
7
- import "./token_service-DwnfAR9F.js";
8
- import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t as oauthGuard } from "./main-DGBJhq3E.js";
1
+ import { t as configure } from "./configure-t9q3KonY.js";
2
+ import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
3
+ import { n as OAuthGuard, t as oauthGuard } from "./main-Dx9kZv07.js";
4
+ import { InvalidArgumentsException } from "@adonisjs/core/exceptions";
5
+ import { configProvider } from "@adonisjs/core";
9
6
  //#region src/define_config.ts
10
7
  /**
11
8
  * Resolve user-supplied `SesameConfig` into a `ResolvedSesameConfig`
@@ -22,7 +19,9 @@ import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t
22
19
  * - `allowPublicRegistration`: `false`
23
20
  */
24
21
  function defineConfig(config) {
22
+ if (!config.store) throw new InvalidArgumentsException("Missing \"store\" in Sesame config");
25
23
  return {
24
+ store: config.store,
26
25
  issuer: config.issuer,
27
26
  scopes: config.scopes ?? {},
28
27
  defaultScopes: config.defaultScopes ?? [],
@@ -43,4 +42,34 @@ function defineConfig(config) {
43
42
  };
44
43
  }
45
44
  //#endregion
46
- export { OAuthAccessToken, OAuthAuthorizationCode, OAuthClient, OAuthConsent, OAuthGuard, OAuthLucidUserProvider, OAuthRefreshToken, SesameManager, configure, defineConfig, oauthGuard, oauthUserProvider };
45
+ //#region src/stores.ts
46
+ /**
47
+ * Store driver factories resolved by the AdonisJS service provider.
48
+ */
49
+ const stores = {
50
+ /**
51
+ * Use the application's Lucid connection for OAuth persistence.
52
+ */
53
+ lucid() {
54
+ return configProvider.create(async (app) => {
55
+ await app.container.make("lucid.db");
56
+ const { lucidStore } = await import("./src/storage/drivers/lucid.js");
57
+ return lucidStore();
58
+ });
59
+ },
60
+ /**
61
+ * Use an existing Kysely connection for OAuth persistence.
62
+ */
63
+ kysely(options) {
64
+ return configProvider.create(async (app) => {
65
+ const connection = typeof options.connection === "function" ? await options.connection(app) : options.connection;
66
+ const { kyselyStore } = await import("./src/storage/drivers/kysely.js");
67
+ return kyselyStore({
68
+ db: connection,
69
+ dialect: options.dialect
70
+ });
71
+ });
72
+ }
73
+ };
74
+ //#endregion
75
+ export { OAuthGuard, SesameManager, configure, defineConfig, oauthGuard, stores };
@@ -1,9 +1,5 @@
1
- import "./chunk-DF48asd8.js";
2
- import { a as OAuthRefreshToken, o as ClientService, t as SesameManager } from "./sesame_manager-DYUSZ0NC.js";
3
- import "./oauth_client-BSanvSql.js";
4
- import "./oauth_error-C7UhDb2q.js";
5
- import { t as OAuthAccessToken } from "./oauth_access_token-Cz_5gNBx.js";
6
- import { t as TokenService } from "./token_service-DwnfAR9F.js";
1
+ import { n as ClientService, t as SesameManager } from "./sesame_manager-CeEmlzba.js";
2
+ import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
7
3
  //#region src/controllers/introspect_controller.ts
8
4
  const INACTIVE = { active: false };
9
5
  /**
@@ -22,7 +18,9 @@ const INACTIVE = { active: false };
22
18
  var IntrospectController = class {
23
19
  async handle(ctx) {
24
20
  const manager = await ctx.containerResolver.make(SesameManager);
25
- const client = await new ClientService().authenticateClient({
21
+ const clientService = new ClientService(manager);
22
+ const store = manager.store;
23
+ const client = await clientService.authenticateClient({
26
24
  authorizationHeader: ctx.request.header("authorization"),
27
25
  bodyClientId: ctx.request.body().client_id,
28
26
  bodyClientSecret: ctx.request.body().client_secret
@@ -32,7 +30,10 @@ var IntrospectController = class {
32
30
  const tokenTypeHint = ctx.request.body().token_type_hint;
33
31
  const hashed = new TokenService(manager).hashToken(token);
34
32
  if (!tokenTypeHint || tokenTypeHint === "access_token") {
35
- const record = await OAuthAccessToken.query().where("tokenHash", hashed).where("clientId", client.clientId).first();
33
+ const record = await store.findAccessToken({
34
+ hash: hashed,
35
+ clientId: client.clientId
36
+ });
36
37
  if (record && !record.revokedAt && record.expiresAt.toJSDate() >= /* @__PURE__ */ new Date()) return {
37
38
  active: true,
38
39
  token_type: "Bearer",
@@ -46,7 +47,10 @@ var IntrospectController = class {
46
47
  if (tokenTypeHint === "access_token") return INACTIVE;
47
48
  }
48
49
  if (!tokenTypeHint || tokenTypeHint === "refresh_token") {
49
- const refreshToken = await OAuthRefreshToken.query().where("token", hashed).where("clientId", client.clientId).first();
50
+ const refreshToken = await store.findRefreshToken({
51
+ hash: hashed,
52
+ clientId: client.clientId
53
+ });
50
54
  if (!refreshToken) return INACTIVE;
51
55
  if (refreshToken.revokedAt) return INACTIVE;
52
56
  if (refreshToken.expiresAt.toJSDate() < /* @__PURE__ */ new Date()) return INACTIVE;
@@ -1,5 +1,4 @@
1
- import { i as OAuthAuthorizationCode } from "./sesame_manager-DYUSZ0NC.js";
2
- import { t as TokenService } from "./token_service-DwnfAR9F.js";
1
+ import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
3
2
  import { DateTime } from "luxon";
4
3
  import string from "@adonisjs/core/helpers/string";
5
4
  //#region src/actions/issue_authorization_code.ts
@@ -21,7 +20,7 @@ var IssueAuthorizationCodeAction = class {
21
20
  const raw = tokenService.generateOpaqueToken();
22
21
  const hashed = tokenService.hashToken(raw);
23
22
  const ttl = string.seconds.parse(manager.config.authorizationCodeTtl);
24
- await OAuthAuthorizationCode.create({
23
+ await manager.store.createAuthorizationCode({
25
24
  id: crypto.randomUUID(),
26
25
  code: hashed,
27
26
  clientId: input.client.clientId,
@@ -1,8 +1,4 @@
1
- import "./chunk-DF48asd8.js";
2
- import { t as SesameManager } from "./sesame_manager-DYUSZ0NC.js";
3
- import "./oauth_client-BSanvSql.js";
4
- import "./oauth_error-C7UhDb2q.js";
5
- import "./oauth_access_token-Cz_5gNBx.js";
1
+ import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
6
2
  //#region src/controllers/jwks_controller.ts
7
3
  /**
8
4
  * Serves the JSON Web Key Set (JWKS) containing public keys
@@ -1,8 +1,5 @@
1
- import { t as OAuthClient } from "./oauth_client-BSanvSql.js";
2
- import { t as OAuthAccessToken } from "./oauth_access_token-Cz_5gNBx.js";
3
- import { t as TokenService } from "./token_service-DwnfAR9F.js";
1
+ import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
4
2
  import { DateTime } from "luxon";
5
- import { RuntimeException } from "@adonisjs/core/exceptions";
6
3
  import { errors } from "@adonisjs/auth";
7
4
  //#region src/guard/guard.ts
8
5
  /**
@@ -66,7 +63,7 @@ var OAuthGuard = class {
66
63
  const tokenService = new TokenService(this.#manager);
67
64
  const includeError = { includeError: true };
68
65
  const hashed = tokenService.hashToken(rawToken);
69
- const record = await OAuthAccessToken.query().where("tokenHash", hashed).first();
66
+ const record = await this.#manager.store.findAccessToken({ hash: hashed });
70
67
  if (!record) throw this.#authenticationFailed("Invalid or expired token", includeError);
71
68
  if (record.revokedAt) throw this.#authenticationFailed("Invalid or expired token", includeError);
72
69
  if (record.expiresAt.toJSDate() < /* @__PURE__ */ new Date()) throw this.#authenticationFailed("Invalid or expired token", includeError);
@@ -109,19 +106,26 @@ var OAuthGuard = class {
109
106
  async authenticateAsClient(user) {
110
107
  const tokenService = new TokenService(this.#manager);
111
108
  const defaultScopes = this.#manager.config.defaultScopes;
112
- const testClient = await OAuthClient.firstOrCreate({ clientId: "__test_client__" }, {
109
+ const store = this.#manager.store;
110
+ let testClient = await store.findClient("__test_client__");
111
+ if (!testClient) testClient = await store.createClient({
113
112
  id: crypto.randomUUID(),
114
113
  clientId: "__test_client__",
114
+ clientSecret: null,
115
115
  name: "Test Client",
116
116
  redirectUris: ["http://localhost/callback"],
117
117
  grantTypes: ["authorization_code"],
118
118
  scopes: defaultScopes,
119
119
  isPublic: true,
120
- requirePkce: false
120
+ isDisabled: false,
121
+ requirePkce: false,
122
+ type: "public",
123
+ metadata: null,
124
+ userId: null
121
125
  });
122
126
  const userId = String(user.id ?? user.getId?.() ?? "test-user");
123
127
  const { raw, hash, expiresAt } = tokenService.createAccessToken();
124
- await OAuthAccessToken.create({
128
+ await store.createAccessToken({
125
129
  id: crypto.randomUUID(),
126
130
  tokenHash: hash,
127
131
  clientId: testClient.clientId,
@@ -133,42 +137,6 @@ var OAuthGuard = class {
133
137
  }
134
138
  };
135
139
  //#endregion
136
- //#region src/guard/user_provider.ts
137
- /**
138
- * Lucid-based user provider for the OAuth guard.
139
- * Lazily loads the model and wraps instances as guard users.
140
- */
141
- var OAuthLucidUserProvider = class {
142
- #model;
143
- #options;
144
- constructor(options) {
145
- this.#options = options;
146
- }
147
- async #getModel() {
148
- if (this.#model && !("hot" in import.meta)) return this.#model;
149
- this.#model = (await this.#options.model()).default;
150
- return this.#model;
151
- }
152
- async createUserForGuard(user) {
153
- const model = await this.#getModel();
154
- if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
155
- return {
156
- getId() {
157
- if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`);
158
- return user.$primaryKeyValue;
159
- },
160
- getOriginal() {
161
- return user;
162
- }
163
- };
164
- }
165
- async findById(identifier) {
166
- const user = await (await this.#getModel()).find(identifier);
167
- if (!user) return null;
168
- return this.createUserForGuard(user);
169
- }
170
- };
171
- //#endregion
172
140
  //#region src/guard/main.ts
173
141
  /**
174
142
  * Configure the OAuth guard for `@adonisjs/auth`.
@@ -176,16 +144,10 @@ var OAuthLucidUserProvider = class {
176
144
  function oauthGuard(config) {
177
145
  return { async resolver(name, app) {
178
146
  const emitter = await app.container.make("emitter");
179
- const { SesameManager } = await import("./sesame_manager-B1Jgq1v2.js");
147
+ const { SesameManager } = await import("./sesame_manager-DQxvsTEq.js");
180
148
  const manager = await app.container.make(SesameManager);
181
149
  return (ctx) => new OAuthGuard(name, ctx, emitter, config.provider, manager, config.resource);
182
150
  } };
183
151
  }
184
- /**
185
- * Create a Lucid-based user provider for the OAuth guard.
186
- */
187
- function oauthUserProvider(options) {
188
- return new OAuthLucidUserProvider(options);
189
- }
190
152
  //#endregion
191
- export { OAuthGuard as i, oauthUserProvider as n, OAuthLucidUserProvider as r, oauthGuard as t };
153
+ export { OAuthGuard as n, oauthGuard as t };