@better-auth/oauth-provider 1.6.3 → 1.7.0-beta.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.
@@ -1,4 +1,4 @@
1
- import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-IpUqx-_N.mjs";
1
+ import { _ as Scope, d as OAuthConsent, h as Prompt, i as OAuthClient, p as OAuthOptions, r as GrantType, s as TokenEndpointAuthMethod, t as AuthMethod } from "./oauth-CU79t-eG.mjs";
2
2
  import * as better_call0 from "better-call";
3
3
  import * as z from "zod";
4
4
  import * as better_auth_plugins0 from "better-auth/plugins";
@@ -54,7 +54,64 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
54
54
  metadata: {
55
55
  SERVER_ONLY: true;
56
56
  };
57
- }, AuthServerMetadata>;
57
+ }, {
58
+ jwks_uri?: string | undefined;
59
+ userinfo_endpoint: string;
60
+ acr_values_supported: string[];
61
+ subject_types_supported: ("public" | "pairwise")[];
62
+ claims_supported: string[];
63
+ end_session_endpoint: string;
64
+ prompt_values_supported: Prompt[];
65
+ issuer: string;
66
+ authorization_endpoint: string;
67
+ token_endpoint: string;
68
+ registration_endpoint: string;
69
+ scopes_supported?: string[] | undefined;
70
+ response_types_supported: "code"[];
71
+ response_modes_supported: "query"[];
72
+ grant_types_supported: GrantType[];
73
+ token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
74
+ token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
75
+ service_documentation?: string | undefined;
76
+ ui_locales_supported?: string[] | undefined;
77
+ op_policy_uri?: string | undefined;
78
+ op_tos_uri?: string | undefined;
79
+ revocation_endpoint?: string | undefined;
80
+ revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
81
+ revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
82
+ introspection_endpoint?: string | undefined;
83
+ introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
84
+ introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
85
+ code_challenge_methods_supported: "S256"[];
86
+ authorization_response_iss_parameter_supported?: boolean | undefined;
87
+ client_id_metadata_document_supported?: boolean | undefined;
88
+ id_token_signing_alg_values_supported: better_auth_plugins0.JWSAlgorithms[] | ["HS256"];
89
+ } | {
90
+ issuer: string;
91
+ authorization_endpoint: string;
92
+ token_endpoint: string;
93
+ jwks_uri?: string;
94
+ registration_endpoint: string;
95
+ scopes_supported?: string[];
96
+ response_types_supported: "code"[];
97
+ response_modes_supported: "query"[];
98
+ grant_types_supported: GrantType[];
99
+ token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
100
+ token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
101
+ service_documentation?: string;
102
+ ui_locales_supported?: string[];
103
+ op_policy_uri?: string;
104
+ op_tos_uri?: string;
105
+ revocation_endpoint?: string;
106
+ revocation_endpoint_auth_methods_supported?: AuthMethod[];
107
+ revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
108
+ introspection_endpoint?: string;
109
+ introspection_endpoint_auth_methods_supported?: AuthMethod[];
110
+ introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
111
+ code_challenge_methods_supported: "S256"[];
112
+ authorization_response_iss_parameter_supported?: boolean;
113
+ client_id_metadata_document_supported?: boolean;
114
+ }>;
58
115
  /**
59
116
  * A server-only endpoint that helps provide the
60
117
  * OpenId configuration at the well-known endpoint.
@@ -67,7 +124,37 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
67
124
  metadata: {
68
125
  SERVER_ONLY: true;
69
126
  };
70
- }, Omit<OIDCMetadata, "id_token_signing_alg_values_supported"> & {
127
+ }, {
128
+ jwks_uri?: string | undefined;
129
+ userinfo_endpoint: string;
130
+ acr_values_supported: string[];
131
+ subject_types_supported: ("public" | "pairwise")[];
132
+ claims_supported: string[];
133
+ end_session_endpoint: string;
134
+ prompt_values_supported: Prompt[];
135
+ issuer: string;
136
+ authorization_endpoint: string;
137
+ token_endpoint: string;
138
+ registration_endpoint: string;
139
+ scopes_supported?: string[] | undefined;
140
+ response_types_supported: "code"[];
141
+ response_modes_supported: "query"[];
142
+ grant_types_supported: GrantType[];
143
+ token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
144
+ token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
145
+ service_documentation?: string | undefined;
146
+ ui_locales_supported?: string[] | undefined;
147
+ op_policy_uri?: string | undefined;
148
+ op_tos_uri?: string | undefined;
149
+ revocation_endpoint?: string | undefined;
150
+ revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
151
+ revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
152
+ introspection_endpoint?: string | undefined;
153
+ introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
154
+ introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
155
+ code_challenge_methods_supported: "S256"[];
156
+ authorization_response_iss_parameter_supported?: boolean | undefined;
157
+ client_id_metadata_document_supported?: boolean | undefined;
71
158
  id_token_signing_alg_values_supported: better_auth_plugins0.JWSAlgorithms[] | ["HS256"];
72
159
  }>;
73
160
  oauth2Authorize: better_call0.StrictEndpoint<"/oauth2/authorize", {
@@ -298,6 +385,8 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
298
385
  }>;
299
386
  client_id: z.ZodOptional<z.ZodString>;
300
387
  client_secret: z.ZodOptional<z.ZodString>;
388
+ client_assertion: z.ZodOptional<z.ZodString>;
389
+ client_assertion_type: z.ZodOptional<z.ZodString>;
301
390
  code: z.ZodOptional<z.ZodString>;
302
391
  code_verifier: z.ZodOptional<z.ZodString>;
303
392
  redirect_uri: z.ZodOptional<z.ZodURL>;
@@ -438,6 +527,8 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
438
527
  body: z.ZodObject<{
439
528
  client_id: z.ZodOptional<z.ZodString>;
440
529
  client_secret: z.ZodOptional<z.ZodString>;
530
+ client_assertion: z.ZodOptional<z.ZodString>;
531
+ client_assertion_type: z.ZodOptional<z.ZodString>;
441
532
  token: z.ZodString;
442
533
  token_type_hint: z.ZodOptional<z.ZodEnum<{
443
534
  refresh_token: "refresh_token";
@@ -575,6 +666,8 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
575
666
  body: z.ZodObject<{
576
667
  client_id: z.ZodOptional<z.ZodString>;
577
668
  client_secret: z.ZodOptional<z.ZodString>;
669
+ client_assertion: z.ZodOptional<z.ZodString>;
670
+ client_assertion_type: z.ZodOptional<z.ZodString>;
578
671
  token: z.ZodString;
579
672
  token_type_hint: z.ZodOptional<z.ZodEnum<{
580
673
  refresh_token: "refresh_token";
@@ -833,7 +926,12 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
833
926
  none: "none";
834
927
  client_secret_basic: "client_secret_basic";
835
928
  client_secret_post: "client_secret_post";
929
+ private_key_jwt: "private_key_jwt";
836
930
  }>>>;
931
+ jwks: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodObject<{
932
+ keys: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
933
+ }, z.core.$strip>]>>;
934
+ jwks_uri: z.ZodOptional<z.ZodString>;
837
935
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
838
936
  authorization_code: "authorization_code";
839
937
  client_credentials: "client_credentials";
@@ -1006,7 +1104,12 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1006
1104
  none: "none";
1007
1105
  client_secret_basic: "client_secret_basic";
1008
1106
  client_secret_post: "client_secret_post";
1107
+ private_key_jwt: "private_key_jwt";
1009
1108
  }>>>;
1109
+ jwks: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodObject<{
1110
+ keys: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1111
+ }, z.core.$strip>]>>;
1112
+ jwks_uri: z.ZodOptional<z.ZodString>;
1010
1113
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
1011
1114
  authorization_code: "authorization_code";
1012
1115
  client_credentials: "client_credentials";
@@ -1210,7 +1313,12 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1210
1313
  none: "none";
1211
1314
  client_secret_basic: "client_secret_basic";
1212
1315
  client_secret_post: "client_secret_post";
1316
+ private_key_jwt: "private_key_jwt";
1213
1317
  }>>>;
1318
+ jwks: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodObject<{
1319
+ keys: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1320
+ }, z.core.$strip>]>>;
1321
+ jwks_uri: z.ZodOptional<z.ZodString>;
1214
1322
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
1215
1323
  authorization_code: "authorization_code";
1216
1324
  client_credentials: "client_credentials";
@@ -1876,6 +1984,14 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1876
1984
  type: "string";
1877
1985
  required: false;
1878
1986
  };
1987
+ jwks: {
1988
+ type: "string";
1989
+ required: false;
1990
+ };
1991
+ jwksUri: {
1992
+ type: "string";
1993
+ required: false;
1994
+ };
1879
1995
  grantTypes: {
1880
1996
  type: "string[]";
1881
1997
  required: false;
@@ -1,3 +1,4 @@
1
+ import { AssertionSigningAlgorithm } from "@better-auth/core/oauth2";
1
2
  import { JWSAlgorithms } from "better-auth/plugins";
2
3
  import { JWTPayload } from "jose";
3
4
  import { InferOptionSchema, Session, User } from "better-auth/types";
@@ -102,6 +103,14 @@ declare const schema: {
102
103
  type: "string";
103
104
  required: false;
104
105
  };
106
+ jwks: {
107
+ type: "string";
108
+ required: false;
109
+ };
110
+ jwksUri: {
111
+ type: "string";
112
+ required: false;
113
+ };
105
114
  grantTypes: {
106
115
  type: "string[]";
107
116
  required: false;
@@ -306,6 +315,46 @@ type InternallySupportedScopes = "openid" | "profile" | "email" | "offline_acces
306
315
  type Scope = LiteralString | InternallySupportedScopes;
307
316
  type Prompt = "none" | "consent" | "login" | "create" | "select_account";
308
317
  type AuthorizePrompt = Prompt | "login consent" | "select_account consent";
318
+ /**
319
+ * Describes how to resolve a `client_id` from an external source (a URL-based
320
+ * metadata document, a federated registry, an attestation header, etc.) and
321
+ * what fields that source contributes to discovery metadata.
322
+ *
323
+ * Plugins install one of these onto {@link OAuthOptions.clientDiscovery}.
324
+ * The host walks the configured entries in order and returns the first
325
+ * non-null `resolve()` result.
326
+ */
327
+ interface ClientDiscovery<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
328
+ /**
329
+ * Stable identifier used in error messages and diagnostics. Convention
330
+ * is to match the plugin id (for example `"cimd"`).
331
+ */
332
+ readonly id: string;
333
+ /**
334
+ * Return `true` if this discovery handles the given `client_id`. Called
335
+ * on every `getClient()` lookup for every configured discovery, so keep
336
+ * it cheap and synchronous.
337
+ */
338
+ matches: (clientId: string) => boolean;
339
+ /**
340
+ * Resolve a client when this discovery matches. Receives the existing DB
341
+ * record (or `null`) so an implementation can decide between creating,
342
+ * refreshing, or passing through to the database result.
343
+ *
344
+ * Return:
345
+ * - a client record: `getClient()` returns it (creation / refresh / takeover).
346
+ * - `null`: `getClient()` falls through to the next matching discovery
347
+ * or to the database record (if any).
348
+ */
349
+ resolve: (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scopes> | null) => Awaitable<SchemaClient<Scopes> | null>;
350
+ /**
351
+ * Fields merged into `/.well-known/oauth-authorization-server` and
352
+ * `/.well-known/openid-configuration` responses. Useful for advertising
353
+ * RFC-registered discovery flags like
354
+ * `client_id_metadata_document_supported`.
355
+ */
356
+ discoveryMetadata?: Record<string, unknown>;
357
+ }
309
358
  interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
