@julr/sesame 0.0.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 (69) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +130 -0
  3. package/build/authorize_controller-CUdEDNEi.js +136 -0
  4. package/build/client_info_controller-DeIVcW8B.js +18 -0
  5. package/build/client_service-B9fD3ZGe.js +53 -0
  6. package/build/commands/commands.json +1 -0
  7. package/build/commands/main.d.ts +4 -0
  8. package/build/commands/main.js +36 -0
  9. package/build/commands/sesame_purge.d.ts +21 -0
  10. package/build/commands/sesame_purge.js +28 -0
  11. package/build/configure.d.ts +2 -0
  12. package/build/configure.js +16 -0
  13. package/build/consent_controller-DFfx7qVs.js +87 -0
  14. package/build/decorate-2_8Ex77k.js +15 -0
  15. package/build/index.d.ts +14 -0
  16. package/build/index.js +27 -0
  17. package/build/introspect_controller-BzwfaUUE.js +63 -0
  18. package/build/main-kn40V-hF.js +2 -0
  19. package/build/metadata_controller-BSRRElQX.js +51 -0
  20. package/build/oauth_access_token-BpG8sq-c.js +18 -0
  21. package/build/oauth_client-eh0e5ql-.js +24 -0
  22. package/build/oauth_error-BQPqV-MV.js +78 -0
  23. package/build/providers/sesame_provider.d.ts +21 -0
  24. package/build/providers/sesame_provider.js +19 -0
  25. package/build/register_controller-BA7uQAgt.js +139 -0
  26. package/build/revoke_controller-CNIgNKH3.js +50 -0
  27. package/build/rolldown-runtime-BASaM9lw.js +12 -0
  28. package/build/routes-D6QCu0Pz.js +43 -0
  29. package/build/sesame_manager-B4tO2PLO.js +116 -0
  30. package/build/src/controllers/authorize_controller.d.ts +53 -0
  31. package/build/src/controllers/client_info_controller.d.ts +22 -0
  32. package/build/src/controllers/consent_controller.d.ts +27 -0
  33. package/build/src/controllers/introspect_controller.d.ts +28 -0
  34. package/build/src/controllers/metadata_controller.d.ts +64 -0
  35. package/build/src/controllers/register_controller.d.ts +91 -0
  36. package/build/src/controllers/revoke_controller.d.ts +16 -0
  37. package/build/src/controllers/token_controller.d.ts +24 -0
  38. package/build/src/decorators.d.ts +10 -0
  39. package/build/src/define_config.d.ts +16 -0
  40. package/build/src/grants/authorization_code_grant.d.ts +27 -0
  41. package/build/src/grants/refresh_token_grant.d.ts +27 -0
  42. package/build/src/guard/guard.d.ts +30 -0
  43. package/build/src/guard/main.d.ts +20 -0
  44. package/build/src/guard/main.js +17 -0
  45. package/build/src/guard/types.d.ts +46 -0
  46. package/build/src/guard/user_provider.d.ts +14 -0
  47. package/build/src/models/oauth_access_token.d.ts +23 -0
  48. package/build/src/models/oauth_authorization_code.d.ts +30 -0
  49. package/build/src/models/oauth_client.d.ts +33 -0
  50. package/build/src/models/oauth_consent.d.ts +22 -0
  51. package/build/src/models/oauth_refresh_token.d.ts +29 -0
  52. package/build/src/oauth_error.d.ts +359 -0
  53. package/build/src/routes.d.ts +28 -0
  54. package/build/src/rules.d.ts +12 -0
  55. package/build/src/services/client_service.d.ts +67 -0
  56. package/build/src/services/token_service.d.ts +42 -0
  57. package/build/src/sesame_manager.d.ts +66 -0
  58. package/build/src/types.d.ts +141 -0
  59. package/build/stubs/config/sesame.stub +30 -0
  60. package/build/stubs/main.d.ts +5 -0
  61. package/build/stubs/migrations/create_oauth_access_tokens_table.stub +25 -0
  62. package/build/stubs/migrations/create_oauth_authorization_codes_table.stub +27 -0
  63. package/build/stubs/migrations/create_oauth_clients_table.stub +31 -0
  64. package/build/stubs/migrations/create_oauth_consents_table.stub +24 -0
  65. package/build/stubs/migrations/create_oauth_refresh_tokens_table.stub +26 -0
  66. package/build/token_controller-C9wh813f.js +172 -0
  67. package/build/token_service-Czz9v5GI.js +30 -0
  68. package/build/user_provider-B3rXEUT3.js +150 -0
  69. package/package.json +144 -0
