@julr/sesame 0.1.0 → 0.2.1

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 (43) hide show
  1. package/README.md +167 -86
  2. package/build/{authorize_controller-CUdEDNEi.js → authorize_controller-BNWhlPZQ.js} +21 -20
  3. package/build/{client_info_controller-DeIVcW8B.js → client_info_controller-BucHGx4u.js} +3 -3
  4. package/build/{client_service-B9fD3ZGe.js → client_service-BqPSlaTS.js} +1 -1
  5. package/build/commands/sesame_purge.js +3 -3
  6. package/build/configure.js +11 -2
  7. package/build/{consent_controller-DFfx7qVs.js → consent_controller-COvvkpHM.js} +28 -30
  8. package/build/index.d.ts +1 -3
  9. package/build/index.js +9 -10
  10. package/build/{introspect_controller-BzwfaUUE.js → introspect_controller-JjAAXFIV.js} +7 -7
  11. package/build/metadata_controller-BzCjyqUG.js +73 -0
  12. package/build/{oauth_access_token-BpG8sq-c.js → oauth_access_token-bsoM5KeU.js} +1 -1
  13. package/build/{oauth_client-eh0e5ql-.js → oauth_client-BIoY5jBR.js} +1 -1
  14. package/build/{oauth_error-BQPqV-MV.js → oauth_error-CnJ3L8tf.js} +17 -1
  15. package/build/providers/sesame_provider.d.ts +0 -8
  16. package/build/providers/sesame_provider.js +3 -8
  17. package/build/{register_controller-BA7uQAgt.js → register_controller-DOlN9wNl.js} +6 -6
  18. package/build/{revoke_controller-CNIgNKH3.js → revoke_controller-B281b8ZO.js} +7 -7
  19. package/build/{sesame_manager-B4tO2PLO.js → sesame_manager-CFq4VEIZ.js} +75 -15
  20. package/build/src/controllers/metadata_controller.d.ts +1 -0
  21. package/build/src/define_config.d.ts +6 -1
  22. package/build/src/guard/guard.d.ts +4 -3
  23. package/build/src/guard/main.js +6 -6
  24. package/build/src/middleware/any_scope_middleware.d.ts +15 -0
  25. package/build/src/middleware/any_scope_middleware.js +18 -0
  26. package/build/src/middleware/scope_middleware.d.ts +15 -0
  27. package/build/src/middleware/scope_middleware.js +18 -0
  28. package/build/src/models/oauth_pending_authorization_request.d.ts +29 -0
  29. package/build/src/oauth_error.d.ts +32 -0
  30. package/build/src/routes.d.ts +21 -20
  31. package/build/src/sesame_manager.d.ts +39 -8
  32. package/build/src/types.d.ts +34 -0
  33. package/build/stubs/config/sesame.stub +6 -1
  34. package/build/stubs/migrations/create_oauth_pending_authorization_requests_table.stub +32 -0
  35. package/build/{token_controller-C9wh813f.js → token_controller-ll9sjcvn.js} +12 -11
  36. package/build/{token_service-Czz9v5GI.js → token_service-fhoA4slP.js} +2 -1
  37. package/build/{user_provider-B3rXEUT3.js → user_provider-DXAOfv8-.js} +3 -3
  38. package/package.json +7 -2
  39. package/build/main-kn40V-hF.js +0 -2
  40. package/build/metadata_controller-BSRRElQX.js +0 -51
  41. package/build/rolldown-runtime-BASaM9lw.js +0 -12
  42. package/build/routes-D6QCu0Pz.js +0 -43
  43. /package/build/{decorate-2_8Ex77k.js → decorate-BKZEjPRg.js} +0 -0