310
359
  /**
311
360
  * Custom schema definitions
@@ -388,6 +437,13 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
388
437
  * { "write:payments": "5m", "read:payments": "30m" }
389
438
  */
390
439
  scopeExpirations?: { [K in Scopes[number]]?: number | string | Date };
440
+ /**
441
+ * Maximum lifetime in seconds for client assertion JWTs
442
+ * used with `private_key_jwt` authentication.
443
+ *
444
+ * @default 300 (5 minutes)
445
+ */
446
+ assertionMaxLifetime?: number;
391
447
  /**
392
448
  * Allows /oauth2/public-client-prelogin endpoint to be
393
449
  * requestable prior to login via a valid oauth_query.
@@ -396,10 +452,13 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
396
452
  /**
397
453
  * Allow unauthenticated dynamic client registration.
398
454
  *
399
- * Support for `allowUnauthenticatedClientRegistration` **will be deprecated**
400
- * when the MCP protocol standardizes unauthenticated dynamic client registration.
401
- * As of writing, both [Client ID Metadata Documents](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/991)
402
- * and [`software_statement` and `jwks_uri`](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1032) are under debate.
455
+ * When enabled, the `/oauth2/register` endpoint accepts requests
456
+ * without a session, but only for public clients
457
+ * (`token_endpoint_auth_method: "none"`).
458
+ *
459
+ * For verified client discovery (MCP), consider installing the
460
+ * `@better-auth/cimd` plugin, which verifies client identity through
461
+ * domain ownership via Client ID Metadata Documents.
403
462
  *
404
463
  * @default false
405
464
  */
