@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.
- package/LICENSE.md +1 -1
- package/README.md +470 -64
- package/build/authorize_controller-sCZIjnc-.js +251 -0
- package/build/{client_info_controller-BucHGx4u.js → client_info_controller-B5A3tpbk.js} +11 -4
- package/build/commands/sesame_client.d.ts +20 -0
- package/build/commands/sesame_key.d.ts +12 -0
- package/build/commands/sesame_purge.d.ts +0 -2
- package/build/commands/sesame_purge.js +12 -3
- package/build/configure-t9q3KonY.js +31 -0
- package/build/configure.js +1 -24
- package/build/consent_controller-ChBboaSd.js +96 -0
- package/build/consent_retry-DMkfhmk9.js +41 -0
- package/build/{decorate-BKZEjPRg.js → decorate-RQD1h28J.js} +3 -9
- package/build/id_token_service-BtBUiL_n.js +54 -0
- package/build/index.d.ts +2 -8
- package/build/index.js +59 -10
- package/build/{introspect_controller-6bRt9sZt.js → introspect_controller-LVYGWhMj.js} +28 -10
- package/build/issue_authorization_code-Dr4-bbMp.js +39 -0
- package/build/jwks_controller-4HUVS_HC.js +22 -0
- package/build/{main-EbeMS5S9.js → main-Dx9kZv07.js} +35 -43
- package/build/{metadata_controller-DeaMRnUr.js → metadata_controller-hwfmxlF7.js} +81 -6
- package/build/oauth_error-CvxtZvjp.js +189 -0
- package/build/providers/sesame_provider.d.ts +1 -1
- package/build/providers/sesame_provider.js +17 -6
- package/build/{register_controller-sIJ1rxdM.js → register_controller-Cdk3c32j.js} +44 -8
- package/build/revoke_controller-Daz4Yqfs.js +48 -0
- package/build/services/main.js +3 -3
- package/build/sesame_manager-CeEmlzba.js +515 -0
- package/build/sesame_manager-DQxvsTEq.js +2 -0
- package/build/src/actions/authorize.d.ts +46 -0
- package/build/src/actions/exchange_authorization_code.d.ts +34 -0
- package/build/src/actions/exchange_client_credentials.d.ts +28 -0
- package/build/src/actions/exchange_refresh_token.d.ts +59 -0
- package/build/src/actions/issue_authorization_code.d.ts +26 -0
- package/build/src/controllers/authorize_controller.d.ts +18 -17
- package/build/src/controllers/consent_controller.d.ts +5 -0
- package/build/src/controllers/jwks_controller.d.ts +14 -0
- package/build/src/controllers/metadata_controller.d.ts +10 -3
- package/build/src/controllers/register_controller.d.ts +30 -30
- package/build/src/controllers/token_controller.d.ts +8 -5
- package/build/src/controllers/userinfo_controller.d.ts +14 -0
- 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 +10 -1
- package/build/src/middleware/scope_middleware.js +10 -1
- package/build/src/models/oauth_authorization_code.d.ts +1 -0
- package/build/src/models/oauth_pending_authorization_request.d.ts +1 -0
- package/build/src/oauth_error.d.ts +74 -74
- package/build/src/routes.d.ts +3 -1
- package/build/src/services/client_service.d.ts +5 -2
- package/build/src/services/id_token_service.d.ts +30 -0
- package/build/src/services/key_service.d.ts +20 -0
- package/build/src/sesame_manager.d.ts +56 -4
- 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 +120 -0
- 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/main.ts +5 -0
- package/build/stubs/migrations/create_oauth_authorization_codes_table.stub +1 -0
- package/build/stubs/migrations/create_oauth_pending_authorization_requests_table.stub +1 -0
- package/build/stubs/migrations/create_oauth_refresh_tokens_table.stub +1 -1
- package/build/stubs/migrations/kysely/create_oauth_tables.stub +151 -0
- package/build/taze.config.d.ts +2 -0
- package/build/token_controller-DRatCQNT.js +492 -0
- package/build/token_service-DDQ3Dxaj.js +59 -0
- package/build/types-DEtn2Zdb.js +75 -0
- package/build/userinfo_controller-YwN26wK-.js +37 -0
- package/build/vite.config.d.ts +2 -0
- package/package.json +55 -54
- package/build/authorize_controller-YUfAy-R2.js +0 -138
- package/build/client_service-WTNMqWzY.js +0 -65
- package/build/consent_controller-Dprwd1ed.js +0 -85
- package/build/oauth_access_token-bsoM5KeU.js +0 -18
- package/build/oauth_client-BIoY5jBR.js +0 -24
- package/build/oauth_error-CnJ3L8tf.js +0 -94
- package/build/revoke_controller-D6isoQCi.js +0 -41
- package/build/sesame_manager-Bu4MHqZV.js +0 -4
- package/build/sesame_manager-DwDZy5Vy.js +0 -167
- package/build/src/grants/authorization_code_grant.d.ts +0 -23
- package/build/src/grants/client_credentials_grant.d.ts +0 -23
- package/build/src/grants/refresh_token_grant.d.ts +0 -27
- package/build/token_controller-DzcrLMyS.js +0 -194
- package/build/token_service-fhoA4slP.js +0 -31
package/build/index.js
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import { configure } from "./configure.js";
|
|
2
|
-
import {
|
|
3
|
-
import "./
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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";
|
|
6
|
+
//#region src/define_config.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolve user-supplied `SesameConfig` into a `ResolvedSesameConfig`
|
|
9
|
+
* by applying sensible defaults for all optional fields.
|
|
10
|
+
*
|
|
11
|
+
* Defaults:
|
|
12
|
+
* - `scopes`: `{}`
|
|
13
|
+
* - `defaultScopes`: `[]`
|
|
14
|
+
* - `grantTypes`: `['authorization_code', 'refresh_token']`
|
|
15
|
+
* - `accessTokenTtl`: `'1h'`
|
|
16
|
+
* - `refreshTokenTtl`: `'30d'`
|
|
17
|
+
* - `authorizationCodeTtl`: `'10m'`
|
|
18
|
+
* - `allowDynamicRegistration`: `false`
|
|
19
|
+
* - `allowPublicRegistration`: `false`
|
|
20
|
+
*/
|
|
9
21
|
function defineConfig(config) {
|
|
22
|
+
if (!config.store) throw new InvalidArgumentsException("Missing \"store\" in Sesame config");
|
|
10
23
|
return {
|
|
24
|
+
store: config.store,
|
|
11
25
|
issuer: config.issuer,
|
|
12
26
|
scopes: config.scopes ?? {},
|
|
13
27
|
defaultScopes: config.defaultScopes ?? [],
|
|
@@ -15,12 +29,47 @@ function defineConfig(config) {
|
|
|
15
29
|
accessTokenTtl: config.accessTokenTtl ?? "1h",
|
|
16
30
|
clientCredentialsAccessTokenTtl: config.clientCredentialsAccessTokenTtl ?? config.accessTokenTtl ?? "1h",
|
|
17
31
|
refreshTokenTtl: config.refreshTokenTtl ?? "30d",
|
|
32
|
+
refreshTokenRotationGracePeriod: config.refreshTokenRotationGracePeriod ?? 120,
|
|
18
33
|
authorizationCodeTtl: config.authorizationCodeTtl ?? "10m",
|
|
19
34
|
authorizationRequestTtl: config.authorizationRequestTtl ?? config.authorizationCodeTtl ?? "10m",
|
|
20
35
|
loginPage: config.loginPage,
|
|
21
36
|
consentPage: config.consentPage,
|
|
22
37
|
allowDynamicRegistration: config.allowDynamicRegistration ?? false,
|
|
23
|
-
allowPublicRegistration: config.allowPublicRegistration ?? false
|
|
38
|
+
allowPublicRegistration: config.allowPublicRegistration ?? false,
|
|
39
|
+
jwk: config.jwk,
|
|
40
|
+
oidcProvider: config.oidcProvider,
|
|
41
|
+
idTokenTtl: config.idTokenTtl ?? "1h"
|
|
24
42
|
};
|
|
25
43
|
}
|
|
26
|
-
|
|
44
|
+
//#endregion
|
|
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,15 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
3
|
-
|
|
4
|
-
import "./oauth_error-CnJ3L8tf.js";
|
|
5
|
-
import "./oauth_client-BIoY5jBR.js";
|
|
6
|
-
import { t as TokenService } from "./token_service-fhoA4slP.js";
|
|
7
|
-
import { t as ClientService } from "./client_service-WTNMqWzY.js";
|
|
1
|
+
import { n as ClientService, t as SesameManager } from "./sesame_manager-CeEmlzba.js";
|
|
2
|
+
import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
|
|
3
|
+
//#region src/controllers/introspect_controller.ts
|
|
8
4
|
const INACTIVE = { active: false };
|
|
5
|
+
/**
|
|
6
|
+
* Handles the OAuth 2.0 Token Introspection Endpoint (RFC 7662).
|
|
7
|
+
*
|
|
8
|
+
* Allows an authenticated client to determine the active state and
|
|
9
|
+
* metadata of a token (access token or refresh token). The response
|
|
10
|
+
* always includes at least `{ active: boolean }`.
|
|
11
|
+
*
|
|
12
|
+
* Supports the `token_type_hint` parameter to optimize lookup order.
|
|
13
|
+
* Both access tokens and refresh tokens are opaque values looked up
|
|
14
|
+
* by their SHA-256 hash in the database.
|
|
15
|
+
*
|
|
16
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7662
|
|
17
|
+
*/
|
|
9
18
|
var IntrospectController = class {
|
|
10
19
|
async handle(ctx) {
|
|
11
20
|
const manager = await ctx.containerResolver.make(SesameManager);
|
|
12
|
-
const
|
|
21
|
+
const clientService = new ClientService(manager);
|
|
22
|
+
const store = manager.store;
|
|
23
|
+
const client = await clientService.authenticateClient({
|
|
13
24
|
authorizationHeader: ctx.request.header("authorization"),
|
|
14
25
|
bodyClientId: ctx.request.body().client_id,
|
|
15
26
|
bodyClientSecret: ctx.request.body().client_secret
|
|
@@ -19,7 +30,10 @@ var IntrospectController = class {
|
|
|
19
30
|
const tokenTypeHint = ctx.request.body().token_type_hint;
|
|
20
31
|
const hashed = new TokenService(manager).hashToken(token);
|
|
21
32
|
if (!tokenTypeHint || tokenTypeHint === "access_token") {
|
|
22
|
-
const record = await
|
|
33
|
+
const record = await store.findAccessToken({
|
|
34
|
+
hash: hashed,
|
|
35
|
+
clientId: client.clientId
|
|
36
|
+
});
|
|
23
37
|
if (record && !record.revokedAt && record.expiresAt.toJSDate() >= /* @__PURE__ */ new Date()) return {
|
|
24
38
|
active: true,
|
|
25
39
|
token_type: "Bearer",
|
|
@@ -33,7 +47,10 @@ var IntrospectController = class {
|
|
|
33
47
|
if (tokenTypeHint === "access_token") return INACTIVE;
|
|
34
48
|
}
|
|
35
49
|
if (!tokenTypeHint || tokenTypeHint === "refresh_token") {
|
|
36
|
-
const refreshToken = await
|
|
50
|
+
const refreshToken = await store.findRefreshToken({
|
|
51
|
+
hash: hashed,
|
|
52
|
+
clientId: client.clientId
|
|
53
|
+
});
|
|
37
54
|
if (!refreshToken) return INACTIVE;
|
|
38
55
|
if (refreshToken.revokedAt) return INACTIVE;
|
|
39
56
|
if (refreshToken.expiresAt.toJSDate() < /* @__PURE__ */ new Date()) return INACTIVE;
|
|
@@ -51,4 +68,5 @@ var IntrospectController = class {
|
|
|
51
68
|
return INACTIVE;
|
|
52
69
|
}
|
|
53
70
|
};
|
|
71
|
+
//#endregion
|
|
54
72
|
export { IntrospectController as default };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { t as TokenService } from "./token_service-DDQ3Dxaj.js";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
|
+
import string from "@adonisjs/core/helpers/string";
|
|
4
|
+
//#region src/actions/issue_authorization_code.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create and persist an authorization code in the database.
|
|
7
|
+
* Returns the raw (unhashed) code to be sent to the client.
|
|
8
|
+
*
|
|
9
|
+
* Shared between the authorize and consent flows to avoid
|
|
10
|
+
* duplicating the code-issuance logic.
|
|
11
|
+
*/
|
|
12
|
+
var IssueAuthorizationCodeAction = class {
|
|
13
|
+
/**
|
|
14
|
+
* Generate an opaque authorization code, store its SHA-256
|
|
15
|
+
* hash in the database, and return the raw value for the
|
|
16
|
+
* client redirect.
|
|
17
|
+
*/
|
|
18
|
+
async execute(manager, input) {
|
|
19
|
+
const tokenService = new TokenService(manager);
|
|
20
|
+
const raw = tokenService.generateOpaqueToken();
|
|
21
|
+
const hashed = tokenService.hashToken(raw);
|
|
22
|
+
const ttl = string.seconds.parse(manager.config.authorizationCodeTtl);
|
|
23
|
+
await manager.store.createAuthorizationCode({
|
|
24
|
+
id: crypto.randomUUID(),
|
|
25
|
+
code: hashed,
|
|
26
|
+
clientId: input.client.clientId,
|
|
27
|
+
userId: input.userId,
|
|
28
|
+
scopes: input.scopes,
|
|
29
|
+
redirectUri: input.redirectUri,
|
|
30
|
+
codeChallenge: input.codeChallenge ?? null,
|
|
31
|
+
codeChallengeMethod: input.codeChallengeMethod ?? null,
|
|
32
|
+
nonce: input.nonce ?? null,
|
|
33
|
+
expiresAt: DateTime.now().plus({ seconds: ttl })
|
|
34
|
+
});
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { IssueAuthorizationCodeAction as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
|
|
2
|
+
//#region src/controllers/jwks_controller.ts
|
|
3
|
+
/**
|
|
4
|
+
* Serves the JSON Web Key Set (JWKS) containing public keys
|
|
5
|
+
* used to verify ID token signatures.
|
|
6
|
+
*
|
|
7
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7517
|
|
8
|
+
*/
|
|
9
|
+
var JwksController = class {
|
|
10
|
+
async handle(ctx) {
|
|
11
|
+
const manager = await ctx.containerResolver.make(SesameManager);
|
|
12
|
+
if (!manager.isOidcEnabled) {
|
|
13
|
+
ctx.response.status(404);
|
|
14
|
+
return { error: "OIDC is not configured" };
|
|
15
|
+
}
|
|
16
|
+
ctx.response.header("Cache-Control", "public, max-age=900");
|
|
17
|
+
ctx.response.header("Content-Type", "application/jwk-set+json");
|
|
18
|
+
return manager.keyService.getPublicJwks();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { JwksController as default };
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
|
|
3
|
-
import { t as TokenService } from "./token_service-fhoA4slP.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";
|
|
4
|
+
//#region src/guard/guard.ts
|
|
5
|
+
/**
|
|
6
|
+
* OAuth 2.0 guard for `@adonisjs/auth`.
|
|
7
|
+
*
|
|
8
|
+
* Verifies opaque Bearer tokens against the database,
|
|
9
|
+
* checks revocation and expiry, loads the real User model
|
|
10
|
+
* via the provider, and exposes OAuth-specific data (scopes, clientId).
|
|
11
|
+
*/
|
|
7
12
|
var OAuthGuard = class {
|
|
8
13
|
driverName = "oauth";
|
|
9
14
|
authenticationAttempted = false;
|
|
@@ -58,7 +63,7 @@ var OAuthGuard = class {
|
|
|
58
63
|
const tokenService = new TokenService(this.#manager);
|
|
59
64
|
const includeError = { includeError: true };
|
|
60
65
|
const hashed = tokenService.hashToken(rawToken);
|
|
61
|
-
const record = await
|
|
66
|
+
const record = await this.#manager.store.findAccessToken({ hash: hashed });
|
|
62
67
|
if (!record) throw this.#authenticationFailed("Invalid or expired token", includeError);
|
|
63
68
|
if (record.revokedAt) throw this.#authenticationFailed("Invalid or expired token", includeError);
|
|
64
69
|
if (record.expiresAt.toJSDate() < /* @__PURE__ */ new Date()) throw this.#authenticationFailed("Invalid or expired token", includeError);
|
|
@@ -91,22 +96,36 @@ var OAuthGuard = class {
|
|
|
91
96
|
hasAnyScope(...scopes) {
|
|
92
97
|
return scopes.some((s) => this.scopes.includes(s));
|
|
93
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Used internally by Japa's `loginAs` helper during testing.
|
|
101
|
+
* Creates a test client and access token in DB, then returns
|
|
102
|
+
* the authorization headers for the test HTTP client to use.
|
|
103
|
+
*
|
|
104
|
+
* @see https://docs.adonisjs.com/guides/auth/custom-auth-guard#implementing-the-guard
|
|
105
|
+
*/
|
|
94
106
|
async authenticateAsClient(user) {
|
|
95
107
|
const tokenService = new TokenService(this.#manager);
|
|
96
108
|
const defaultScopes = this.#manager.config.defaultScopes;
|
|
97
|
-
const
|
|
109
|
+
const store = this.#manager.store;
|
|
110
|
+
let testClient = await store.findClient("__test_client__");
|
|
111
|
+
if (!testClient) testClient = await store.createClient({
|
|
98
112
|
id: crypto.randomUUID(),
|
|
99
113
|
clientId: "__test_client__",
|
|
114
|
+
clientSecret: null,
|
|
100
115
|
name: "Test Client",
|
|
101
116
|
redirectUris: ["http://localhost/callback"],
|
|
102
117
|
grantTypes: ["authorization_code"],
|
|
103
118
|
scopes: defaultScopes,
|
|
104
119
|
isPublic: true,
|
|
105
|
-
|
|
120
|
+
isDisabled: false,
|
|
121
|
+
requirePkce: false,
|
|
122
|
+
type: "public",
|
|
123
|
+
metadata: null,
|
|
124
|
+
userId: null
|
|
106
125
|
});
|
|
107
126
|
const userId = String(user.id ?? user.getId?.() ?? "test-user");
|
|
108
127
|
const { raw, hash, expiresAt } = tokenService.createAccessToken();
|
|
109
|
-
await
|
|
128
|
+
await store.createAccessToken({
|
|
110
129
|
id: crypto.randomUUID(),
|
|
111
130
|
tokenHash: hash,
|
|
112
131
|
clientId: testClient.clientId,
|
|
@@ -117,45 +136,18 @@ var OAuthGuard = class {
|
|
|
117
136
|
return { headers: { authorization: `Bearer ${raw}` } };
|
|
118
137
|
}
|
|
119
138
|
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
async #getModel() {
|
|
127
|
-
if (this.#model && !("hot" in import.meta)) return this.#model;
|
|
128
|
-
this.#model = (await this.#options.model()).default;
|
|
129
|
-
return this.#model;
|
|
130
|
-
}
|
|
131
|
-
async createUserForGuard(user) {
|
|
132
|
-
const model = await this.#getModel();
|
|
133
|
-
if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
|
|
134
|
-
return {
|
|
135
|
-
getId() {
|
|
136
|
-
if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`);
|
|
137
|
-
return user.$primaryKeyValue;
|
|
138
|
-
},
|
|
139
|
-
getOriginal() {
|
|
140
|
-
return user;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
async findById(identifier) {
|
|
145
|
-
const user = await (await this.#getModel()).find(identifier);
|
|
146
|
-
if (!user) return null;
|
|
147
|
-
return this.createUserForGuard(user);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/guard/main.ts
|
|
141
|
+
/**
|
|
142
|
+
* Configure the OAuth guard for `@adonisjs/auth`.
|
|
143
|
+
*/
|
|
150
144
|
function oauthGuard(config) {
|
|
151
145
|
return { async resolver(name, app) {
|
|
152
146
|
const emitter = await app.container.make("emitter");
|
|
153
|
-
const { SesameManager } = await import("./sesame_manager-
|
|
147
|
+
const { SesameManager } = await import("./sesame_manager-DQxvsTEq.js");
|
|
154
148
|
const manager = await app.container.make(SesameManager);
|
|
155
149
|
return (ctx) => new OAuthGuard(name, ctx, emitter, config.provider, manager, config.resource);
|
|
156
150
|
} };
|
|
157
151
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
export { OAuthGuard as i, oauthUserProvider as n, OAuthLucidUserProvider as r, oauthGuard as t };
|
|
152
|
+
//#endregion
|
|
153
|
+
export { OAuthGuard as n, oauthGuard as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
|
|
1
|
+
import { t as BUILTIN_SCOPES } from "./types-DEtn2Zdb.js";
|
|
2
|
+
import { l as E_SERVER_ERROR } from "./oauth_error-CvxtZvjp.js";
|
|
3
|
+
import { t as SesameManager } from "./sesame_manager-CeEmlzba.js";
|
|
4
|
+
//#region src/controllers/metadata_controller.ts
|
|
5
5
|
const DISCOVERY_ROUTE_NAMES = {
|
|
6
6
|
authorization_endpoint: "sesame.authorize",
|
|
7
7
|
token_endpoint: "sesame.token",
|
|
@@ -9,6 +9,22 @@ const DISCOVERY_ROUTE_NAMES = {
|
|
|
9
9
|
introspection_endpoint: "sesame.introspect",
|
|
10
10
|
revocation_endpoint: "sesame.revoke"
|
|
11
11
|
};
|
|
12
|
+
const OIDC_DISCOVERY_ROUTE_NAMES = {
|
|
13
|
+
userinfo_endpoint: "sesame.userinfo",
|
|
14
|
+
jwks_uri: "sesame.jwks"
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Serves OAuth 2.0 discovery metadata documents.
|
|
18
|
+
*
|
|
19
|
+
* Exposes three well-known endpoints:
|
|
20
|
+
* - `authServer()` — OAuth Authorization Server Metadata (RFC 8414)
|
|
21
|
+
* - `oidc()` — OpenID Connect Discovery 1.0 (extends authServer metadata)
|
|
22
|
+
* - `protectedResource()` — OAuth Protected Resource Metadata (RFC 9728)
|
|
23
|
+
*
|
|
24
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8414
|
|
25
|
+
* @see https://datatracker.ietf.org/doc/html/rfc9728
|
|
26
|
+
* @see https://openid.net/specs/openid-connect-discovery-1_0.html
|
|
27
|
+
*/
|
|
12
28
|
var MetadataController = class {
|
|
13
29
|
#assertDiscoveryRoutes(router, options) {
|
|
14
30
|
const requiredRoutes = [
|
|
@@ -21,6 +37,19 @@ var MetadataController = class {
|
|
|
21
37
|
const missingRoutes = requiredRoutes.filter((routeName) => !router.has(routeName));
|
|
22
38
|
if (missingRoutes.length > 0) throw new E_SERVER_ERROR(`OAuth discovery is misconfigured. Missing named route(s): ${missingRoutes.join(", ")}. Register OAuth routes with sesame.registerRoutes(router) before exposing well-known metadata.`);
|
|
23
39
|
}
|
|
40
|
+
#assertOidcDiscoveryRoutes(router) {
|
|
41
|
+
const missingRoutes = Object.values(OIDC_DISCOVERY_ROUTE_NAMES).filter((routeName) => !router.has(routeName));
|
|
42
|
+
if (missingRoutes.length > 0) throw new E_SERVER_ERROR(`OIDC discovery is misconfigured. Missing named route(s): ${missingRoutes.join(", ")}. Register OAuth routes with sesame.registerRoutes(router) and sesame.registerWellKnownRoutes(router) before exposing OpenID discovery.`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* OAuth 2.0 Authorization Server Metadata (RFC 8414).
|
|
46
|
+
*
|
|
47
|
+
* Returns a JSON document describing the server's endpoints,
|
|
48
|
+
* supported grant types, response types, authentication methods,
|
|
49
|
+
* and PKCE support.
|
|
50
|
+
*
|
|
51
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8414#section-2
|
|
52
|
+
*/
|
|
24
53
|
async authServer(ctx) {
|
|
25
54
|
const manager = await ctx.containerResolver.make(SesameManager);
|
|
26
55
|
const router = await ctx.containerResolver.make("router");
|
|
@@ -57,15 +86,60 @@ var MetadataController = class {
|
|
|
57
86
|
authorization_response_iss_parameter_supported: true
|
|
58
87
|
};
|
|
59
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* OpenID Connect Discovery 1.0 metadata.
|
|
91
|
+
*
|
|
92
|
+
* Extends the authorization server metadata with OIDC-specific
|
|
93
|
+
* fields like `subject_types_supported` and
|
|
94
|
+
* `id_token_signing_alg_values_supported`.
|
|
95
|
+
*
|
|
96
|
+
* @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
|
97
|
+
*/
|
|
60
98
|
async oidc(ctx) {
|
|
61
|
-
const base = await this.authServer(ctx);
|
|
62
99
|
const manager = await ctx.containerResolver.make(SesameManager);
|
|
100
|
+
if (!manager.isOidcEnabled) {
|
|
101
|
+
ctx.response.status(404);
|
|
102
|
+
return { error: "OIDC is not configured" };
|
|
103
|
+
}
|
|
104
|
+
const base = await this.authServer(ctx);
|
|
105
|
+
const router = await ctx.containerResolver.make("router");
|
|
106
|
+
const prefixUrl = manager.config.issuer;
|
|
107
|
+
this.#assertOidcDiscoveryRoutes(router);
|
|
63
108
|
return {
|
|
64
109
|
...base,
|
|
110
|
+
userinfo_endpoint: router.makeUrl("sesame.userinfo", {}, { prefixUrl }),
|
|
111
|
+
jwks_uri: router.makeUrl("sesame.jwks", {}, { prefixUrl }),
|
|
65
112
|
subject_types_supported: ["public"],
|
|
66
|
-
|
|
113
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
114
|
+
scopes_supported: [.../* @__PURE__ */ new Set([
|
|
115
|
+
"openid",
|
|
116
|
+
"profile",
|
|
117
|
+
"email",
|
|
118
|
+
...Object.keys(manager.config.scopes),
|
|
119
|
+
...BUILTIN_SCOPES
|
|
120
|
+
])],
|
|
121
|
+
claims_supported: [
|
|
122
|
+
"sub",
|
|
123
|
+
"iss",
|
|
124
|
+
"aud",
|
|
125
|
+
"exp",
|
|
126
|
+
"iat",
|
|
127
|
+
"nonce",
|
|
128
|
+
"at_hash"
|
|
129
|
+
],
|
|
130
|
+
response_types_supported: ["code"]
|
|
67
131
|
};
|
|
68
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* OAuth 2.0 Protected Resource Metadata (RFC 9728).
|
|
135
|
+
*
|
|
136
|
+
* Returns a JSON document that tells clients which authorization
|
|
137
|
+
* servers protect this resource, which scopes are available, and
|
|
138
|
+
* how to present bearer tokens. Used by MCP clients to discover
|
|
139
|
+
* the authorization server.
|
|
140
|
+
*
|
|
141
|
+
* @see https://datatracker.ietf.org/doc/html/rfc9728
|
|
142
|
+
*/
|
|
69
143
|
async protectedResource(ctx) {
|
|
70
144
|
const manager = await ctx.containerResolver.make(SesameManager);
|
|
71
145
|
const issuer = manager.config.issuer;
|
|
@@ -78,4 +152,5 @@ var MetadataController = class {
|
|
|
78
152
|
};
|
|
79
153
|
}
|
|
80
154
|
};
|
|
155
|
+
//#endregion
|
|
81
156
|
export { MetadataController as default };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Exception } from "@adonisjs/core/exceptions";
|
|
2
|
+
//#region src/oauth_error.ts
|
|
3
|
+
/**
|
|
4
|
+
* OAuth 2.0 error classes following the error codes defined in
|
|
5
|
+
* RFC 6749 §5.2 (https://datatracker.ietf.org/doc/html/rfc6749#section-5.2)
|
|
6
|
+
* and RFC 7591 §3.2.2 (https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2).
|
|
7
|
+
*
|
|
8
|
+
* Each error class extends AdonisJS Exception and self-renders via
|
|
9
|
+
* the `handle()` method, producing a standard OAuth JSON error response.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Base class for all OAuth errors. Extends AdonisJS Exception
|
|
13
|
+
* and renders the standard OAuth error response format:
|
|
14
|
+
* `{ error: "<oauth_code>", error_description: "<message>" }`.
|
|
15
|
+
*
|
|
16
|
+
* Subclasses declare a static `oauthCode` matching the `error` field
|
|
17
|
+
* defined by the OAuth 2.0 spec (e.g. `invalid_request`, `invalid_client`).
|
|
18
|
+
*/
|
|
19
|
+
var OAuthError = class extends Exception {
|
|
20
|
+
static oauthCode;
|
|
21
|
+
get oauthCode() {
|
|
22
|
+
return this.constructor.oauthCode;
|
|
23
|
+
}
|
|
24
|
+
handle(error, ctx) {
|
|
25
|
+
ctx.response.status(error.status).json({
|
|
26
|
+
error: error.oauthCode,
|
|
27
|
+
error_description: error.message
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The request is missing a required parameter, includes an unsupported
|
|
33
|
+
* parameter value, repeats a parameter, or is otherwise malformed.
|
|
34
|
+
*
|
|
35
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
36
|
+
*/
|
|
37
|
+
const E_INVALID_REQUEST = class extends OAuthError {
|
|
38
|
+
static status = 400;
|
|
39
|
+
static code = "E_INVALID_REQUEST";
|
|
40
|
+
static message = "Invalid request";
|
|
41
|
+
static oauthCode = "invalid_request";
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Client authentication failed (e.g. unknown client, no client
|
|
45
|
+
* authentication included, or unsupported authentication method).
|
|
46
|
+
*
|
|
47
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
48
|
+
*/
|
|
49
|
+
const E_INVALID_CLIENT = class extends OAuthError {
|
|
50
|
+
static status = 401;
|
|
51
|
+
static code = "E_INVALID_CLIENT";
|
|
52
|
+
static message = "Invalid client";
|
|
53
|
+
static oauthCode = "invalid_client";
|
|
54
|
+
/**
|
|
55
|
+
* If the client attempted to authenticate via the Authorization header
|
|
56
|
+
* (Basic auth), the server MUST include WWW-Authenticate: Basic.
|
|
57
|
+
*
|
|
58
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-3.2.4
|
|
59
|
+
*/
|
|
60
|
+
handle(error, ctx) {
|
|
61
|
+
if (ctx.request.header("authorization")?.startsWith("Basic ")) ctx.response.header("WWW-Authenticate", "Basic");
|
|
62
|
+
super.handle(error, ctx);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The provided authorization grant (authorization code, refresh token,
|
|
67
|
+
* resource owner credentials) is invalid, expired, revoked, or does
|
|
68
|
+
* not match the redirection URI used in the authorization request.
|
|
69
|
+
*
|
|
70
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
71
|
+
*/
|
|
72
|
+
const E_INVALID_GRANT = class extends OAuthError {
|
|
73
|
+
static status = 400;
|
|
74
|
+
static code = "E_INVALID_GRANT";
|
|
75
|
+
static message = "Invalid grant";
|
|
76
|
+
static oauthCode = "invalid_grant";
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The requested scope is invalid, unknown, malformed, or exceeds
|
|
80
|
+
* the scope granted by the resource owner.
|
|
81
|
+
*
|
|
82
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
83
|
+
*/
|
|
84
|
+
const E_INVALID_SCOPE = class extends OAuthError {
|
|
85
|
+
static status = 400;
|
|
86
|
+
static code = "E_INVALID_SCOPE";
|
|
87
|
+
static message = "Invalid scope";
|
|
88
|
+
static oauthCode = "invalid_scope";
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The access token provided is expired, revoked, malformed,
|
|
92
|
+
* or invalid for other reasons.
|
|
93
|
+
*
|
|
94
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
95
|
+
*/
|
|
96
|
+
const E_INVALID_TOKEN = class extends OAuthError {
|
|
97
|
+
static status = 401;
|
|
98
|
+
static code = "E_INVALID_TOKEN";
|
|
99
|
+
static message = "Invalid token";
|
|
100
|
+
static oauthCode = "invalid_token";
|
|
101
|
+
handle(error, ctx) {
|
|
102
|
+
ctx.response.header("WWW-Authenticate", `Bearer error="invalid_token", error_description="${error.message}"`);
|
|
103
|
+
super.handle(error, ctx);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The authorization grant type is not supported by the
|
|
108
|
+
* authorization server.
|
|
109
|
+
*
|
|
110
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
111
|
+
*/
|
|
112
|
+
const E_UNSUPPORTED_GRANT_TYPE = class extends OAuthError {
|
|
113
|
+
static status = 400;
|
|
114
|
+
static code = "E_UNSUPPORTED_GRANT_TYPE";
|
|
115
|
+
static message = "Unsupported grant type";
|
|
116
|
+
static oauthCode = "unsupported_grant_type";
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* The authorization server does not support obtaining an
|
|
120
|
+
* authorization code using this response type.
|
|
121
|
+
*
|
|
122
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
|
123
|
+
*/
|
|
124
|
+
const E_UNSUPPORTED_RESPONSE_TYPE = class extends OAuthError {
|
|
125
|
+
static status = 400;
|
|
126
|
+
static code = "E_UNSUPPORTED_RESPONSE_TYPE";
|
|
127
|
+
static message = "Unsupported response type";
|
|
128
|
+
static oauthCode = "unsupported_response_type";
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* The resource owner or authorization server denied the request.
|
|
132
|
+
*
|
|
133
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
|
134
|
+
*/
|
|
135
|
+
const E_ACCESS_DENIED = class extends OAuthError {
|
|
136
|
+
static status = 403;
|
|
137
|
+
static code = "E_ACCESS_DENIED";
|
|
138
|
+
static message = "Access denied";
|
|
139
|
+
static oauthCode = "access_denied";
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* The client metadata value is invalid, as defined in the dynamic
|
|
143
|
+
* client registration protocol.
|
|
144
|
+
*
|
|
145
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2
|
|
146
|
+
*/
|
|
147
|
+
const E_INVALID_CLIENT_METADATA = class extends OAuthError {
|
|
148
|
+
static status = 400;
|
|
149
|
+
static code = "E_INVALID_CLIENT_METADATA";
|
|
150
|
+
static message = "Invalid client metadata";
|
|
151
|
+
static oauthCode = "invalid_client_metadata";
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* The authorization server encountered an unexpected condition
|
|
155
|
+
* that prevented it from fulfilling the request.
|
|
156
|
+
*
|
|
157
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
|
158
|
+
*/
|
|
159
|
+
const E_SERVER_ERROR = class extends OAuthError {
|
|
160
|
+
static status = 500;
|
|
161
|
+
static code = "E_SERVER_ERROR";
|
|
162
|
+
static message = "Server error";
|
|
163
|
+
static oauthCode = "server_error";
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* The access token does not have the required scope(s) to access
|
|
167
|
+
* the protected resource. Returns 403 with a WWW-Authenticate header
|
|
168
|
+
* per RFC 6750 §3.1.
|
|
169
|
+
*
|
|
170
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
171
|
+
*/
|
|
172
|
+
const E_INSUFFICIENT_SCOPE = class extends OAuthError {
|
|
173
|
+
static status = 403;
|
|
174
|
+
static code = "E_INSUFFICIENT_SCOPE";
|
|
175
|
+
static message = "Insufficient scope";
|
|
176
|
+
static oauthCode = "insufficient_scope";
|
|
177
|
+
missingScopes;
|
|
178
|
+
constructor(missingScopes, message) {
|
|
179
|
+
super(message ?? "The token does not have the required scope(s)");
|
|
180
|
+
this.missingScopes = missingScopes;
|
|
181
|
+
}
|
|
182
|
+
handle(error, ctx) {
|
|
183
|
+
const scope = error.missingScopes.join(" ");
|
|
184
|
+
ctx.response.header("WWW-Authenticate", `Bearer error="insufficient_scope", error_description="${error.message}", scope="${scope}"`);
|
|
185
|
+
super.handle(error, ctx);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
export { E_INVALID_GRANT as a, E_INVALID_TOKEN as c, E_UNSUPPORTED_RESPONSE_TYPE as d, E_INVALID_CLIENT_METADATA as i, E_SERVER_ERROR as l, E_INSUFFICIENT_SCOPE as n, E_INVALID_REQUEST as o, E_INVALID_CLIENT as r, E_INVALID_SCOPE as s, E_ACCESS_DENIED as t, E_UNSUPPORTED_GRANT_TYPE as u };
|
|
@@ -3,7 +3,7 @@ import type { ApplicationService } from '@adonisjs/core/types';
|
|
|
3
3
|
* AdonisJS service provider for the Sésame OAuth 2.1 server.
|
|
4
4
|
*/
|
|
5
5
|
export default class SesameProvider {
|
|
6
|
-
|
|
6
|
+
#private;
|
|
7
7
|
constructor(app: ApplicationService);
|
|
8
8
|
/**
|
|
9
9
|
* Register `SesameManager` as a singleton binding.
|