@@ -0,0 +1,73 @@
1
+ import { t as SesameManager } from "./sesame_manager-CFq4VEIZ.js";
2
+ import "./decorate-BKZEjPRg.js";
3
+ import "./oauth_access_token-bsoM5KeU.js";
4
+ import { l as E_SERVER_ERROR } from "./oauth_error-CnJ3L8tf.js";
5
+ const DISCOVERY_ROUTE_NAMES = {
6
+ authorization_endpoint: "sesame.authorize",
7
+ token_endpoint: "sesame.token",
8
+ registration_endpoint: "sesame.register",
9
+ introspection_endpoint: "sesame.introspect",
10
+ revocation_endpoint: "sesame.revoke"
11
+ };
12
+ var MetadataController = class {
13
+ #assertDiscoveryRoutes(router, options) {
14
+ const requiredRoutes = [
15
+ DISCOVERY_ROUTE_NAMES.authorization_endpoint,
16
+ DISCOVERY_ROUTE_NAMES.token_endpoint,
17
+ DISCOVERY_ROUTE_NAMES.introspection_endpoint,
18
+ DISCOVERY_ROUTE_NAMES.revocation_endpoint
19
+ ];
20
+ if (options.includeRegistration) requiredRoutes.push(DISCOVERY_ROUTE_NAMES.registration_endpoint);
21
+ const missingRoutes = requiredRoutes.filter((routeName) => !router.has(routeName));
22
+ 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
+ }
24
+ async authServer(ctx) {
25
+ const manager = await ctx.containerResolver.make(SesameManager);
26
+ const router = await ctx.containerResolver.make("router");
27
+ const issuer = manager.config.issuer;
28
+ const prefixUrl = issuer;
29
+ this.#assertDiscoveryRoutes(router, { includeRegistration: manager.config.allowDynamicRegistration });
30
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
31
+ return {
32
+ issuer,
33
+ authorization_endpoint: router.makeUrl("sesame.authorize", {}, { prefixUrl }),
34
+ token_endpoint: router.makeUrl("sesame.token", {}, { prefixUrl }),
35
+ registration_endpoint: manager.config.allowDynamicRegistration ? router.makeUrl("sesame.register", {}, { prefixUrl }) : void 0,
36
+ introspection_endpoint: router.makeUrl("sesame.introspect", {}, { prefixUrl }),
37
+ revocation_endpoint: router.makeUrl("sesame.revoke", {}, { prefixUrl }),
38
+ response_types_supported: ["code"],
39
+ response_modes_supported: ["query"],
40
+ grant_types_supported: manager.config.grantTypes,
41
+ token_endpoint_auth_methods_supported: [
42
+ "none",
43
+ "client_secret_basic",
44
+ "client_secret_post"
45
+ ],
46
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
47
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
48
+ code_challenge_methods_supported: ["S256"],
49
+ authorization_response_iss_parameter_supported: true
50
+ };
51
+ }
52
+ async oidc(ctx) {
53
+ const base = await this.authServer(ctx);
54
+ const manager = await ctx.containerResolver.make(SesameManager);
55
+ return {
56
+ ...base,
57
+ subject_types_supported: ["public"],
58
+ scopes_supported: Object.keys(manager.config.scopes)
59
+ };
60
+ }
61
+ async protectedResource(ctx) {
62
+ const manager = await ctx.containerResolver.make(SesameManager);
63
+ const issuer = manager.config.issuer;
64
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
65
+ return {
66
+ resource: issuer,
67
+ authorization_servers: [issuer],
68
+ scopes_supported: Object.keys(manager.config.scopes),
69
+ bearer_methods_supported: ["header"]
70
+ };
71
+ }
72
+ };
73
+ export { MetadataController as default };
@@ -1,4 +1,4 @@
1
- import { n as json, t as __decorate } from "./decorate-2_8Ex77k.js";
1
+ import { n as json, t as __decorate } from "./decorate-BKZEjPRg.js";
2
2
  import { BaseModel, column } from "@adonisjs/lucid/orm";
