@julr/sesame 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -24,6 +24,7 @@ node ace add @julr/sesame
24
24
  ```
25
25
 
26
26
  This will:
27
+
27
28
  - Publish the configuration file to `config/sesame.ts`
28
29
  - Publish database migrations (6 tables)
29
30
  - Register the service provider and commands
@@ -47,8 +48,8 @@ const sesameConfig = defineConfig({
47
48
  issuer: env.get('APP_URL'),
48
49
 
49
50
  scopes: {
50
- 'read': 'Read access',
51
- 'write': 'Write access',
51
+ read: 'Read access',
52
+ write: 'Write access',
52
53
  },
53
54
 
54
55
  defaultScopes: ['read'],
@@ -83,9 +84,11 @@ Register OAuth routes from your `start/routes.ts` file:
83
84
  import sesame from '@julr/sesame/services/main'
84
85
 
85
86
  // OAuth endpoints under /oauth
86
- router.group(() => {
87
- sesame.registerRoutes()
88
- }).prefix('/oauth')
87
+ router
88
+ .group(() => {
89
+ sesame.registerRoutes()
90
+ })
91
+ .prefix('/oauth')
89
92
 
90
93
  // Discovery endpoints at the root
91
94
  sesame.registerWellKnownRoutes()
@@ -144,14 +147,10 @@ Two named middleware are available for checking scopes on authenticated requests
144
147
 
145
148
  ```ts
146
149
  // Requires ALL listed scopes
147
- router
148
- .get('/admin', [AdminController])
149
- .use(middleware.scopes({ scopes: ['admin', 'write'] }))
150
+ router.get('/admin', [AdminController]).use(middleware.scopes({ scopes: ['admin', 'write'] }))
150
151
 
151
152
  // Requires AT LEAST ONE of the listed scopes