@@ -410,6 +469,21 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
410
469
  * @default false
411
470
  */
412
471
  allowDynamicClientRegistration?: boolean;
472
+ /**
473
+ * Discovery implementations consulted by `getClient()` when resolving
474
+ * a `client_id`. Each entry decides whether it handles the `client_id`
475
+ * via {@link ClientDiscovery.matches}, then creates, refreshes, or
476
+ * passes on a client record. Entries run in order; the first one to
477
+ * return a client wins.
478
+ *
479
+ * Each entry also contributes {@link ClientDiscovery.discoveryMetadata}
480
+ * into the `/.well-known/oauth-authorization-server` and
481
+ * `/.well-known/openid-configuration` responses.
482
+ *
483
+ * Plugins such as `@better-auth/cimd` install an entry here at init
484
+ * time; users can also pass discovery implementations directly.
485
+ */
486
+ clientDiscovery?: ClientDiscovery<Scopes> | ClientDiscovery<Scopes>[];
413
487
  /**
414
488
  * List of scopes for newly registered clients
415
489
  * if not requested.
@@ -1186,9 +1260,13 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
1186
1260
  * For example, `https://example.com/logout/callback`
1187
1261
  */
1188
1262
  postLogoutRedirectUris?: string[];
1189
- tokenEndpointAuthMethod?: "none" | "client_secret_basic" | "client_secret_post";
1263
+ tokenEndpointAuthMethod?: "none" | "client_secret_basic" | "client_secret_post" | "private_key_jwt";
1190
1264
  grantTypes?: GrantType[];