3
3
  var OAuthAccessToken = class extends BaseModel {
4
4
  static table = "oauth_access_tokens";
@@ -1,4 +1,4 @@
1
- import { n as json, t as __decorate } from "./decorate-2_8Ex77k.js";
1
+ import { n as json, t as __decorate } from "./decorate-BKZEjPRg.js";
2
2
  import { BaseModel, column } from "@adonisjs/lucid/orm";
3
3
  var OAuthClient = class extends BaseModel {
4
4
  static table = "oauth_clients";
@@ -75,4 +75,20 @@ const E_SERVER_ERROR = class extends OAuthError {
75
75
  static message = "Server error";
76
76
  static oauthCode = "server_error";
77
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 };
78
+ const E_INSUFFICIENT_SCOPE = class extends OAuthError {
79
+ static status = 403;
80
+ static code = "E_INSUFFICIENT_SCOPE";
81
+ static message = "Insufficient scope";
82
+ static oauthCode = "insufficient_scope";
83
+ missingScopes;
84
+ constructor(missingScopes, message) {
85
+ super(message ?? "The token does not have the required scope(s)");
86
+ this.missingScopes = missingScopes;
87
+ }
88
+ handle(error, ctx) {
89
+ const scope = error.missingScopes.join(" ");
90
+ ctx.response.header("WWW-Authenticate", `Bearer error="insufficient_scope", error_description="${error.message}", scope="${scope}"`);
91
+ super.handle(error, ctx);
92
+ }
93
+ };
94
+ export { E_INVALID_GRANT as a, E_INVALID_TOKEN as c, E_UNSUPPORTED_RESPONSE_TYPE as d, OAuthError as f, 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 };
@@ -1,10 +1,6 @@
1
1
  import type { ApplicationService } from '@adonisjs/core/types';
2
2
  /**
3
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
4
  */
9
5
  export default class SesameProvider {
10
6
  protected app: ApplicationService;
@@ -14,8 +10,4 @@ export default class SesameProvider {
14
10
  * The manager is resolved from the `sesame` config key.
15
11
  */
16
12
  register(): void;
17
- /**
18
- * Boot the provider by registering all OAuth 2.1 routes.
19
- */
20
- boot(): Promise<void>;
21
13
  }
@@ -1,6 +1,6 @@
1
- import "../decorate-2_8Ex77k.js";
2
- import "../oauth_access_token-BpG8sq-c.js";
3
- import { t as SesameManager } from "../sesame_manager-B4tO2PLO.js";
1
+ import { t as SesameManager } from "../sesame_manager-CFq4VEIZ.js";
2
+ import "../decorate-BKZEjPRg.js";
3
+ import "../oauth_access_token-bsoM5KeU.js";
4
4
  var SesameProvider = class {
5
5
  constructor(app) {
6
6
  this.app = app;
@@ -10,10 +10,5 @@ var SesameProvider = class {
10
10
  return new SesameManager(this.app.config.get("sesame"));
11
11
  });
12
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
13
  };
19
14
  export { SesameProvider as default };
@@ -1,9 +1,9 @@
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";
1
+ import { t as SesameManager } from "./sesame_manager-CFq4VEIZ.js";
2
+ import "./decorate-BKZEjPRg.js";
3
+ import "./oauth_access_token-bsoM5KeU.js";
4
+ import { i as E_INVALID_CLIENT_METADATA, o as E_INVALID_REQUEST, s as E_INVALID_SCOPE, t as E_ACCESS_DENIED } from "./oauth_error-CnJ3L8tf.js";
5
+ import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
6
+ import { t as ClientService } from "./client_service-BqPSlaTS.js";
7
7
  import vine from "@vinejs/vine";
8
8
  const DANGEROUS_SCHEMES = [
9
9
  "javascript:",
@@ -1,10 +1,10 @@
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";
1
+ import { o as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-CFq4VEIZ.js";
2
+ import "./decorate-BKZEjPRg.js";
3
+ import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
4
+ import { r as E_INVALID_CLIENT } from "./oauth_error-CnJ3L8tf.js";
5
+ import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
6
+ import { t as TokenService } from "./token_service-fhoA4slP.js";
7
+ import { t as ClientService } from "./client_service-BqPSlaTS.js";
8
8
  import { DateTime } from "luxon";
9
9
  var RevokeController = class {
10
10
  async handle(ctx) {
@@ -1,8 +1,42 @@
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";
1
+ import { n as json, t as __decorate } from "./decorate-BKZEjPRg.js";
2
+ import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
3
+ import "node:module";
4
4
  import { DateTime } from "luxon";
5
5
  import { BaseModel, column } from "@adonisjs/lucid/orm";
6
+ var __defProp = Object.defineProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
+ return target;
15
+ };
16
+ const controllers = {
17
+ token: () => import("./token_controller-ll9sjcvn.js"),
18
+ authorize: () => import("./authorize_controller-BNWhlPZQ.js"),
19
+ consent: () => import("./consent_controller-COvvkpHM.js"),
20
+ introspect: () => import("./introspect_controller-JjAAXFIV.js"),
21
+ revoke: () => import("./revoke_controller-B281b8ZO.js"),
22
+ register: () => import("./register_controller-DOlN9wNl.js"),
23
+ metadata: () => import("./metadata_controller-BzCjyqUG.js"),
24
+ clientInfo: () => import("./client_info_controller-BucHGx4u.js")
25
+ };
26
+ function registerOAuthRoutes(router) {
27
+ router.post("/token", [controllers.token]).as("sesame.token");
28
+ router.get("/authorize", [controllers.authorize]).as("sesame.authorize");
29
+ router.post("/consent", [controllers.consent]).as("sesame.consent");
30
+ router.get("/client-info", [controllers.clientInfo]).as("sesame.clientInfo");
31
+ router.post("/introspect", [controllers.introspect]).as("sesame.introspect");
32
+ router.post("/revoke", [controllers.revoke]).as("sesame.revoke");
33
+ router.post("/register", [controllers.register]).as("sesame.register");
34
+ }
35
+ function registerWellKnownRoutes(router) {
36
+ router.get("/.well-known/oauth-authorization-server", [controllers.metadata, "authServer"]).as("sesame.metadata.authServer");
37
+ router.get("/.well-known/openid-configuration", [controllers.metadata, "oidc"]).as("sesame.metadata.oidc");
38
+ router.get("/.well-known/oauth-protected-resource", [controllers.metadata, "protectedResource"]).as("sesame.metadata.protectedResource");
39
+ }
6
40
  var OAuthRefreshToken = class extends BaseModel {
7
41
  static table = "oauth_refresh_tokens";
8
42
  };
@@ -48,6 +82,20 @@ __decorate([column.dateTime({
48
82
  autoCreate: true,
49
83
  autoUpdate: true
50
84
  })], OAuthConsent.prototype, "updatedAt", void 0);
85
+ var OAuthPendingAuthorizationRequest = class extends BaseModel {
86
+ static table = "oauth_pending_authorization_requests";
87
+ };
88
+ __decorate([column({ isPrimary: true })], OAuthPendingAuthorizationRequest.prototype, "id", void 0);
89
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "token", void 0);
90
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "userId", void 0);
91
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "clientId", void 0);
92
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "redirectUri", void 0);
93
+ __decorate([json()], OAuthPendingAuthorizationRequest.prototype, "scopes", void 0);
94
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "state", void 0);
95
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "codeChallenge", void 0);
96
+ __decorate([column()], OAuthPendingAuthorizationRequest.prototype, "codeChallengeMethod", void 0);
97
+ __decorate([column.dateTime()], OAuthPendingAuthorizationRequest.prototype, "expiresAt", void 0);
98
+ __decorate([column.dateTime({ autoCreate: true })], OAuthPendingAuthorizationRequest.prototype, "createdAt", void 0);
51
99
  var sesame_manager_exports = /* @__PURE__ */ __exportAll({ SesameManager: () => SesameManager });
