@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.
- package/README.md +69 -6
- package/build/{authorize_controller-BiycO4be.js → authorize_controller-sCZIjnc-.js} +10 -10
- package/build/{client_info_controller-AcOG8lWu.js → client_info_controller-B5A3tpbk.js} +3 -4
- package/build/commands/sesame_key.d.ts +1 -1
- package/build/commands/sesame_purge.js +2 -5
- package/build/{configure-DkDkIlt8.js → configure-t9q3KonY.js} +7 -3
- package/build/configure.js +1 -2
- package/build/{consent_controller-Dsdhv6-f.js → consent_controller-ChBboaSd.js} +17 -29
- package/build/consent_retry-DMkfhmk9.js +41 -0
- package/build/decorate-RQD1h28J.js +9 -0
- package/build/{id_token_service-CpTzOUDe.js → id_token_service-BtBUiL_n.js} +1 -1
- package/build/index.d.ts +2 -8
- package/build/index.js +38 -9
- package/build/{introspect_controller-DvOp9scr.js → introspect_controller-LVYGWhMj.js} +13 -9
- package/build/{issue_authorization_code-B9ERu1uO.js → issue_authorization_code-Dr4-bbMp.js} +2 -3
- package/build/{jwks_controller-keo4kBZc.js → jwks_controller-4HUVS_HC.js} +1 -5
- package/build/{main-DGBJhq3E.js → main-Dx9kZv07.js} +14 -52
- package/build/{metadata_controller-BVsTo0Gp.js → metadata_controller-hwfmxlF7.js} +4 -6
- package/build/providers/sesame_provider.d.ts +1 -1
- package/build/providers/sesame_provider.js +8 -8
- package/build/{register_controller-gbq7p8a5.js → register_controller-Cdk3c32j.js} +3 -6
- package/build/{revoke_controller-z_ghrEB7.js → revoke_controller-Daz4Yqfs.js} +15 -21
- package/build/services/main.js +1 -5
- package/build/{sesame_manager-DYUSZ0NC.js → sesame_manager-CeEmlzba.js} +78 -256
- package/build/sesame_manager-DQxvsTEq.js +2 -0
- package/build/src/actions/exchange_authorization_code.d.ts +5 -5
- package/build/src/actions/exchange_client_credentials.d.ts +3 -3
- package/build/src/actions/exchange_refresh_token.d.ts +4 -4
- package/build/src/actions/issue_authorization_code.d.ts +2 -2
- package/build/src/controllers/authorize_controller.d.ts +11 -11
- package/build/src/controllers/metadata_controller.d.ts +7 -7
- package/build/src/controllers/register_controller.d.ts +30 -30
- package/build/src/guard/guard.d.ts +1 -1
- package/build/src/guard/lucid.d.ts +8 -0
- package/build/src/guard/lucid.js +46 -0
- package/build/src/guard/main.d.ts +2 -9
- package/build/src/guard/main.js +2 -6
- package/build/src/guard/types.d.ts +0 -9
- package/build/src/guard/user_provider.d.ts +9 -1
- package/build/src/middleware/any_scope_middleware.js +1 -2
- package/build/src/middleware/scope_middleware.js +1 -2
- package/build/src/oauth_error.d.ts +74 -74
- package/build/src/services/client_service.d.ts +5 -2
- package/build/src/sesame_manager.d.ts +6 -5
- package/build/src/storage/consent_retry.d.ts +4 -0
- package/build/src/storage/drivers/kysely.d.ts +166 -0
- package/build/src/storage/drivers/kysely.js +480 -0
- package/build/src/storage/drivers/lucid.d.ts +162 -0
- package/build/src/storage/drivers/lucid.js +549 -0
- package/build/src/storage/migrations/kysely.d.ts +6 -0
- package/build/src/storage/types.d.ts +197 -0
- package/build/src/storage/types.js +1 -0
- package/build/src/stores.d.ts +21 -0
- package/build/src/types.d.ts +9 -1
- package/build/src/types.js +2 -0
- package/build/stubs/config/sesame.stub +4 -1
- package/build/stubs/config/sesame_kysely.stub +35 -0
- package/build/stubs/migrations/kysely/create_oauth_tables.stub +151 -0
- package/build/taze.config.d.ts +2 -0
- package/build/{token_controller-DyI7oy-U.js → token_controller-DRatCQNT.js} +74 -63
- package/build/types-DEtn2Zdb.js +75 -0
- package/build/{userinfo_controller-RLk8cN_o.js → userinfo_controller-YwN26wK-.js} +5 -8
- package/build/vite.config.d.ts +1 -1
- package/package.json +33 -17
- package/build/chunk-DF48asd8.js +0 -9
- package/build/oauth_access_token-Cz_5gNBx.js +0 -29
- package/build/oauth_client-BSanvSql.js +0 -63
- package/build/sesame_manager-B1Jgq1v2.js +0 -6
- /package/build/{oauth_error-C7UhDb2q.js → oauth_error-CvxtZvjp.js} +0 -0
- /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
|
-
|
|
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
|
|
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
|
|
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 "./
|
|
2
|
-
import { n as
|
|
3
|
-
import { t as
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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 "./
|
|
2
|
-
import { t as
|
|
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
|
|
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,
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { t as
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
]
|
|
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
|
});
|
package/build/configure.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import {
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 "./
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import "
|
|
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
|
-
|
|
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 "./
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 "./
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
|
153
|
+
export { OAuthGuard as n, oauthGuard as t };
|