1191
1265
  responseTypes?: "code"[];
1266
+ /** Client's JSON Web Key Set for `private_key_jwt` authentication. Mutually exclusive with `jwksUri`. */
1267
+ jwks?: string;
1268
+ /** URI for the client's JSON Web Key Set. Mutually exclusive with `jwks`. Must be HTTPS. */
1269
+ jwksUri?: string;
1192
1270
  /**
1193
1271
  * Indicates whether the client is public or confidential.
1194
1272
  * If public, refreshing tokens doesn't require
@@ -1325,7 +1403,7 @@ type OAuthConsent<Scopes extends readonly Scope[] = InternallySupportedScopes[]>
1325
1403
  * Supported grant types of the token endpoint
1326
1404
  */
1327
1405
  type GrantType = "authorization_code" | "client_credentials" | "refresh_token";
1328
- type AuthMethod = "client_secret_basic" | "client_secret_post";
1406
+ type AuthMethod = "client_secret_basic" | "client_secret_post" | "private_key_jwt";
1329
1407
  type TokenEndpointAuthMethod = AuthMethod | "none";
1330
1408
  type BearerMethodsSupported = "header" | "body";
1331
1409
  /**
@@ -1402,7 +1480,7 @@ interface AuthServerMetadata {
1402
1480
  * token endpoint for the "private_key_jwt" and "client_secret_jwt"
1403
1481
  * authentication methods (see field token_endpoint_auth_methods_supported).
1404
1482
  */
1405
- token_endpoint_auth_signing_alg_values_supported?: JWSAlgorithms[];
1483
+ token_endpoint_auth_signing_alg_values_supported?: AssertionSigningAlgorithm[];
1406
1484
  /**
1407
1485
  * URL of a page containing human-readable information
1408
1486
  * that developers might want or need to know when using the
@@ -1448,7 +1526,7 @@ interface AuthServerMetadata {
1448
1526
  * token endpoint for the "private_key_jwt" and "client_secret_jwt"
1449
1527
  * authentication methods (see field revocation_endpoint_auth_methods_supported).
1450
1528
  */