@@ -0,0 +1,63 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import { t as OAuthAccessToken } from "./oauth_access_token-BpG8sq-c.js";
3
+ import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ import { n as E_INVALID_CLIENT } from "./oauth_error-BQPqV-MV.js";
5
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
6
+ import { t as TokenService } from "./token_service-Czz9v5GI.js";
7
+ import { t as ClientService } from "./client_service-B9fD3ZGe.js";
8
+ const INACTIVE = { active: false };
9
+ var IntrospectController = class {
10
+ async handle(ctx) {
11
+ const manager = await ctx.containerResolver.make(SesameManager);
12
+ const clientService = new ClientService();
13
+ const credentials = clientService.extractCredentials({
14
+ authorizationHeader: ctx.request.header("authorization"),
15
+ bodyClientId: ctx.request.body().client_id,
16
+ bodyClientSecret: ctx.request.body().client_secret
17
+ });
18
+ if (!credentials) throw new E_INVALID_CLIENT("Client authentication required");
19
+ const client = await OAuthClient.query().where("clientId", credentials.clientId).first();
20
+ if (!client) throw new E_INVALID_CLIENT("Client not found");
21
+ if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
22
+ if (!client.isPublic) {
23
+ if (!credentials.clientSecret) throw new E_INVALID_CLIENT("Missing client secret");
24
+ if (!clientService.verifySecret(credentials.clientSecret, client.clientSecret)) throw new E_INVALID_CLIENT("Invalid client secret");
25
+ }
26
+ const token = ctx.request.body().token;
27
+ if (!token) return INACTIVE;
28
+ const tokenTypeHint = ctx.request.body().token_type_hint;
29
+ const hashed = new TokenService(manager).hashToken(token);
30
+ if (!tokenTypeHint || tokenTypeHint === "access_token") {
31
+ const record = await OAuthAccessToken.query().where("tokenHash", hashed).where("clientId", client.clientId).first();
32
+ if (record && !record.revokedAt && record.expiresAt.toJSDate() >= /* @__PURE__ */ new Date()) return {
33
+ active: true,
34
+ token_type: "Bearer",
35
+ client_id: record.clientId,
36
+ sub: record.userId || void 0,
37
+ scope: record.scopes.join(" "),
38
+ iss: manager.config.issuer,
39
+ iat: Math.floor(record.createdAt.toMillis() / 1e3),
40
+ exp: Math.floor(record.expiresAt.toMillis() / 1e3)
41
+ };
42
+ if (tokenTypeHint === "access_token") return INACTIVE;
43
+ }
44
+ if (!tokenTypeHint || tokenTypeHint === "refresh_token") {
45
+ const refreshToken = await OAuthRefreshToken.query().where("token", hashed).where("clientId", client.clientId).first();
46
+ if (!refreshToken) return INACTIVE;
47
+ if (refreshToken.revokedAt) return INACTIVE;
48
+ if (refreshToken.expiresAt.toJSDate() < /* @__PURE__ */ new Date()) return INACTIVE;
49
+ return {
50
+ active: true,
51
+ token_type: "refresh_token",
52
+ client_id: refreshToken.clientId,
53
+ sub: refreshToken.userId,
54
+ scope: refreshToken.scopes.join(" "),
55
+ iss: manager.config.issuer,
56
+ iat: Math.floor(refreshToken.createdAt.toMillis() / 1e3),
57
+ exp: Math.floor(refreshToken.expiresAt.toMillis() / 1e3)
58
+ };
59
+ }
60
+ return INACTIVE;
61
+ }
62
+ };
63
+ export { IntrospectController as default };
@@ -0,0 +1,2 @@
1
+ const stubsRoot = import.meta.dirname;
2
+ export { stubsRoot as t };
@@ -0,0 +1,51 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import "./oauth_access_token-BpG8sq-c.js";
3
+ import { t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ var MetadataController = class {
5
+ async authServer(ctx) {
6
+ const manager = await ctx.containerResolver.make(SesameManager);
7
+ const issuer = manager.config.issuer;
8
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
9
+ return {
10
+ issuer,
11
+ authorization_endpoint: `${issuer}/oauth/authorize`,
12
+ token_endpoint: `${issuer}/oauth/token`,
13
+ registration_endpoint: manager.config.allowDynamicRegistration ? `${issuer}/oauth/register` : void 0,
14
+ introspection_endpoint: `${issuer}/oauth/introspect`,
15
+ revocation_endpoint: `${issuer}/oauth/revoke`,
16
+ response_types_supported: ["code"],
17
+ response_modes_supported: ["query"],
18
+ grant_types_supported: manager.config.grantTypes,
19
+ token_endpoint_auth_methods_supported: [
20
+ "none",
21
+ "client_secret_basic",
22
+ "client_secret_post"
23
+ ],
24
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
25
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
26
+ code_challenge_methods_supported: ["S256"],
27
+ authorization_response_iss_parameter_supported: true
28
+ };
29
+ }
30
+ async oidc(ctx) {
31
+ const base = await this.authServer(ctx);
32
+ const manager = await ctx.containerResolver.make(SesameManager);
33
+ return {
34
+ ...base,
35
+ subject_types_supported: ["public"],
36
+ scopes_supported: Object.keys(manager.config.scopes)
37
+ };
38
+ }
39
+ async protectedResource(ctx) {
40
+ const manager = await ctx.containerResolver.make(SesameManager);
41
+ const issuer = manager.config.issuer;
42
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
43
+ return {
44
+ resource: issuer,
45
+ authorization_servers: [issuer],
46
+ scopes_supported: Object.keys(manager.config.scopes),
47
+ bearer_methods_supported: ["header"]
48
+ };
49
+ }
50
+ };
51
+ export { MetadataController as default };
@@ -0,0 +1,18 @@
1
+ import { n as json, t as __decorate } from "./decorate-2_8Ex77k.js";
2
+ import { BaseModel, column } from "@adonisjs/lucid/orm";
3
+ var OAuthAccessToken = class extends BaseModel {
4
+ static table = "oauth_access_tokens";
5
+ };
6
+ __decorate([column({ isPrimary: true })], OAuthAccessToken.prototype, "id", void 0);
7
+ __decorate([column()], OAuthAccessToken.prototype, "tokenHash", void 0);
8
+ __decorate([column()], OAuthAccessToken.prototype, "clientId", void 0);
9
+ __decorate([column()], OAuthAccessToken.prototype, "userId", void 0);
10
+ __decorate([json()], OAuthAccessToken.prototype, "scopes", void 0);
11
+ __decorate([column.dateTime()], OAuthAccessToken.prototype, "expiresAt", void 0);
12
+ __decorate([column.dateTime()], OAuthAccessToken.prototype, "revokedAt", void 0);
13
+ __decorate([column.dateTime({ autoCreate: true })], OAuthAccessToken.prototype, "createdAt", void 0);
14
+ __decorate([column.dateTime({
15
+ autoCreate: true,
16
+ autoUpdate: true
17
+ })], OAuthAccessToken.prototype, "updatedAt", void 0);
18
+ export { OAuthAccessToken as t };
@@ -0,0 +1,24 @@
1
+ import { n as json, t as __decorate } from "./decorate-2_8Ex77k.js";
2
+ import { BaseModel, column } from "@adonisjs/lucid/orm";
3
+ var OAuthClient = class extends BaseModel {
4
+ static table = "oauth_clients";
5
+ };
6
+ __decorate([column({ isPrimary: true })], OAuthClient.prototype, "id", void 0);
7
+ __decorate([column()], OAuthClient.prototype, "clientId", void 0);
8
+ __decorate([column({ serializeAs: null })], OAuthClient.prototype, "clientSecret", void 0);
9
+ __decorate([column()], OAuthClient.prototype, "name", void 0);
10
+ __decorate([json()], OAuthClient.prototype, "redirectUris", void 0);
11
+ __decorate([json()], OAuthClient.prototype, "scopes", void 0);
12
+ __decorate([json()], OAuthClient.prototype, "grantTypes", void 0);
13
+ __decorate([column()], OAuthClient.prototype, "isPublic", void 0);
14
+ __decorate([column()], OAuthClient.prototype, "isDisabled", void 0);
15
+ __decorate([column()], OAuthClient.prototype, "requirePkce", void 0);
16
+ __decorate([column()], OAuthClient.prototype, "type", void 0);
17
+ __decorate([json()], OAuthClient.prototype, "metadata", void 0);
18
+ __decorate([column()], OAuthClient.prototype, "userId", void 0);
19
+ __decorate([column.dateTime({ autoCreate: true })], OAuthClient.prototype, "createdAt", void 0);
20
+ __decorate([column.dateTime({
21
+ autoCreate: true,
22
+ autoUpdate: true
23
+ })], OAuthClient.prototype, "updatedAt", void 0);
24
+ export { OAuthClient as t };
@@ -0,0 +1,78 @@
1
+ import { Exception } from "@adonisjs/core/exceptions";
2
+ var OAuthError = class extends Exception {
3
+ static oauthCode;
4
+ get oauthCode() {
5
+ return this.constructor.oauthCode;
6
+ }
7
+ handle(error, ctx) {
8
+ ctx.response.status(error.status).json({
9
+ error: error.oauthCode,
10
+ error_description: error.message
11
+ });
12
+ }
13
+ };
14
+ const E_INVALID_REQUEST = class extends OAuthError {
15
+ static status = 400;
16
+ static code = "E_INVALID_REQUEST";
17
+ static message = "Invalid request";
18
+ static oauthCode = "invalid_request";
19
+ };
20
+ const E_INVALID_CLIENT = class extends OAuthError {
21
+ static status = 401;
22
+ static code = "E_INVALID_CLIENT";
23
+ static message = "Invalid client";
24
+ static oauthCode = "invalid_client";
25
+ handle(error, ctx) {
26
+ if (ctx.request.header("authorization")?.startsWith("Basic ")) ctx.response.header("WWW-Authenticate", "Basic");
27
+ super.handle(error, ctx);
28
+ }
29
+ };
30
+ const E_INVALID_GRANT = class extends OAuthError {
31
+ static status = 400;
32
+ static code = "E_INVALID_GRANT";
33
+ static message = "Invalid grant";
34
+ static oauthCode = "invalid_grant";
35
+ };
36
+ const E_INVALID_SCOPE = class extends OAuthError {
37
+ static status = 400;
38
+ static code = "E_INVALID_SCOPE";
39
+ static message = "Invalid scope";
40
+ static oauthCode = "invalid_scope";
41
+ };
42
+ const E_INVALID_TOKEN = class extends OAuthError {
43
+ static status = 401;
44
+ static code = "E_INVALID_TOKEN";
45
+ static message = "Invalid token";
46
+ static oauthCode = "invalid_token";
47
+ };
48
+ const E_UNSUPPORTED_GRANT_TYPE = class extends OAuthError {
49
+ static status = 400;
50
+ static code = "E_UNSUPPORTED_GRANT_TYPE";
51
+ static message = "Unsupported grant type";
52
+ static oauthCode = "unsupported_grant_type";
53
+ };
54
+ const E_UNSUPPORTED_RESPONSE_TYPE = class extends OAuthError {
55
+ static status = 400;
56
+ static code = "E_UNSUPPORTED_RESPONSE_TYPE";
57
+ static message = "Unsupported response type";
58
+ static oauthCode = "unsupported_response_type";
59
+ };
60
+ const E_ACCESS_DENIED = class extends OAuthError {
61
+ static status = 403;
62
+ static code = "E_ACCESS_DENIED";
63
+ static message = "Access denied";
64
+ static oauthCode = "access_denied";
65
+ };
66
+ const E_INVALID_CLIENT_METADATA = class extends OAuthError {
67
+ static status = 400;
68
+ static code = "E_INVALID_CLIENT_METADATA";
69
+ static message = "Invalid client metadata";
70
+ static oauthCode = "invalid_client_metadata";
71
+ };
72
+ const E_SERVER_ERROR = class extends OAuthError {
73
+ static status = 500;
74
+ static code = "E_SERVER_ERROR";
75
+ static message = "Server error";
76
+ static oauthCode = "server_error";
77
+ };
78
+ export { E_INVALID_REQUEST as a, E_SERVER_ERROR as c, OAuthError as d, E_INVALID_GRANT as i, E_UNSUPPORTED_GRANT_TYPE as l, E_INVALID_CLIENT as n, E_INVALID_SCOPE as o, E_INVALID_CLIENT_METADATA as r, E_INVALID_TOKEN as s, E_ACCESS_DENIED as t, E_UNSUPPORTED_RESPONSE_TYPE as u };
@@ -0,0 +1,21 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ /**
3
+ * AdonisJS service provider for the Sésame OAuth 2.1 server.
4
+ *
5
+ * - `register()`: Binds `SesameManager` as a singleton in the IoC
6
+ * container, reading the resolved config from `config/sesame.ts`.
7
+ * - `boot()`: Registers all OAuth routes on the AdonisJS router.
8
+ */
9
+ export default class SesameProvider {
10
+ protected app: ApplicationService;
11
+ constructor(app: ApplicationService);
12
+ /**
13
+ * Register `SesameManager` as a singleton binding.
14
+ * The manager is resolved from the `sesame` config key.
15
+ */
16
+ register(): void;
17
+ /**
18
+ * Boot the provider by registering all OAuth 2.1 routes.
19
+ */
20
+ boot(): Promise<void>;
21
+ }
@@ -0,0 +1,19 @@
1
+ import "../decorate-2_8Ex77k.js";
2
+ import "../oauth_access_token-BpG8sq-c.js";
3
+ import { t as SesameManager } from "../sesame_manager-B4tO2PLO.js";
4
+ var SesameProvider = class {
5
+ constructor(app) {
6
+ this.app = app;
7
+ }
8
+ register() {
9
+ this.app.container.singleton(SesameManager, () => {
10
+ return new SesameManager(this.app.config.get("sesame"));
11
+ });
12
+ }
13
+ async boot() {
14
+ const router = await this.app.container.make("router");
15
+ const { registerRoutes } = await import("../routes-D6QCu0Pz.js").then((n) => n.r);
16
+ registerRoutes(router);
17
+ }
18
+ };
19
+ export { SesameProvider as default };
@@ -0,0 +1,139 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import "./oauth_access_token-BpG8sq-c.js";
3
+ import { t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ import { a as E_INVALID_REQUEST, o as E_INVALID_SCOPE, r as E_INVALID_CLIENT_METADATA, t as E_ACCESS_DENIED } from "./oauth_error-BQPqV-MV.js";
5
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
6
+ import { t as ClientService } from "./client_service-B9fD3ZGe.js";
7
+ import vine from "@vinejs/vine";
8
+ const DANGEROUS_SCHEMES = [
9
+ "javascript:",
10
+ "data:",
11
+ "vbscript:"
12
+ ];
13
+ const LOCALHOST_HOSTS = [
14
+ "localhost",
15
+ "127.0.0.1",
16
+ "[::1]"
17
+ ];
18
+ const redirectUriRule = vine.createRule((value, _options, field) => {
19
+ let parsed;
20
+ try {
21
+ parsed = new URL(value);
22
+ } catch {
23
+ field.report("Invalid redirect URI", "redirectUri", field);
24
+ return;
25
+ }
26
+ if (parsed.hash) {
27
+ field.report("Redirect URI must not contain a fragment", "redirectUri", field);
28
+ return;
29
+ }
30
+ if (DANGEROUS_SCHEMES.includes(parsed.protocol)) {
31
+ field.report("Redirect URI uses a disallowed scheme", "redirectUri", field);
32
+ return;
33
+ }
34
+ if (parsed.protocol === "http:" && !LOCALHOST_HOSTS.includes(parsed.hostname)) field.report("Redirect URI must use HTTPS for non-localhost hosts", "redirectUri", field);
35
+ });
36
+ const metadataUriRule = vine.createRule((value, _options, field) => {
37
+ const parsed = new URL(value);
38
+ if (DANGEROUS_SCHEMES.includes(parsed.protocol)) {
39
+ field.report("{{ field }} uses a disallowed scheme", "metadataUri", field);
40
+ return;
41
+ }
42
+ const redirectUris = field.data.redirect_uris ?? [];
43
+ const redirectOrigins = new Set(redirectUris.map((u) => {
44
+ try {
45
+ return new URL(u);
46
+ } catch {
47
+ return null;
48
+ }
49
+ }).filter(Boolean).map((u) => `${u.protocol}//${u.hostname}`));
50
+ const metaOrigin = `${parsed.protocol}//${parsed.hostname}`;
51
+ if (!redirectOrigins.has(metaOrigin)) field.report("{{ field }} host and scheme must match at least one redirect_uri", "metadataUri", field);
52
+ });
53
+ const metadataUrl = vine.string().url({ require_protocol: true }).use(metadataUriRule()).optional();
54
+ var RegisterController = class RegisterController {
55
+ static validator = vine.create({
56
+ redirect_uris: vine.array(vine.string().use(redirectUriRule())).minLength(1),
57
+ token_endpoint_auth_method: vine.string().in([
58
+ "client_secret_basic",
59
+ "client_secret_post",
60
+ "none"
61
+ ]).optional(),
62
+ grant_types: vine.array(vine.string()).optional(),
63
+ response_types: vine.array(vine.string()).optional(),
64
+ scope: vine.string().optional(),
65
+ client_name: vine.string().maxLength(255).trim().optional(),
66
+ client_uri: metadataUrl,
67
+ logo_uri: metadataUrl,
68
+ tos_uri: metadataUrl,
69
+ policy_uri: metadataUrl,
70
+ contacts: vine.array(vine.string().email()).optional(),
71
+ software_id: vine.string().optional(),
72
+ software_version: vine.string().optional()
73
+ });
74
+ async handle(ctx) {
75
+ const manager = await ctx.containerResolver.make(SesameManager);
76
+ if (!manager.config.allowDynamicRegistration) throw new E_ACCESS_DENIED("Dynamic client registration is disabled");
77
+ const user = ctx.auth?.user;
78
+ if (!user && !manager.config.allowPublicRegistration) throw new E_INVALID_REQUEST("Authentication required for client registration");
79
+ let body;
80
+ try {
81
+ body = await RegisterController.validator.validate(ctx.request.body());
82
+ } catch {
83
+ throw new E_INVALID_CLIENT_METADATA("Invalid request body");
84
+ }
85
+ const clientService = new ClientService();
86
+ const tokenEndpointAuthMethod = body.token_endpoint_auth_method ?? "client_secret_basic";
87
+ const isPublic = tokenEndpointAuthMethod === "none";
88
+ const grantTypes = body.grant_types ?? ["authorization_code"];
89
+ const responseTypes = body.response_types ?? ["code"];
90
+ const scopes = body.scope ? body.scope.split(" ") : manager.config.defaultScopes;
91
+ const invalidScopes = manager.validateScopes(scopes);
92
+ if (invalidScopes.length > 0) throw new E_INVALID_SCOPE(`Unknown scopes: ${invalidScopes.join(", ")}`);
93
+ const clientName = body.client_name ?? "Unnamed Client";
94
+ for (const gt of grantTypes) if (!manager.isGrantTypeEnabled(gt)) throw new E_INVALID_CLIENT_METADATA(`Unsupported grant type: ${gt}`);
95
+ if (responseTypes.some((rt) => rt !== "code")) throw new E_INVALID_CLIENT_METADATA("Only \"code\" response type is supported");
96
+ const clientId = clientService.generateClientId();
97
+ const clientSecret = isPublic ? null : clientService.generateClientSecret();
98
+ const hashedSecret = clientSecret ? clientService.hashSecret(clientSecret) : null;
99
+ const metadata = {
100
+ token_endpoint_auth_method: tokenEndpointAuthMethod,
101
+ response_types: responseTypes,
102
+ ...body.client_uri ? { client_uri: body.client_uri } : {},
103
+ ...body.logo_uri ? { logo_uri: body.logo_uri } : {},
104
+ ...body.contacts ? { contacts: body.contacts } : {},
105
+ ...body.tos_uri ? { tos_uri: body.tos_uri } : {},
106
+ ...body.policy_uri ? { policy_uri: body.policy_uri } : {},
107
+ ...body.software_id ? { software_id: body.software_id } : {},
108
+ ...body.software_version ? { software_version: body.software_version } : {}
109
+ };
110
+ await OAuthClient.create({
111
+ id: crypto.randomUUID(),
112
+ clientId,
113
+ clientSecret: hashedSecret,
114
+ name: clientName,
115
+ redirectUris: body.redirect_uris,
116
+ scopes,
117
+ grantTypes,
118
+ isPublic,
119
+ isDisabled: false,
120
+ requirePkce: true,
121
+ type: isPublic ? "public" : "confidential",
122
+ metadata,
123
+ userId: user ? String(user.id) : null
124
+ });
125
+ ctx.response.header("Cache-Control", "no-store");
126
+ ctx.response.status(201);
127
+ return {
128
+ client_id: clientId,
129
+ ...clientSecret ? { client_secret: clientSecret } : {},
130
+ client_secret_expires_at: 0,
131
+ client_name: clientName,
132
+ redirect_uris: body.redirect_uris,
133
+ grant_types: grantTypes,
134
+ scope: scopes.join(" "),
135
+ ...metadata
136
+ };
137
+ }
138
+ };
139
+ export { RegisterController as default };
@@ -0,0 +1,50 @@
1
+ import "./decorate-2_8Ex77k.js";
2
+ import { t as OAuthAccessToken } from "./oauth_access_token-BpG8sq-c.js";
3
+ import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-B4tO2PLO.js";
4
+ import { n as E_INVALID_CLIENT } from "./oauth_error-BQPqV-MV.js";
5
+ import { t as OAuthClient } from "./oauth_client-eh0e5ql-.js";
6
+ import { t as TokenService } from "./token_service-Czz9v5GI.js";
7
+ import { t as ClientService } from "./client_service-B9fD3ZGe.js";
8
+ import { DateTime } from "luxon";
9
+ var RevokeController = class {
10
+ async handle(ctx) {
11
+ const manager = await ctx.containerResolver.make(SesameManager);
12
+ const clientService = new ClientService();
13
+ const credentials = clientService.extractCredentials({
14
+ authorizationHeader: ctx.request.header("authorization"),
15
+ bodyClientId: ctx.request.body().client_id,
16
+ bodyClientSecret: ctx.request.body().client_secret
17
+ });
18
+ if (!credentials) throw new E_INVALID_CLIENT("Client authentication required");
19
+ const client = await OAuthClient.query().where("clientId", credentials.clientId).first();
20
+ if (!client) throw new E_INVALID_CLIENT("Client not found");
21
+ if (client.isDisabled) throw new E_INVALID_CLIENT("Client is disabled");
22
+ if (!client.isPublic) {
23
+ if (!credentials.clientSecret) throw new E_INVALID_CLIENT("Missing client secret");
24
+ if (!clientService.verifySecret(credentials.clientSecret, client.clientSecret)) throw new E_INVALID_CLIENT("Invalid client secret");
25
+ }
26
+ const token = ctx.request.body().token;
27
+ if (!token) return ctx.response.ok({});
28
+ const tokenTypeHint = ctx.request.body().token_type_hint;
29
+ const hashed = new TokenService(manager).hashToken(token);
30
+ if (!tokenTypeHint || tokenTypeHint === "access_token") {
31
+ const record = await OAuthAccessToken.query().where("tokenHash", hashed).where("clientId", client.clientId).first();
32
+ if (record && !record.revokedAt) {
33
+ record.revokedAt = DateTime.now();
34
+ await record.save();
35
+ return ctx.response.ok({});
36
+ }
37
+ if (tokenTypeHint === "access_token") return ctx.response.ok({});
38
+ }
39
+ if (!tokenTypeHint || tokenTypeHint === "refresh_token") {
40
+ const refreshToken = await OAuthRefreshToken.query().where("token", hashed).where("clientId", client.clientId).first();
41
+ if (refreshToken && !refreshToken.revokedAt) {
42
+ refreshToken.revokedAt = DateTime.now();
43
+ await refreshToken.save();
44
+ await OAuthAccessToken.query().where("tokenHash", refreshToken.accessTokenId).whereNull("revokedAt").update({ revokedAt: DateTime.now().toSQL() });
45
+ }
46
+ }
47
+ return ctx.response.ok({});
48
+ }
49
+ };
50
+ export { RevokeController as default };
@@ -0,0 +1,12 @@
1
+ import "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ export { __exportAll as t };
@@ -0,0 +1,43 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-BASaM9lw.js";
2
+ var routes_exports = /* @__PURE__ */ __exportAll({
3
+ registerProtectedResource: () => registerProtectedResource,
4
+ registerRoutes: () => registerRoutes
5
+ });
6
+ const controllers = {
7
+ token: () => import("./token_controller-C9wh813f.js"),
8
+ authorize: () => import("./authorize_controller-CUdEDNEi.js"),
9
+ consent: () => import("./consent_controller-DFfx7qVs.js"),
10
+ introspect: () => import("./introspect_controller-BzwfaUUE.js"),
11
+ revoke: () => import("./revoke_controller-CNIgNKH3.js"),
12
+ register: () => import("./register_controller-BA7uQAgt.js"),
13
+ metadata: () => import("./metadata_controller-BSRRElQX.js"),
14
+ clientInfo: () => import("./client_info_controller-DeIVcW8B.js")
15
+ };
16
+ function registerRoutes(router) {
17
+ router.post("/oauth/token", [controllers.token]).as("sesame.token");
18
+ router.get("/oauth/authorize", [controllers.authorize]).as("sesame.authorize");
19
+ router.post("/oauth/consent", [controllers.consent]).as("sesame.consent");
20
+ router.get("/oauth/client-info", [controllers.clientInfo]).as("sesame.clientInfo");
21
+ router.post("/oauth/introspect", [controllers.introspect]).as("sesame.introspect");
22
+ router.post("/oauth/revoke", [controllers.revoke]).as("sesame.revoke");
23
+ router.post("/oauth/register", [controllers.register]).as("sesame.register");
24
+ router.get("/.well-known/oauth-authorization-server", [controllers.metadata, "authServer"]).as("sesame.metadata.authServer");
25
+ router.get("/.well-known/openid-configuration", [controllers.metadata, "oidc"]).as("sesame.metadata.oidc");
26
+ router.get("/.well-known/oauth-protected-resource", [controllers.metadata, "protectedResource"]).as("sesame.metadata.protectedResource");
27
+ }
28
+ function registerProtectedResource(router, options) {
29
+ const wellKnownPath = `/.well-known/oauth-protected-resource${options.resource}`;
30
+ router.get(wellKnownPath, async (ctx) => {
31
+ const { SesameManager } = await import("./sesame_manager-B4tO2PLO.js").then((n) => n.n);
32
+ const manager = await ctx.containerResolver.make(SesameManager);
33
+ const issuer = manager.config.issuer;
34
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
35
+ return {
36
+ resource: `${issuer}${options.resource}`,
37
+ authorization_servers: [issuer],
38
+ scopes_supported: options.scopes ?? Object.keys(manager.config.scopes),
39
+ bearer_methods_supported: ["header"]
40
+ };
41
+ });
42
+ }
43
+ export { registerRoutes as n, routes_exports as r, registerProtectedResource as t };
@@ -0,0 +1,116 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-BASaM9lw.js";
2
+ import { n as json, t as __decorate } from "./decorate-2_8Ex77k.js";
3
+ import { t as OAuthAccessToken } from "./oauth_access_token-BpG8sq-c.js";
4
+ import { DateTime } from "luxon";
5
+ import { BaseModel, column } from "@adonisjs/lucid/orm";
6
+ var OAuthRefreshToken = class extends BaseModel {
7
+ static table = "oauth_refresh_tokens";
8
+ };
9
+ __decorate([column({ isPrimary: true })], OAuthRefreshToken.prototype, "id", void 0);
10
+ __decorate([column({ serializeAs: null })], OAuthRefreshToken.prototype, "token", void 0);
11
+ __decorate([column()], OAuthRefreshToken.prototype, "accessTokenId", void 0);
12
+ __decorate([column()], OAuthRefreshToken.prototype, "clientId", void 0);
13
+ __decorate([column()], OAuthRefreshToken.prototype, "userId", void 0);
14
+ __decorate([json()], OAuthRefreshToken.prototype, "scopes", void 0);
15
+ __decorate([column.dateTime()], OAuthRefreshToken.prototype, "expiresAt", void 0);
16
+ __decorate([column.dateTime()], OAuthRefreshToken.prototype, "revokedAt", void 0);
17
+ __decorate([column.dateTime({ autoCreate: true })], OAuthRefreshToken.prototype, "createdAt", void 0);
18
+ __decorate([column.dateTime({
19
+ autoCreate: true,
20
+ autoUpdate: true
21
+ })], OAuthRefreshToken.prototype, "updatedAt", void 0);
22
+ var OAuthAuthorizationCode = class extends BaseModel {
23
+ static table = "oauth_authorization_codes";
24
+ };
25
+ __decorate([column({ isPrimary: true })], OAuthAuthorizationCode.prototype, "id", void 0);
26
+ __decorate([column()], OAuthAuthorizationCode.prototype, "code", void 0);
27
+ __decorate([column()], OAuthAuthorizationCode.prototype, "clientId", void 0);
28
+ __decorate([column()], OAuthAuthorizationCode.prototype, "userId", void 0);
29
+ __decorate([json()], OAuthAuthorizationCode.prototype, "scopes", void 0);
30
+ __decorate([column()], OAuthAuthorizationCode.prototype, "redirectUri", void 0);
31
+ __decorate([column()], OAuthAuthorizationCode.prototype, "codeChallenge", void 0);
32
+ __decorate([column()], OAuthAuthorizationCode.prototype, "codeChallengeMethod", void 0);
33
+ __decorate([column.dateTime()], OAuthAuthorizationCode.prototype, "expiresAt", void 0);
34
+ __decorate([column.dateTime({ autoCreate: true })], OAuthAuthorizationCode.prototype, "createdAt", void 0);
35
+ __decorate([column.dateTime({
36
+ autoCreate: true,
37
+ autoUpdate: true
38
+ })], OAuthAuthorizationCode.prototype, "updatedAt", void 0);
39
+ var OAuthConsent = class extends BaseModel {
40
+ static table = "oauth_consents";
41
+ };
42
+ __decorate([column({ isPrimary: true })], OAuthConsent.prototype, "id", void 0);
43
+ __decorate([column()], OAuthConsent.prototype, "clientId", void 0);
44
+ __decorate([column()], OAuthConsent.prototype, "userId", void 0);
45
+ __decorate([json()], OAuthConsent.prototype, "scopes", void 0);
46
+ __decorate([column.dateTime({ autoCreate: true })], OAuthConsent.prototype, "createdAt", void 0);
47
+ __decorate([column.dateTime({
48
+ autoCreate: true,
49
+ autoUpdate: true
50
+ })], OAuthConsent.prototype, "updatedAt", void 0);
51
+ var sesame_manager_exports = /* @__PURE__ */ __exportAll({ SesameManager: () => SesameManager });
52
+ var SesameManager = class {
53
+ #config;
54
+ constructor(config) {
55
+ this.#config = config;
56
+ }
57
+ get config() {
58
+ return this.#config;
59
+ }
60
+ hasScope(scope) {
61
+ return scope in this.#config.scopes;
62
+ }
63
+ validateScopes(scopes) {
64
+ if (Object.keys(this.#config.scopes).length === 0) return scopes;
65
+ return scopes.filter((s) => !this.hasScope(s));
66
+ }
67
+ isGrantTypeEnabled(grantType) {
68
+ return this.#config.grantTypes.includes(grantType);
69
+ }
70
+ async revokeAllForUser(userId) {
71
+ const now = DateTime.now();
72
+ await OAuthAccessToken.query().where("userId", userId).whereNull("revokedAt").update({ revokedAt: now.toSQL() });
73
+ await OAuthRefreshToken.query().where("userId", userId).whereNull("revokedAt").update({ revokedAt: now.toSQL() });
74
+ await OAuthAuthorizationCode.query().where("userId", userId).delete();
75
+ await OAuthConsent.query().where("userId", userId).delete();
76
+ }
77
+ async purgeTokens(options) {
78
+ const revokedOnly = options?.revokedOnly ?? false;
79
+ const expiredOnly = options?.expiredOnly ?? false;
80
+ const retentionHours = options?.retentionHours ?? 168;
81
+ const purgeRevoked = revokedOnly || !expiredOnly;
82
+ const purgeExpired = expiredOnly || !revokedOnly;
83
+ const cutoff = DateTime.now().minus({ hours: retentionHours });
84
+ let accessTokens = 0;
85
+ let refreshTokens = 0;
86
+ let authorizationCodes = 0;
87
+ if (purgeRevoked) {
88
+ accessTokens += await this.#deleteCount(OAuthAccessToken.query().whereNotNull("revokedAt").delete());
89
+ refreshTokens += await this.#deleteCount(OAuthRefreshToken.query().whereNotNull("revokedAt").delete());
90
+ }
91
+ if (purgeExpired) {
92
+ accessTokens += await this.#deleteCount(OAuthAccessToken.query().where("expiresAt", "<", cutoff.toSQL()).whereNull("revokedAt").delete());
93
+ refreshTokens += await this.#deleteCount(OAuthRefreshToken.query().where("expiresAt", "<", cutoff.toSQL()).whereNull("revokedAt").delete());
94
+ authorizationCodes += await this.#deleteCount(OAuthAuthorizationCode.query().where("expiresAt", "<", cutoff.toSQL()).delete());
95
+ }
96
+ return {
97
+ accessTokens,
98
+ refreshTokens,
99
+ authorizationCodes
100
+ };
101
+ }
102
+ #deleteCount(result) {
103
+ return result.then((r) => Array.isArray(r) ? Number(r[0] ?? 0) : Number(r));
104
+ }
105
+ parseTtl(ttl) {
106
+ const match = ttl.match(/^(\d+)(s|m|h|d)$/);
107
+ if (!match) throw new Error(`Invalid TTL format: ${ttl}`);
108
+ return Number.parseInt(match[1], 10) * {
109
+ s: 1,
110
+ m: 60,
111
+ h: 3600,
112
+ d: 86400
113
+ }[match[2]];
114
+ }
115
+ };
116
+ export { OAuthRefreshToken as a, OAuthAuthorizationCode as i, sesame_manager_exports as n, OAuthConsent as r, SesameManager as t };