52
100
  var SesameManager = class {
53
101
  #config;
@@ -72,6 +120,7 @@ var SesameManager = class {
72
120
  await OAuthAccessToken.query().where("userId", userId).whereNull("revokedAt").update({ revokedAt: now.toSQL() });
73
121
  await OAuthRefreshToken.query().where("userId", userId).whereNull("revokedAt").update({ revokedAt: now.toSQL() });
74
122
  await OAuthAuthorizationCode.query().where("userId", userId).delete();
123
+ await OAuthPendingAuthorizationRequest.query().where("userId", userId).delete();
75
124
  await OAuthConsent.query().where("userId", userId).delete();
76
125
  }
77
126
  async purgeTokens(options) {
@@ -84,6 +133,7 @@ var SesameManager = class {
84
133
  let accessTokens = 0;
85
134
  let refreshTokens = 0;
86
135
  let authorizationCodes = 0;
136
+ let pendingRequests = 0;
87
137
  if (purgeRevoked) {
88
138
  accessTokens += await this.#deleteCount(OAuthAccessToken.query().whereNotNull("revokedAt").delete());
89
139
  refreshTokens += await this.#deleteCount(OAuthRefreshToken.query().whereNotNull("revokedAt").delete());
@@ -93,24 +143,34 @@ var SesameManager = class {
93
143
  refreshTokens += await this.#deleteCount(OAuthRefreshToken.query().where("expiresAt", "<", cutoff.toSQL()).whereNull("revokedAt").delete());
94
144
  authorizationCodes += await this.#deleteCount(OAuthAuthorizationCode.query().where("expiresAt", "<", cutoff.toSQL()).delete());
95
145
  }
146
+ pendingRequests += await this.#deleteCount(OAuthPendingAuthorizationRequest.query().where("expiresAt", "<", DateTime.now().toSQL()).delete());
96
147
  return {
97
148
  accessTokens,
98
149
  refreshTokens,
99
- authorizationCodes
150
+ authorizationCodes,
151
+ pendingRequests
100
152
  };
101
153
  }
154
+ registerRoutes(router) {
155
+ registerOAuthRoutes(router);
156
+ }
157
+ registerWellKnownRoutes(router) {
158
+ registerWellKnownRoutes(router);
159
+ }
160
+ registerProtectedResource(router, options) {
161
+ const wellKnownPath = `/.well-known/oauth-protected-resource${options.resource}`;
162
+ router.get(wellKnownPath, async (ctx) => {
163
+ ctx.response.header("Cache-Control", "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400");
164
+ return {
165
+ resource: `${this.#config.issuer}${options.resource}`,
166
+ authorization_servers: [this.#config.issuer],
167
+ scopes_supported: options.scopes ?? Object.keys(this.#config.scopes),
168
+ bearer_methods_supported: ["header"]
169
+ };
170
+ });
171
+ }
102
172
  #deleteCount(result) {
103
173
  return result.then((r) => Array.isArray(r) ? Number(r[0] ?? 0) : Number(r));
104
174
  }
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
175
  };
116
- export { OAuthRefreshToken as a, OAuthAuthorizationCode as i, sesame_manager_exports as n, OAuthConsent as r, SesameManager as t };
176
+ export { OAuthAuthorizationCode as a, OAuthConsent as i, sesame_manager_exports as n, OAuthRefreshToken as o, OAuthPendingAuthorizationRequest as r, SesameManager as t };
@@ -13,6 +13,7 @@ import type { AuthServerMetadata, ResourceServerMetadata } from '../types.ts';
13
13
  * @see https://openid.net/specs/openid-connect-discovery-1_0.html
14
14
  */
15
15
  export default class MetadataController {
16
+ #private;
16
17
  /**
17
18
  * OAuth 2.0 Authorization Server Metadata (RFC 8414).
18
19
  *
@@ -13,4 +13,9 @@ import type { SesameConfig, ResolvedSesameConfig } from './types.ts';
13
13
  * - `allowDynamicRegistration`: `false`
14
14
  * - `allowPublicRegistration`: `false`
15
15
  */
16
- export declare function defineConfig(config: SesameConfig): ResolvedSesameConfig;
16
+ export declare function defineConfig<const TScopes extends Record<string, string>>(config: Omit<SesameConfig, 'scopes' | 'defaultScopes'> & {
17
+ scopes?: TScopes;
18
+ defaultScopes?: Array<Extract<keyof TScopes, string>>;
19
+ }): Omit<ResolvedSesameConfig, 'scopes'> & {
20
+ scopes: TScopes;
21
+ };
@@ -2,6 +2,7 @@ import type { HttpContext } from '@adonisjs/core/http';
2
2
  import type { EmitterLike } from '@adonisjs/core/types/events';
3
3
  import { symbols } from '@adonisjs/auth';
4
4
  import type { AuthClientResponse, GuardContract } from '@adonisjs/auth/types';
5
+ import type { Scope } from '../types.ts';
5
6
  import type { SesameManager } from '../sesame_manager.ts';
6
7
  import type { OAuthGuardEvents, OAuthUserProviderContract } from './types.ts';
7
8
  /**
@@ -18,13 +19,13 @@ export declare class OAuthGuard<UserProvider extends OAuthUserProviderContract<u
18
19
  authenticationAttempted: boolean;
19
20
  isAuthenticated: boolean;
20
21
  user?: UserProvider[typeof symbols.PROVIDER_REAL_USER];
21
- scopes: string[];
22
+ scopes: Scope[];
22
23
  clientId?: string;
23
24
  constructor(name: string, ctx: HttpContext, emitter: EmitterLike<OAuthGuardEvents<UserProvider[typeof symbols.PROVIDER_REAL_USER]>>, userProvider: UserProvider, manager: SesameManager, resource?: string);
24
25
  getUserOrFail(): UserProvider[typeof symbols.PROVIDER_REAL_USER];
25
26
  authenticate(): Promise<UserProvider[typeof symbols.PROVIDER_REAL_USER]>;
26
27
  check(): Promise<boolean>;
27
- hasScope(...scopes: string[]): boolean;
28
- hasAnyScope(...scopes: string[]): boolean;
28
+ hasScope(...scopes: Scope[]): boolean;
29
+ hasAnyScope(...scopes: Scope[]): boolean;
29
30
  authenticateAsClient(user: UserProvider[typeof symbols.PROVIDER_REAL_USER]): Promise<AuthClientResponse>;
30
31
  }
@@ -1,12 +1,12 @@
1
- import "../../decorate-2_8Ex77k.js";
2
- import "../../oauth_access_token-BpG8sq-c.js";
3
- import "../../oauth_client-eh0e5ql-.js";
4
- import "../../token_service-Czz9v5GI.js";
5
- import { n as OAuthGuard, t as OAuthLucidUserProvider } from "../../user_provider-B3rXEUT3.js";
1
+ import "../../decorate-BKZEjPRg.js";
2
+ import "../../oauth_access_token-bsoM5KeU.js";
3
+ import "../../oauth_client-BIoY5jBR.js";
4
+ import "../../token_service-fhoA4slP.js";
5
+ import { n as OAuthGuard, t as OAuthLucidUserProvider } from "../../user_provider-DXAOfv8-.js";
6
6
  function oauthGuard(config) {
7
7
  return { async resolver(name, app) {
8
8
  const emitter = await app.container.make("emitter");
9
- const { SesameManager } = await import("../../sesame_manager-B4tO2PLO.js").then((n) => n.n);
9
+ const { SesameManager } = await import("../../sesame_manager-CFq4VEIZ.js").then((n) => n.n);
10
10
  const manager = await app.container.make(SesameManager);
11
11
  return (ctx) => new OAuthGuard(name, ctx, emitter, config.provider, manager, config.resource);
12
12
  } };
@@ -0,0 +1,15 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { NextFn } from '@adonisjs/core/types/http';
3
+ import type { Scope } from '../types.ts';
4
+ /**
5
+ * Any-scope middleware requires ANY of the listed scopes on the
6
+ * authenticated OAuth token. Throws 403 if none match.
7
+ *
8
+ * @example
9
+ * router.get('/data', [DataController]).use(middleware.anyScope({ scopes: ['read', 'read-all'] }))
10
+ */
11
+ export default class AnyScopeMiddleware {
12
+ handle(ctx: HttpContext, next: NextFn, options: {
13
+ scopes: Scope[];
14
+ }): Promise<any>;
15
+ }
@@ -0,0 +1,18 @@
1
+ import { n as E_INSUFFICIENT_SCOPE } from "../../oauth_error-CnJ3L8tf.js";
2
+ var AnyScopeMiddleware = class {
3
+ async handle(ctx, next, options) {
4
+ const guard = ctx.auth.use("oauth");
5
+ if (guard.isAuthenticated) {
6
+ if (!guard.hasAnyScope(...options.scopes)) throw new E_INSUFFICIENT_SCOPE(options.scopes);
7
+ return next();
8
+ }
9
+ if (ctx.request.header("authorization")) {
10
+ await guard.authenticate();
11
+ if (!guard.hasAnyScope(...options.scopes)) throw new E_INSUFFICIENT_SCOPE(options.scopes);
12
+ return next();
13
+ }
14
+ if (await ctx.auth.check()) return next();
15
+ await guard.authenticate();
16
+ }
17
+ };
18
+ export { AnyScopeMiddleware as default };
@@ -0,0 +1,15 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { NextFn } from '@adonisjs/core/types/http';
3
+ import type { Scope } from '../types.ts';
4
+ /**
5
+ * Scope middleware requires ALL listed scopes on the authenticated
6
+ * OAuth token. Throws 403 if any scope is missing.
7
+ *
8
+ * @example
9
+ * router.get('/admin', [AdminController]).use(middleware.scopes({ scopes: ['admin', 'manage'] }))
10
+ */
11
+ export default class ScopeMiddleware {
12
+ handle(ctx: HttpContext, next: NextFn, options: {
13
+ scopes: Scope[];
14
+ }): Promise<any>;
15
+ }
@@ -0,0 +1,18 @@
1
+ import { n as E_INSUFFICIENT_SCOPE } from "../../oauth_error-CnJ3L8tf.js";
2
+ var ScopeMiddleware = class {
3
+ async handle(ctx, next, options) {
4
+ const guard = ctx.auth.use("oauth");
5
+ if (guard.isAuthenticated) {
6
+ if (!guard.hasScope(...options.scopes)) throw new E_INSUFFICIENT_SCOPE(options.scopes);
7
+ return next();
8
+ }
9
+ if (ctx.request.header("authorization")) {
10
+ await guard.authenticate();
11
+ if (!guard.hasScope(...options.scopes)) throw new E_INSUFFICIENT_SCOPE(options.scopes);
12
+ return next();
13
+ }
14
+ if (await ctx.auth.check()) return next();
15
+ await guard.authenticate();
16
+ }
17
+ };
18
+ export { ScopeMiddleware as default };
@@ -0,0 +1,29 @@
1
+ import { DateTime } from 'luxon';
2
+ import { BaseModel } from '@adonisjs/lucid/orm';
3
+ /**
4
+ * Temporary server-side record for an in-flight OAuth authorization
5
+ * request, created when the user is redirected to the consent page
6
+ * and consumed (deleted) when the user approves or denies.
7
+ *
8
+ * Stored in the database instead of the HTTP session to avoid
9
+ * last-write-wins race conditions when concurrent SPA requests
10
+ * overwrite session data.
11
+ *
12
+ * The `token` column stores a SHA-256 hash of the raw auth_token
13
+ * sent to the consent page, consistent with how authorization
14
+ * codes are stored.
15
+ */
16
+ export declare class OAuthPendingAuthorizationRequest extends BaseModel {
17
+ static table: string;
18
+ id: string;
19
+ token: string;
20
+ userId: string;
21
+ clientId: string;
22
+ redirectUri: string;
23
+ scopes: string[];
24
+ state: string | null;
25
+ codeChallenge: string | null;
26
+ codeChallengeMethod: string | null;
27
+ expiresAt: DateTime;
28
+ createdAt: DateTime;
29
+ }
@@ -357,3 +357,35 @@ export declare const E_SERVER_ERROR: {
357
357
  prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
358
358
  stackTraceLimit: number;
359
359
  };
360
+ /**
361
+ * The access token does not have the required scope(s) to access
362
+ * the protected resource. Returns 403 with a WWW-Authenticate header
363
+ * per RFC 6750 §3.1.
364
+ *
365
+ * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
366
+ */
367
+ export declare const E_INSUFFICIENT_SCOPE: {
368
+ new (missingScopes: string[], message?: string): {
369
+ missingScopes: string[];
370
+ handle(error: /*elided*/ any, ctx: HttpContext): void;
371
+ get oauthCode(): string;
372
+ name: string;
373
+ help?: string;
374
+ code?: string;
375
+ status: number;
376
+ toString(): string;
377
+ get [Symbol.toStringTag](): string;
378
+ message: string;
379
+ stack?: string;
380
+ cause?: unknown;
381
+ };
382
+ readonly status: number;
383
+ readonly code: string;
384
+ readonly message: string;
385
+ readonly oauthCode: string;
386
+ help?: string;
387
+ isError(error: unknown): error is Error;
388
+ captureStackTrace(targetObject: object, constructorOpt?: Function): void;
389
+ prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
390
+ stackTraceLimit: number;
391
+ };
@@ -1,28 +1,29 @@
1
1
  import { Router } from '@adonisjs/core/http';
2
2
  /**
3
- * Register all OAuth 2.1 routes on the given AdonisJS router.
3
+ * Register OAuth 2.1 endpoint routes on the given router.
4
+ *
5
+ * Paths are relative (no prefix) — the user wraps the call
6
+ * in a `router.group().prefix('/oauth')` to control the mount point.
4
7
  *
5
8
  * Endpoints registered:
6
- * - `POST /oauth/token` — Token endpoint (RFC 6749 §3.2)
7
- * - `GET /oauth/authorize` — Authorization endpoint (RFC 6749 §3.1)
8
- * - `POST /oauth/consent` — User consent submission
9
- * - `POST /oauth/introspect` — Token introspection (RFC 7662)
10
- * - `POST /oauth/revoke` — Token revocation (RFC 7009)
11
- * - `POST /oauth/register` — Dynamic client registration (RFC 7591)
12
- * - `GET /oauth/client-info` — Public client information (RFC 6819 §4.4.1.4)
13
- * - `GET /.well-known/oauth-authorization-server` — Server metadata (RFC 8414)
14
- * - `GET /.well-known/openid-configuration` — OpenID Connect discovery
15
- * - `GET /.well-known/oauth-protected-resource` — Protected resource metadata (RFC 9728)
9
+ * - `POST /token` — Token endpoint (RFC 6749 §3.2)
10
+ * - `GET /authorize` — Authorization endpoint (RFC 6749 §3.1)
11
+ * - `POST /consent` — User consent submission
12
+ * - `POST /introspect` — Token introspection (RFC 7662)
13
+ * - `POST /revoke` — Token revocation (RFC 7009)
14
+ * - `POST /register` — Dynamic client registration (RFC 7591)
15
+ * - `GET /client-info` — Public client information (RFC 6819 §4.4.1.4)
16
16
  */
17
- export declare function registerRoutes(router: Router): void;
17
+ export declare function registerOAuthRoutes(router: Router): void;
18
18
  /**
19
- * Register a `/.well-known/oauth-protected-resource` endpoint for a
20
- * specific resource path (RFC 9728). Useful for MCP servers that need
21
- * per-resource discovery.
19
+ * Register well-known discovery routes at the root level.
20
+ *
21
+ * These must be registered outside any prefix group so they
22
+ * remain at `/.well-known/...`.
22
23
  *
23
- * @see https://datatracker.ietf.org/doc/html/rfc9728
24
+ * Endpoints registered:
25
+ * - `GET /.well-known/oauth-authorization-server` — Server metadata (RFC 8414)
26
+ * - `GET /.well-known/openid-configuration` — OpenID Connect discovery
27
+ * - `GET /.well-known/oauth-protected-resource` — Protected resource metadata (RFC 9728)
24
28
  */
25
- export declare function registerProtectedResource(router: Router, options: {
26
- resource: string;
27
- scopes?: string[];
28
- }): void;
29
+ export declare function registerWellKnownRoutes(router: Router): void;