152
- router
153
- .get('/data', [DataController])
154
- .use(middleware.anyScope({ scopes: ['read', 'write'] }))
153
+ router.get('/data', [DataController]).use(middleware.anyScope({ scopes: ['read', 'write'] }))
155
154
  ```
156
155
 
157
156
  ## MCP Support
@@ -1,10 +1,10 @@
1
- import { i as OAuthAuthorizationCode, n as OAuthPendingAuthorizationRequest, r as OAuthConsent, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { i as OAuthAuthorizationCode, n as OAuthPendingAuthorizationRequest, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import "./oauth_access_token-bsoM5KeU.js";
4
4
  import { d as E_UNSUPPORTED_RESPONSE_TYPE, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-CnJ3L8tf.js";
5
5
  import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
6
6
  import { t as TokenService } from "./token_service-fhoA4slP.js";
7
- import { t as ClientService } from "./client_service-BD1CDQuC.js";
7
+ import { t as ClientService } from "./client_service-WTNMqWzY.js";
8
8
  import { DateTime } from "luxon";
9
9
  import string from "@adonisjs/core/helpers/string";
10
10
  import vine from "@vinejs/vine";
@@ -1,3 +1,4 @@
1
+ import { o as BUILTIN_SCOPES } from "./sesame_manager-DwDZy5Vy.js";
1
2
  import { o as E_INVALID_REQUEST, r as E_INVALID_CLIENT, s as E_INVALID_SCOPE } from "./oauth_error-CnJ3L8tf.js";
2
3
  import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
3
4
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
@@ -37,9 +38,10 @@ var ClientService = class {
37
38
  return client;
38
39
  }
39
40
  validateClientScopes(requestedScopes, clientScopes) {
40
- if (clientScopes.length === 0 && requestedScopes.length > 0) throw new E_INVALID_SCOPE(`Scope not allowed: ${requestedScopes.join(", ")}`);
41
+ const nonBuiltinScopes = requestedScopes.filter((s) => !BUILTIN_SCOPES.has(s));
42
+ if (clientScopes.length === 0 && nonBuiltinScopes.length > 0) throw new E_INVALID_SCOPE(`Scope not allowed: ${nonBuiltinScopes.join(", ")}`);
41
43
  const allowedSet = new Set(clientScopes);
42
- const invalid = requestedScopes.filter((s) => !allowedSet.has(s));
44
+ const invalid = nonBuiltinScopes.filter((s) => !allowedSet.has(s));
43
45
  if (invalid.length > 0) throw new E_INVALID_SCOPE(`Scope not allowed: ${invalid.join(", ")}`);
44
46
  }
45
47
  hashSecret(secret) {
@@ -1,4 +1,4 @@
1
- import { t as SesameManager } from "../sesame_manager-BIHBwkqK.js";
1
+ import { t as SesameManager } from "../sesame_manager-DwDZy5Vy.js";
2
2
  import { t as __decorate } from "../decorate-BKZEjPRg.js";
3
3
  import "../oauth_access_token-bsoM5KeU.js";
4
4
  import { BaseCommand, flags } from "@adonisjs/core/ace";
@@ -1,4 +1,4 @@
1
- import { i as OAuthAuthorizationCode, n as OAuthPendingAuthorizationRequest, r as OAuthConsent, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { i as OAuthAuthorizationCode, n as OAuthPendingAuthorizationRequest, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import "./oauth_access_token-bsoM5KeU.js";
4
4
  import { a as E_INVALID_GRANT, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT } from "./oauth_error-CnJ3L8tf.js";
package/build/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { configure } from "./configure.js";
2
- import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
2
+ import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, r as OAuthConsent, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
3
3
  import "./decorate-BKZEjPRg.js";
4
4
  import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
5
5
  import { a as E_INVALID_GRANT, c as E_INVALID_TOKEN, d as E_UNSUPPORTED_RESPONSE_TYPE, f as OAuthError, i as E_INVALID_CLIENT_METADATA, l as E_SERVER_ERROR, n as E_INSUFFICIENT_SCOPE, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT, s as E_INVALID_SCOPE, t as E_ACCESS_DENIED, u as E_UNSUPPORTED_GRANT_TYPE } from "./oauth_error-CnJ3L8tf.js";
6
6
  import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
7
7
  import "./token_service-fhoA4slP.js";
8
- import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t as oauthGuard } from "./main-BJRXMGrZ.js";
8
+ import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t as oauthGuard } from "./main-EbeMS5S9.js";
9
9
  function defineConfig(config) {
10
10
  return {
11
11
  issuer: config.issuer,
@@ -13,6 +13,7 @@ function defineConfig(config) {
13
13
  defaultScopes: config.defaultScopes ?? [],
14
14
  grantTypes: config.grantTypes ?? ["authorization_code", "refresh_token"],
15
15
  accessTokenTtl: config.accessTokenTtl ?? "1h",
16
+ clientCredentialsAccessTokenTtl: config.clientCredentialsAccessTokenTtl ?? config.accessTokenTtl ?? "1h",
16
17
  refreshTokenTtl: config.refreshTokenTtl ?? "30d",
17
18
  authorizationCodeTtl: config.authorizationCodeTtl ?? "10m",
18
19
  authorizationRequestTtl: config.authorizationRequestTtl ?? config.authorizationCodeTtl ?? "10m",
@@ -1,10 +1,10 @@
1
- import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
4
4
  import "./oauth_error-CnJ3L8tf.js";
5
5
  import "./oauth_client-BIoY5jBR.js";
6
6
  import { t as TokenService } from "./token_service-fhoA4slP.js";
7
- import { t as ClientService } from "./client_service-BD1CDQuC.js";
7
+ import { t as ClientService } from "./client_service-WTNMqWzY.js";
8
8
  const INACTIVE = { active: false };
9
9
  var IntrospectController = class {
10
10
  async handle(ctx) {
@@ -150,7 +150,7 @@ var OAuthLucidUserProvider = class {
150
150
  function oauthGuard(config) {
151
151
  return { async resolver(name, app) {
152
152
  const emitter = await app.container.make("emitter");
153
- const { SesameManager } = await import("./sesame_manager-BWqeaqn9.js");
153
+ const { SesameManager } = await import("./sesame_manager-Bu4MHqZV.js");
154
154
  const manager = await app.container.make(SesameManager);
155
155
  return (ctx) => new OAuthGuard(name, ctx, emitter, config.provider, manager, config.resource);
156
156
  } };
@@ -1,4 +1,4 @@
1
- import { t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { o as BUILTIN_SCOPES, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import "./oauth_access_token-bsoM5KeU.js";
4
4
  import { l as E_SERVER_ERROR } from "./oauth_error-CnJ3L8tf.js";
@@ -63,7 +63,7 @@ var MetadataController = class {
63
63
  return {
64
64
  ...base,
65
65
  subject_types_supported: ["public"],
66
- scopes_supported: Object.keys(manager.config.scopes)
66
+ scopes_supported: [...Object.keys(manager.config.scopes), ...BUILTIN_SCOPES]
67
67
  };
68
68
  }
69
69
  async protectedResource(ctx) {
@@ -73,7 +73,7 @@ var MetadataController = class {
73
73
  return {
74
74
  resource: issuer,
75
75
  authorization_servers: [issuer],
76
- scopes_supported: Object.keys(manager.config.scopes),
76
+ scopes_supported: [...Object.keys(manager.config.scopes), ...BUILTIN_SCOPES],
77
77
  bearer_methods_supported: ["header"]
78
78
  };
79
79
  }
@@ -1,4 +1,4 @@
1
- import { t as SesameManager } from "../sesame_manager-BIHBwkqK.js";
1
+ import { t as SesameManager } from "../sesame_manager-DwDZy5Vy.js";
2
2
  import "../decorate-BKZEjPRg.js";
3
3
  import "../oauth_access_token-bsoM5KeU.js";
4
4
  var SesameProvider = class {
@@ -1,9 +1,9 @@
1
- import { t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import "./oauth_access_token-bsoM5KeU.js";
4
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
5
  import { t as OAuthClient } from "./oauth_client-BIoY5jBR.js";
6
- import { t as ClientService } from "./client_service-BD1CDQuC.js";
6
+ import { t as ClientService } from "./client_service-WTNMqWzY.js";
7
7
  import vine from "@vinejs/vine";
8
8
  const DANGEROUS_SCHEMES = [
9
9
  "javascript:",
@@ -1,10 +1,10 @@
1
- import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { a as OAuthRefreshToken, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
4
4
  import "./oauth_error-CnJ3L8tf.js";
5
5
  import "./oauth_client-BIoY5jBR.js";
6
6
  import { t as TokenService } from "./token_service-fhoA4slP.js";
7
- import { t as ClientService } from "./client_service-BD1CDQuC.js";
7
+ import { t as ClientService } from "./client_service-WTNMqWzY.js";
8
8
  import { DateTime } from "luxon";
9
9
  var RevokeController = class {
10
10
  async handle(ctx) {
@@ -1,4 +1,4 @@
1
- import { t as SesameManager } from "../sesame_manager-BIHBwkqK.js";
1
+ import { t as SesameManager } from "../sesame_manager-DwDZy5Vy.js";
2
2
  import "../decorate-BKZEjPRg.js";
3
3
  import "../oauth_access_token-bsoM5KeU.js";
4
4
  import app from "@adonisjs/core/services/app";
@@ -1,4 +1,4 @@
1
- import { t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import "./oauth_access_token-bsoM5KeU.js";
4
4
  export { SesameManager };
@@ -2,14 +2,15 @@ import { n as json, t as __decorate } from "./decorate-BKZEjPRg.js";
2
2
  import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
3
3
  import { DateTime } from "luxon";
4
4
  import { BaseModel, column } from "@adonisjs/lucid/orm";
5
+ const BUILTIN_SCOPES = new Set(["offline_access"]);
5
6
  const controllers = {
6
- token: () => import("./token_controller-BijtWj5C.js"),
7
- authorize: () => import("./authorize_controller-Bb54Bi_s.js"),
8
- consent: () => import("./consent_controller-gMuS9nSx.js"),
9
- introspect: () => import("./introspect_controller-bKg1T2Xx.js"),
10
- revoke: () => import("./revoke_controller-dJtMG1zK.js"),
11
- register: () => import("./register_controller-CnzYR-iH.js"),
12
- metadata: () => import("./metadata_controller-CGaKw3UM.js"),
7
+ token: () => import("./token_controller-DzcrLMyS.js"),
8
+ authorize: () => import("./authorize_controller-YUfAy-R2.js"),
9
+ consent: () => import("./consent_controller-Dprwd1ed.js"),
10
+ introspect: () => import("./introspect_controller-6bRt9sZt.js"),
11
+ revoke: () => import("./revoke_controller-D6isoQCi.js"),
12
+ register: () => import("./register_controller-sIJ1rxdM.js"),
13
+ metadata: () => import("./metadata_controller-DeaMRnUr.js"),
13
14
  clientInfo: () => import("./client_info_controller-BucHGx4u.js")
14
15
  };
15
16
  function registerOAuthRoutes(router) {
@@ -99,8 +100,8 @@ var SesameManager = class {
99
100
  return scope in this.#config.scopes;
100
101
  }
101
102
  validateScopes(scopes) {
102
- if (Object.keys(this.#config.scopes).length === 0) return scopes;
103
- return scopes.filter((s) => !this.hasScope(s));
103
+ if (Object.keys(this.#config.scopes).length === 0) return scopes.filter((s) => !BUILTIN_SCOPES.has(s));
104
+ return scopes.filter((s) => !BUILTIN_SCOPES.has(s) && !this.hasScope(s));
104
105
  }
105
106
  isGrantTypeEnabled(grantType) {
106
107
  return this.#config.grantTypes.includes(grantType);
@@ -154,7 +155,7 @@ var SesameManager = class {
154
155
  return {
155
156
  resource: `${this.#config.issuer}${options.resource}`,
156
157
  authorization_servers: [this.#config.issuer],
157
- scopes_supported: options.scopes ?? Object.keys(this.#config.scopes),
158
+ scopes_supported: [...options.scopes ?? Object.keys(this.#config.scopes), ...BUILTIN_SCOPES],
158
159
  bearer_methods_supported: ["header"]
159
160
  };
160
161
  });
@@ -163,4 +164,4 @@ var SesameManager = class {
163
164
  return result.then((r) => Array.isArray(r) ? Number(r[0] ?? 0) : Number(r));
164
165
  }
165
166
  };
166
- export { OAuthRefreshToken as a, OAuthAuthorizationCode as i, OAuthPendingAuthorizationRequest as n, OAuthConsent as r, SesameManager as t };
167
+ export { OAuthRefreshToken as a, OAuthAuthorizationCode as i, OAuthPendingAuthorizationRequest as n, BUILTIN_SCOPES as o, OAuthConsent as r, SesameManager as t };
@@ -1,5 +1,5 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
- import type { AuthServerMetadata, ResourceServerMetadata } from '../types.ts';
2
+ import { type AuthServerMetadata, type ResourceServerMetadata } from '../types.ts';
3
3
  /**
4
4
  * Serves OAuth 2.0 discovery metadata documents.
5
5
  *
@@ -3,17 +3,13 @@ import type { SesameManager } from '../sesame_manager.ts';
3
3
  /**
4
4
  * Handle the Authorization Code Grant (RFC 6749 §4.1.3).
5
5
  *
6
- * Exchanges an authorization code for an access token (and optionally
7
- * a refresh token if the `offline_access` scope was granted).
6
+ * Exchanges an authorization code for an access token and a refresh
7
+ * token. A refresh token is always issued when the `refresh_token`
8
+ * grant type is enabled on the server — the client does not need
9
+ * to request `offline_access` explicitly.
8
10
  *
9
- * Performs the following validations:
10
- * - Client authentication (Basic header or POST body credentials)
11
- * - Authorization code existence, expiration, and single-use enforcement
12
- * - Redirect URI matching against the original authorization request
13
- * - PKCE code_verifier verification using S256 (RFC 7636 §4.6)
14
- *
15
- * All tokens (access tokens, refresh tokens, authorization codes)
16
- * are opaque values stored as SHA-256 hashes.
11
+ * This matches the behavior of major OAuth providers and avoids
12
+ * forcing MCP clients like ClaudeDesktop to know about `offline_access` to get long-lived sessions.
17
13
  *
18
14
  * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
19
15
  * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
@@ -0,0 +1,23 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { SesameManager } from '../sesame_manager.ts';
3
+ /**
4
+ * Handle the Client Credentials Grant (RFC 6749 §4.4).
5
+ *
6
+ * Issues an access token directly to the client for
7
+ * machine-to-machine (M2M) communication. The client
8
+ * authenticates with its own credentials and receives an
9
+ * access token without an interactive user step.
10
+ *
11
+ * No refresh token is issued (per spec and convention).
12
+ *
13
+ * Built-in OIDC scopes (e.g. `offline_access`) are rejected
14
+ * since they are user-centric and meaningless in an M2M context.
15
+ *
16
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
17
+ */
18
+ export declare function handleClientCredentialsGrant(ctx: HttpContext, manager: SesameManager): Promise<{
19
+ access_token: string;
20
+ token_type: string;
21
+ expires_in: number;
22
+ scope: string;
23
+ }>;
@@ -2,5 +2,5 @@ import "../../decorate-BKZEjPRg.js";
2
2
  import "../../oauth_access_token-bsoM5KeU.js";
3
3
  import "../../oauth_client-BIoY5jBR.js";
4
4
  import "../../token_service-fhoA4slP.js";
5
- import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t as oauthGuard } from "../../main-BJRXMGrZ.js";
5
+ import { i as OAuthGuard, n as oauthUserProvider, r as OAuthLucidUserProvider, t as oauthGuard } from "../../main-EbeMS5S9.js";
6
6
  export { OAuthGuard, OAuthLucidUserProvider, oauthGuard, oauthUserProvider };