1451
- revocation_endpoint_auth_signing_alg_values_supported?: JWSAlgorithms[];
1529
+ revocation_endpoint_auth_signing_alg_values_supported?: AssertionSigningAlgorithm[];
1452
1530
  /**
1453
1531
  * URL of the authorization server's OAuth 2.0
1454
1532
  * introspection endpoint [RFC7662](https://datatracker.ietf.org/doc/html/rfc7662)
@@ -1469,7 +1547,7 @@ interface AuthServerMetadata {
1469
1547
  * the "private_key_jwt" and "client_secret_jwt" authentication methods
1470
1548
  * (see field introspection_endpoint_auth_methods_supported).
1471
1549
  */
1472
- introspection_endpoint_auth_signing_alg_values_supported?: JWSAlgorithms[];
1550
+ introspection_endpoint_auth_signing_alg_values_supported?: AssertionSigningAlgorithm[];
1473
1551
  /**
1474
1552
  * Supported code challenge methods.
1475
1553
  *
@@ -1484,6 +1562,17 @@ interface AuthServerMetadata {
1484
1562
  * @default true
1485
1563
  */
1486
1564
  authorization_response_iss_parameter_supported?: boolean;
1565
+ /**
1566
+ * Whether the authorization server supports discovering clients via
1567
+ * [Client ID Metadata Documents](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/)
1568
+ * (an HTTPS URL as `client_id`).
1569
+ *
1570
+ * Set at runtime by the `@better-auth/cimd` plugin (or any other
1571
+ * `ClientDiscovery` that contributes this field via
1572
+ * {@link ClientDiscovery.discoveryMetadata}). oauth-provider never sets
1573
+ * it on its own.
1574
+ */
1575
+ client_id_metadata_document_supported?: boolean;
1487
1576
  }
1488
1577
  /**
1489
1578
  * Metadata returned by the openid-configuration endpoint:
@@ -1576,14 +1665,17 @@ interface OAuthClient {
1576
1665
  contacts?: string[];
1577
1666
  tos_uri?: string;
1578
1667
  policy_uri?: string;
1579
- jwks?: string[];
1668
+ /** JWK Set — accepts either a bare key array or an RFC 7517 JWKS object `{"keys":[...]}` */
1669
+ jwks?: Record<string, unknown>[] | {
1670
+ keys: Record<string, unknown>[];
1671
+ };
1580
1672
  jwks_uri?: string;
1581
1673
  software_id?: string;
1582
1674
  software_version?: string;
1583
1675
  software_statement?: string;
1584
1676
  redirect_uris: string[];
1585
1677
  post_logout_redirect_uris?: string[];
1586
- token_endpoint_auth_method?: "none" | "client_secret_basic" | "client_secret_post";
1678
+ token_endpoint_auth_method?: "none" | "client_secret_basic" | "client_secret_post" | "private_key_jwt";
1587
1679
  grant_types?: GrantType[];
1588
1680
  response_types?: "code"[];
1589
1681
  public?: boolean;
@@ -1647,4 +1739,4 @@ interface ResourceServerMetadata {
1647
1739
  dpop_bound_access_tokens_required?: boolean;
1648
1740
  }
1649
1741
  //#endregion
1650
- export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
1742
+ export { Scope as _, OIDCMetadata as a, Awaitable as b, AuthorizePrompt as c, OAuthConsent as d, OAuthOpaqueAccessToken as f, SchemaClient as g, Prompt as h, OAuthClient as i, ClientDiscovery as l, OAuthRefreshToken as m, AuthServerMetadata as n, ResourceServerMetadata as o, OAuthOptions as p, GrantType as r, TokenEndpointAuthMethod as s, AuthMethod as t, OAuthAuthorizationQuery as u, StoreTokenType as v, VerificationValue as y };
@@ -1,62 +1,8 @@
1
- import { isAPIError } from "better-auth/api";
2
- import { verifyAccessToken } from "better-auth/oauth2";
3
- import { APIError as APIError$1 } from "better-call";
1
+ import { APIError } from "better-call";
4
2
  import { constantTimeEqual, makeSignature, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
5
3
  import { BetterAuthError } from "@better-auth/core/error";
6
4
  import { base64, base64Url } from "@better-auth/utils/base64";
7
5
  import { createHash } from "@better-auth/utils/hash";
8
- //#region src/mcp.ts
9
- /**
10
- * A request middleware handler that checks and responds with
11
- * a WWW-Authenticate header for unauthenticated responses.
12
- *
13
- * @external
14
- */
15
- const mcpHandler = (verifyOptions, handler, opts) => {
16
- return async (req) => {
17
- const authorization = req.headers?.get("authorization") ?? void 0;
18
- const accessToken = authorization?.startsWith("Bearer ") ? authorization.replace("Bearer ", "") : authorization;
19
- try {
20
- if (!accessToken?.length) throw new APIError$1("UNAUTHORIZED", { message: "missing authorization header" });
21
- return handler(req, await verifyAccessToken(accessToken, verifyOptions));
22
- } catch (error) {
23
- try {
24
- handleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);
25
- } catch (err) {
26
- if (err instanceof APIError$1) return new Response(err.message, {
27
- ...err,
28
- status: err.statusCode
29
- });
30
- throw new Error(String(err));
31
- }
32
- throw new Error(String(error));
33
- }
34
- };
35
- };
36
- /**
37
- * The following handles all MCP errors and API errors
38
- *
39
- * @internal
40
- */
41
- function handleMcpErrors(error, resource, opts) {
42
- if (isAPIError(error) && error.status === "UNAUTHORIZED") {
43
- const wwwAuthenticateValue = (Array.isArray(resource) ? resource : [resource]).map((v) => {
44
- let audiencePath;
45
- if (URL.canParse?.(v)) {
46
- const url = new URL(v);
47
- audiencePath = url.pathname.endsWith("/") ? url.pathname.slice(0, -1) : url.pathname;
48
- return `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource${audiencePath}"`;
49
- } else {
50
- const resourceMetadata = opts?.resourceMetadataMappings?.[v];
51
- if (!resourceMetadata) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
52
- return `Bearer resource_metadata=${resourceMetadata}`;
53
- }
54
- }).join(", ");
55
- throw new APIError$1("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
56
- } else if (error instanceof Error) throw error;
57
- else throw new Error(error);
58
- }
59
- //#endregion
60
6
  //#region src/utils/index.ts
61
7
  var TTLCache = class {
62
8
  cache = /* @__PURE__ */ new Map();
@@ -143,17 +89,49 @@ async function verifyOAuthQueryParams(oauth_query, secret) {
143
89
  async function getClient(ctx, options, clientId) {
144
90
  const trustedClient = cachedTrustedClients.get(clientId);
145
91
  if (trustedClient) return Object.assign({}, trustedClient);
146
- const dbClient = await ctx.context.adapter.findOne({
92
+ let dbClient = await ctx.context.adapter.findOne({
147
93
  model: options.schema?.oauthClient?.modelName ?? "oauthClient",
148
94
  where: [{
149
95
  field: "clientId",
150
96
  value: clientId
151
97
  }]
152
98
  });
99
+ const discoveries = toClientDiscoveryArray(options.clientDiscovery);
100
+ for (const discovery of discoveries) {
101
+ if (!discovery.matches(clientId)) continue;
102
+ const resolved = await discovery.resolve(ctx, clientId, dbClient);
103
+ if (resolved) {
104
+ dbClient = resolved;
105
+ break;
106
+ }
107
+ }
153
108
  if (dbClient && options.cachedTrustedClients?.has(clientId)) cachedTrustedClients.set(clientId, Object.assign({}, dbClient));
154
109
  return dbClient;
155
110
  }
156
111
  /**
112
+ * Normalize the `clientDiscovery` option into an array. Accepts a single
113
+ * {@link ClientDiscovery}, an array of them, or `undefined`.
114
+ *
115
+ * @internal
116
+ */
117
+ function toClientDiscoveryArray(discovery) {
118
+ if (!discovery) return [];
119
+ return Array.isArray(discovery) ? discovery : [discovery];
120
+ }
121
+ /**
122
+ * Merge `discoveryMetadata` from every configured {@link ClientDiscovery}
123
+ * into a single object. Entries are spread in order; later entries override
124
+ * earlier ones on key collisions.
125
+ *
126
+ * @internal
127
+ */
128
+ function mergeDiscoveryMetadata(discovery) {
129
+ return toClientDiscoveryArray(discovery).reduce((acc, d) => ({
130
+ ...acc,
131
+ ...d.discoveryMetadata ?? {}
132
+ }), {});
133
+ }
134
+ /**
157
135
  * Default client secret hasher using SHA-256
158
136
  *
159
137
  * @internal
@@ -183,7 +161,7 @@ async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret)
183
161
  async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSecret) {
184
162
  const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
185
163
  if (clientSecret && opts.prefix?.clientSecret) if (clientSecret.startsWith(opts.prefix?.clientSecret)) clientSecret = clientSecret.replace(opts.prefix.clientSecret, "");
186
- else throw new APIError$1("UNAUTHORIZED", {
164
+ else throw new APIError("UNAUTHORIZED", {
187
165
  error_description: "invalid client_secret",
188
166
  error: "invalid_client"
189
167
  });
@@ -254,12 +232,12 @@ function basicToClientCredentials(authorization) {
254
232
  if (authorization.startsWith("Basic ")) {
255
233
  const encoded = authorization.replace("Basic ", "");
256
234
  const decoded = new TextDecoder().decode(base64.decode(encoded));
257
- if (!decoded.includes(":")) throw new APIError$1("BAD_REQUEST", {
235
+ if (!decoded.includes(":")) throw new APIError("BAD_REQUEST", {
258
236
  error_description: "invalid authorization header format",
259
237
  error: "invalid_client"
260
238
  });
261
239
  const [id, secret] = decoded.split(":", 2);
262
- if (!id || !secret) throw new APIError$1("BAD_REQUEST", {
240
+ if (!id || !secret) throw new APIError("BAD_REQUEST", {
263
241
  error_description: "invalid authorization header format",
264
242
  error: "invalid_client"
265
243
  });
@@ -275,31 +253,37 @@ function basicToClientCredentials(authorization) {
275
253
  *
276
254
  * @internal
277
255
  */
278
- async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
279
- const client = await getClient(ctx, options, clientId);
280
- if (!client) throw new APIError$1("BAD_REQUEST", {
256
+ async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes, preVerifiedClient) {
257
+ const client = preVerifiedClient ?? await getClient(ctx, options, clientId);
258
+ if (!client) throw new APIError("BAD_REQUEST", {
281
259
  error_description: "missing client",
282
260
  error: "invalid_client"
283
261
  });
284
- if (client.disabled) throw new APIError$1("BAD_REQUEST", {
262
+ if (client.disabled) throw new APIError("BAD_REQUEST", {
285
263
  error_description: "client is disabled",
286
264
  error: "invalid_client"
287
265
  });
288
- if (!client.public && !clientSecret) throw new APIError$1("BAD_REQUEST", {
289
- error_description: "client secret must be provided",
290
- error: "invalid_client"
291
- });
292
- if (clientSecret && !client.clientSecret) throw new APIError$1("BAD_REQUEST", {
293
- error_description: "public client, client secret should not be received",
294
- error: "invalid_client"
295
- });
296
- if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError$1("UNAUTHORIZED", {
297
- error_description: "invalid client_secret",
266
+ if (client.tokenEndpointAuthMethod === "private_key_jwt" && !preVerifiedClient) throw new APIError("BAD_REQUEST", {
267
+ error_description: "client registered for private_key_jwt must use client_assertion",
298
268
  error: "invalid_client"
299
269
  });
270
+ if (!preVerifiedClient) {
271
+ if (!client.public && !clientSecret) throw new APIError("BAD_REQUEST", {
272
+ error_description: "client secret must be provided",
273
+ error: "invalid_client"
274
+ });
275
+ if (clientSecret && !client.clientSecret) throw new APIError("BAD_REQUEST", {
276
+ error_description: "public client, client secret should not be received",
277
+ error: "invalid_client"
278
+ });
279
+ if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError("UNAUTHORIZED", {
280
+ error_description: "invalid client_secret",
281
+ error: "invalid_client"
282
+ });
283
+ }
300
284
  if (scopes && client.scopes) {
301
285
  const validScopes = new Set(client.scopes);
302
- for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError$1("BAD_REQUEST", {
286
+ for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError("BAD_REQUEST", {
303
287
  error_description: `client does not allow scope ${sc}`,
304
288
  error: "invalid_scope"
305
289
  });
@@ -316,6 +300,57 @@ function parseClientMetadata(metadata) {
316
300
  if (!metadata) return void 0;
317
301
  return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
318
302
  }
303
+ /** Unwraps ExtractedCredentials into the fields each grant handler needs. */
304
+ function destructureCredentials(credentials) {
305
+ return {
306
+ clientId: credentials?.clientId,
307
+ clientSecret: credentials?.method === "client_secret_basic" || credentials?.method === "client_secret_post" ? credentials.clientSecret : void 0,
308
+ preVerifiedClient: credentials?.method === "private_key_jwt" ? credentials.client : void 0
309
+ };
310
+ }
311
+ /**
312
+ * Extracts and resolves client credentials from the request.
313
+ * Supports: client_secret_basic, client_secret_post, private_key_jwt, and none (public).
314
+ */
315
+ async function extractClientCredentials(ctx, opts, expectedAudience) {
316
+ const body = ctx.body ?? {};
317
+ const authorization = ctx.request?.headers.get("authorization") ?? void 0;
318
+ if (body.client_assertion_type || body.client_assertion) {
319
+ if (!body.client_assertion || !body.client_assertion_type) throw new APIError("BAD_REQUEST", {
320
+ error_description: "client_assertion and client_assertion_type must both be provided",
321
+ error: "invalid_client"
322
+ });
323
+ if (body.client_secret || authorization?.startsWith("Basic ")) throw new APIError("BAD_REQUEST", {
324
+ error_description: "client_assertion cannot be combined with client_secret or Basic auth",
325
+ error: "invalid_client"
326
+ });
327
+ const { verifyClientAssertion: verify } = await import("./client-assertion-CderPEmR.mjs").then((n) => n.t);
328
+ const result = await verify(ctx, opts, body.client_assertion, body.client_assertion_type, body.client_id, expectedAudience);
329
+ return {
330
+ method: "private_key_jwt",
331
+ clientId: result.clientId,
332
+ client: result.client
333
+ };
334
+ }
335
+ if (authorization?.startsWith("Basic ")) {
336
+ const res = basicToClientCredentials(authorization);
337
+ if (res) return {
338
+ method: "client_secret_basic",
339
+ clientId: res.client_id,
340
+ clientSecret: res.client_secret
341
+ };
342
+ }
343
+ if (body.client_id && body.client_secret) return {
344
+ method: "client_secret_post",
345
+ clientId: body.client_id,
346
+ clientSecret: body.client_secret
347
+ };
348
+ if (body.client_id) return {
349
+ method: "none",
350
+ clientId: body.client_id
351
+ };
352
+ return null;
353
+ }
319
354
  /**
320
355
  * Parse space-separated prompt string into a set of prompts
321
356
  *
@@ -411,4 +446,4 @@ function isPKCERequired(client, requestedScopes) {
411
446
  return false;
412
447
  }
413
448
  //#endregion
414
- export { validateClientCredentials as _, getJwtPlugin as a, mcpHandler as b, isPKCERequired as c, parsePrompt as d, resolveSessionAuthTime as f, storeToken as g, storeClientSecret as h, getClient as i, normalizeTimestampValue as l, searchParamsToQuery as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, resolveSubjectIdentifier as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, parseClientMetadata as u, verifyOAuthQueryParams as v, handleMcpErrors as y };
449
+ export { storeClientSecret as _, getClient as a, validateClientCredentials as b, getStoredToken as c, normalizeTimestampValue as d, parseClientMetadata as f, searchParamsToQuery as g, resolveSubjectIdentifier as h, extractClientCredentials as i, isPKCERequired as l, resolveSessionAuthTime as m, deleteFromPrompt as n, getJwtPlugin as o, parsePrompt as p, destructureCredentials as r, getOAuthProviderPlugin as s, decryptStoredClientSecret as t, mergeDiscoveryMetadata as u, storeToken as v, verifyOAuthQueryParams as x, toClientDiscoveryArray as y };
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.3";
3
+ const PACKAGE_VERSION = "1.7.0-beta.1";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };