@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.
- package/dist/client-assertion-CderPEmR.mjs +281 -0
- package/dist/client-resource.d.mts +1 -1
- package/dist/client-resource.mjs +3 -2
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +48 -4
- package/dist/index.mjs +193 -97
- package/dist/mcp-CYnz-MXn.mjs +56 -0
- package/dist/{oauth-DH61OMT6.d.mts → oauth-B_qonG53.d.mts} +119 -3
- package/dist/{oauth-IpUqx-_N.d.mts → oauth-CU79t-eG.d.mts} +104 -12
- package/dist/{utils-B9Pj9EPf.mjs → utils-Cx_XnD9i.mjs} +110 -75
- package/dist/{version-BlcZ64XB.mjs → version-DIwdpXrQ.mjs} +1 -1
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
289
|
-
error_description: "client
|
|
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
|
|
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 {
|
|
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 };
|