@@ -1,5 +1,5 @@
1
1
  import type { Router } from '@adonisjs/core/http';
2
- import type { ResolvedSesameConfig, Scope } from './types.ts';
2
+ import { type ResolvedSesameConfig, type Scope } from './types.ts';
3
3
  export interface PurgeResult {
4
4
  accessTokens: number;
5
5
  refreshTokens: number;
@@ -27,15 +27,38 @@ export type InferScopes<T extends {
27
27
  [K in keyof T['scopes'] & string]: true;
28
28
  };
29
29
  /**
30
- * Supported grant types for v1 (MCP-focused).
30
+ * Standard OAuth/OIDC scopes that are always valid regardless
31
+ * of server or client scope configuration.
32
+ *
33
+ * These scopes are:
34
+ * - Accepted during scope validation (client and server level)
35
+ * - Advertised in `scopes_supported` of all metadata endpoints
36
+ * (protected resource, OIDC discovery) so MCP clients know
37
+ * they can request them
38
+ *
39
+ * - `offline_access`: signals that the client needs a refresh token
40
+ * (OIDC Core §11). Note: Sesame issues refresh tokens by default
41
+ * when the `refresh_token` grant is enabled, regardless of whether
42
+ * the client requests this scope (per RFC 6749 §5.1). This scope
43
+ * is still advertised for clients that check metadata before
44
+ * building their authorization request.
45
+ *
46
+ * @see https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
47
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
48
+ */
49
+ export declare const BUILTIN_SCOPES: Set<string>;
50
+ /**
51
+ * Supported OAuth 2.1 grant types.
31
52
  *
32
53
  * - `authorization_code`: RFC 6749 §4.1 — Authorization Code Grant
33
54
  * - `refresh_token`: RFC 6749 §6 — Refreshing an Access Token
55
+ * - `client_credentials`: RFC 6749 §4.4 — Client Credentials Grant (M2M)
34
56
  *
35
57
  * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
58
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
36
59
  * @see https://datatracker.ietf.org/doc/html/rfc6749#section-6
37
60
  */
38
- export type GrantType = 'authorization_code' | 'refresh_token';
61
+ export type GrantType = 'authorization_code' | 'refresh_token' | 'client_credentials';
39
62
  /**
40
63
  * User-facing configuration interface for Sésame.
41
64
  *
@@ -77,6 +100,11 @@ export interface SesameConfig {
77
100
  * Defaults to '30d'.
78
101
  */
79
102
  refreshTokenTtl?: string;
103
+ /**
104
+ * Access token TTL for the client_credentials grant (M2M).
105
+ * Defaults to `accessTokenTtl`.
106
+ */
107
+ clientCredentialsAccessTokenTtl?: string;
80
108
  /**
81
109
  * Authorization code TTL as a string duration.
82
110
  * Defaults to '10m'.
@@ -125,6 +153,7 @@ export interface ResolvedSesameConfig {
125
153
  defaultScopes: string[];
126
154
  grantTypes: GrantType[];
127
155
  accessTokenTtl: string;
156
+ clientCredentialsAccessTokenTtl: string;
128
157
  refreshTokenTtl: string;
129
158
  authorizationCodeTtl: string;
130
159
  authorizationRequestTtl: string;
@@ -1,10 +1,10 @@
1
- import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, t as SesameManager } from "./sesame_manager-BIHBwkqK.js";
1
+ import { a as OAuthRefreshToken, i as OAuthAuthorizationCode, o as BUILTIN_SCOPES, t as SesameManager } from "./sesame_manager-DwDZy5Vy.js";
2
2
  import "./decorate-BKZEjPRg.js";
3
3
  import { t as OAuthAccessToken } from "./oauth_access_token-bsoM5KeU.js";
4
4
  import { a as E_INVALID_GRANT, o as E_INVALID_REQUEST, r as E_INVALID_CLIENT, s as E_INVALID_SCOPE, u as E_UNSUPPORTED_GRANT_TYPE } from "./oauth_error-CnJ3L8tf.js";
5
5
  import "./oauth_client-BIoY5jBR.js";
6
6
  import { t as TokenService } from "./token_service-fhoA4slP.js";
7
- import { t as ClientService } from "./client_service-BD1CDQuC.js";
7
+ import { t as ClientService } from "./client_service-WTNMqWzY.js";
8
8
  import { DateTime } from "luxon";
9
9
  import { createHash } from "node:crypto";
10
10
  import string from "@adonisjs/core/helpers/string";
@@ -50,7 +50,7 @@ async function handleAuthorizationCodeGrant(ctx, manager) {
50
50
  expiresAt: DateTime.fromJSDate(expiresAt)
51
51
  });
52
52
  let refreshTokenRaw;
53
- if (authCode.scopes.includes("offline_access")) {
53
+ if (manager.isGrantTypeEnabled("refresh_token")) {
54
54
  const { raw, hash } = tokenService.createRefreshToken();
55
55
  const refreshTtl = string.seconds.parse(manager.config.refreshTokenTtl);
56
56
  await OAuthRefreshToken.create({
@@ -135,9 +135,46 @@ async function handleRefreshTokenGrant(ctx, manager) {
135
135
  refresh_token: newRefreshTokenRaw
136
136
  };
137
137
  }
138
+ async function handleClientCredentialsGrant(ctx, manager) {
139
+ const tokenService = new TokenService(manager);
140
+ const clientService = new ClientService();
141
+ const body = ctx.request.body();
142
+ const client = await clientService.authenticateClient({
143
+ authorizationHeader: ctx.request.header("authorization"),
144
+ bodyClientId: body.client_id,
145
+ bodyClientSecret: body.client_secret
146
+ });
147
+ if (client.isPublic) throw new E_INVALID_CLIENT("Public clients cannot use the client_credentials grant");
148
+ if (!client.grantTypes.includes("client_credentials")) throw new E_INVALID_CLIENT("Client is not allowed to use the client_credentials grant");
149
+ const requestedScopes = body.scope ? body.scope.split(" ") : client.scopes.filter((scope) => !BUILTIN_SCOPES.has(scope));
150
+ const builtinRequested = requestedScopes.filter((s) => BUILTIN_SCOPES.has(s));
151
+ if (builtinRequested.length > 0) throw new E_INVALID_SCOPE(`Scopes not allowed for client_credentials: ${builtinRequested.join(", ")}`);
152
+ const invalidScopes = manager.validateScopes(requestedScopes);
153
+ if (invalidScopes.length > 0) throw new E_INVALID_SCOPE(`Invalid scopes: ${invalidScopes.join(", ")}`);
154
+ clientService.validateClientScopes(requestedScopes, client.scopes);
155
+ if (!client.userId) throw new E_INVALID_CLIENT("Client must be associated with a user to use the client_credentials grant");
156
+ const ttlSeconds = string.seconds.parse(manager.config.clientCredentialsAccessTokenTtl);
157
+ const { raw: accessTokenRaw, hash: tokenHash } = tokenService.createAccessToken();
158
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
159
+ await OAuthAccessToken.create({
160
+ id: crypto.randomUUID(),
161
+ tokenHash,
162
+ clientId: client.clientId,
163
+ userId: client.userId,
164
+ scopes: requestedScopes,
165
+ expiresAt: DateTime.fromJSDate(expiresAt)
166
+ });
167
+ return {
168
+ access_token: accessTokenRaw,
169
+ token_type: "Bearer",
170
+ expires_in: ttlSeconds,
171
+ scope: requestedScopes.join(" ")
172
+ };
173
+ }
138
174
  const grantHandlers = {
139
175
  authorization_code: handleAuthorizationCodeGrant,
140
- refresh_token: handleRefreshTokenGrant
176
+ refresh_token: handleRefreshTokenGrant,
177
+ client_credentials: handleClientCredentialsGrant
141
178
  };
142
179
  var TokenController = class TokenController {
143
180
  static validator = vine.create({ grant_type: vine.string() });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@julr/sesame",
3
3
  "description": "OAuth 2.1 + OIDC server for AdonisJS",
4
- "version": "0.4.0",
4
+ "version": "0.5.1",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },