@better-auth/oauth-provider 1.5.0-beta.13 → 1.5.0-beta.15

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/index.mjs CHANGED
@@ -1,158 +1,58 @@
1
- import { a as getJwtPlugin, c as parseClientMetadata, d as storeToken, f as validateClientCredentials, i as getClient, l as parsePrompt, m as mcpHandler, n as decryptStoredClientSecret, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as storeClientSecret } from "./utils-DWE-cPWY.mjs";
1
+ import { a as getJwtPlugin, c as isPKCERequired, d as storeClientSecret, f as storeToken, h as mcpHandler, i as getClient, l as parseClientMetadata, n as decryptStoredClientSecret, p as validateClientCredentials, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as parsePrompt } from "./utils-fj4yCZS4.mjs";
2
2
  import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
3
3
  import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
4
4
  import { APIError as APIError$1 } from "better-call";
5
5
  import { constantTimeEqual, generateRandomString, makeSignature } from "better-auth/crypto";
6
- import { BetterAuthError } from "@better-auth/core/error";
7
6
  import { defineRequestState } from "@better-auth/core/context";
8
7
  import { logger } from "@better-auth/core/env";
8
+ import { BetterAuthError } from "@better-auth/core/error";
9
9
  import { parseSetCookieHeader } from "better-auth/cookies";
10
10
  import { mergeSchema } from "better-auth/db";
11
11
  import * as z from "zod";
12
12
  import { signJWT, toExpJWT } from "better-auth/plugins";
13
13
  import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
14
14
 
15
- //#region src/authorize.ts
16
- /**
17
- * Formats an error url
18
- */
19
- function formatErrorURL(url, error, description, state, iss) {
20
- const searchParams = new URLSearchParams({
21
- error,
22
- error_description: description
23
- });
24
- state && searchParams.append("state", state);
25
- iss && searchParams.append("iss", iss);
26
- return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
27
- }
28
- const handleRedirect = (ctx, uri) => {
29
- if (ctx.headers?.get("accept")?.includes("application/json")) return {
30
- redirect: true,
31
- url: uri.toString()
32
- };
33
- else throw ctx.redirect(uri);
34
- };
35
- /**
36
- * Validates that the issuer URL
37
- * - MUST use HTTPS scheme (HTTP allowed for localhost in dev)
38
- * - MUST NOT contain query components
39
- * - MUST NOT contain fragment components
40
- *
41
- * @returns The validated issuer URL, or a sanitized version if invalid
42
- */
43
- function validateIssuerUrl(issuer) {
44
- try {
45
- const url = new URL(issuer);
46
- const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
47
- if (url.protocol !== "https:" && !isLocalhost) url.protocol = "https:";
48
- url.search = "";
49
- url.hash = "";
50
- return url.toString().replace(/\/$/, "");
51
- } catch {
52
- return issuer;
53
- }
54
- }
55
- /**
56
- * Gets the issuer identifier
57
- */
58
- function getIssuer(ctx, opts) {
59
- let issuer;
60
- if (opts.disableJwtPlugin) issuer = ctx.context.baseURL;
61
- else try {
62
- issuer = getJwtPlugin(ctx.context).options?.jwt?.issuer ?? ctx.context.baseURL;
63
- } catch {
64
- issuer = ctx.context.baseURL;
65
- }
66
- return validateIssuerUrl(issuer);
67
- }
68
- /**
69
- * Error page url if redirect_uri has not been verified yet
70
- * Generates Url for custom error page
71
- */
72
- function getErrorURL(ctx, error, description) {
73
- return formatErrorURL(ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`, error, description);
74
- }
75
- async function authorizeEndpoint(ctx, opts, settings) {
76
- if (opts.grantTypes && !opts.grantTypes.includes("authorization_code")) throw new APIError$1("NOT_FOUND");
77
- if (!ctx.request) throw new APIError$1("UNAUTHORIZED", {
78
- error_description: "request not found",
15
+ //#region src/consent.ts
16
+ async function consentEndpoint(ctx, opts) {
17
+ const _query = (await oAuthState.get())?.query;
18
+ if (!_query) throw new APIError("BAD_REQUEST", {
19
+ error_description: "missing oauth query",
79
20
  error: "invalid_request"
80
21
  });
81
- const query = ctx.query;
82
- if (!query.client_id) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
83
- if (!query.response_type) throw ctx.redirect(getErrorURL(ctx, "invalid_request", "response_type is required"));
84
- const promptSet = ctx.query?.prompt ? parsePrompt(ctx.query?.prompt) : void 0;
85
- if (promptSet?.has("select_account") && !opts.selectAccount?.page) throw ctx.redirect(getErrorURL(ctx, `unsupported_prompt_select_account`, "unsupported prompt type"));
86
- if (!(query.response_type === "code")) throw ctx.redirect(getErrorURL(ctx, "unsupported_response_type", "unsupported response type"));
87
- const client = await getClient(ctx, opts, query.client_id);
88
- if (!client) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
89
- if (client.disabled) throw ctx.redirect(getErrorURL(ctx, "client_disabled", "client is disabled"));
90
- if (!client.redirectUris?.find((url) => url === query.redirect_uri) || !query.redirect_uri) throw ctx.redirect(getErrorURL(ctx, "invalid_redirect", "invalid redirect uri"));
91
- let requestedScopes = query.scope?.split(" ").filter((s) => s);
22
+ const query = new URLSearchParams(_query);
23
+ const originalRequestedScopes = query.get("scope")?.split(" ") ?? [];
24
+ const clientId = query.get("client_id");
25
+ if (!clientId) throw new APIError("BAD_REQUEST", {
26
+ error_description: "client_id is required",
27
+ error: "invalid_client"
28
+ });
29
+ const requestedScopes = ctx.body.scope?.split(" ");
92
30
  if (requestedScopes) {
93
- const validScopes = new Set(client.scopes ?? opts.scopes);
94
- const invalidScopes = requestedScopes.filter((scope) => {
95
- return !validScopes?.has(scope) || scope === "offline_access" && (query.code_challenge_method !== "S256" || !query.code_challenge);
31
+ if (!requestedScopes.every((sc) => originalRequestedScopes?.includes(sc))) throw new APIError("BAD_REQUEST", {
32
+ error_description: "Scope not originally requested",
33
+ error: "invalid_request"
96
34
  });
97
- if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state, getIssuer(ctx, opts)));
98
35
  }
99
- if (!requestedScopes) {
100
- requestedScopes = client.scopes ?? opts.scopes ?? [];
101
- query.scope = requestedScopes.join(" ");
102
- }
103
- if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "pkce is required", query.state, getIssuer(ctx, opts)));
104
- if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method", query.state, getIssuer(ctx, opts)));
36
+ if (!(ctx.body.accept === true)) return {
37
+ redirect: true,
38
+ uri: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0, getIssuer(ctx, opts))
39
+ };
105
40
  const session = await getSessionFromCtx(ctx);
106
- if (!session || promptSet?.has("login") || promptSet?.has("create")) return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
107
- if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
108
- if (settings?.isAuthorize && opts.selectAccount) {
109
- if (await opts.selectAccount.shouldRedirect({
110
- headers: ctx.request.headers,
111
- user: session.user,
112
- session: session.session,
113
- scopes: requestedScopes
114
- })) return redirectWithPromptCode(ctx, opts, "select_account");
115
- }
116
- if (opts.signup?.shouldRedirect) {
117
- const signupRedirect = await opts.signup.shouldRedirect({
118
- headers: ctx.request.headers,
119
- user: session.user,
120
- session: session.session,
121
- scopes: requestedScopes
122
- });
123
- if (signupRedirect) return redirectWithPromptCode(ctx, opts, "create", typeof signupRedirect === "string" ? signupRedirect : void 0);
124
- }
125
- if (!settings?.postLogin && opts.postLogin) {
126
- if (await opts.postLogin.shouldRedirect({
127
- headers: ctx.request.headers,
128
- user: session.user,
129
- session: session.session,
130
- scopes: requestedScopes
131
- })) return redirectWithPromptCode(ctx, opts, "post_login");
132
- }
133
- if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent");
134
41
  const referenceId = await opts.postLogin?.consentReferenceId?.({
135
- user: session.user,
136
- session: session.session,
137
- scopes: requestedScopes
138
- });
139
- if (client.skipConsent) return redirectWithAuthorizationCode(ctx, opts, {
140
- query,
141
- clientId: client.clientId,
142
- userId: session.user.id,
143
- sessionId: session.session.id,
144
- referenceId
42
+ user: session?.user,
43
+ session: session?.session,
44
+ scopes: requestedScopes ?? originalRequestedScopes
145
45
  });
146
- const consent = await ctx.context.adapter.findOne({
46
+ const foundConsent = await ctx.context.adapter.findOne({
147
47
  model: "oauthConsent",
148
48
  where: [
149
49
  {
150
50
  field: "clientId",
151
- value: client.clientId
51
+ value: clientId
152
52
  },
153
53
  {
154
54
  field: "userId",
155
- value: session.user.id
55
+ value: session?.user.id
156
56
  },
157
57
  ...referenceId ? [{
158
58
  field: "referenceId",
@@ -160,264 +60,63 @@ async function authorizeEndpoint(ctx, opts, settings) {
160
60
  }] : []
161
61
  ]
162
62
  });
163
- if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val))) return redirectWithPromptCode(ctx, opts, "consent");
164
- return redirectWithAuthorizationCode(ctx, opts, {
165
- query,
166
- clientId: client.clientId,
167
- userId: session.user.id,
168
- sessionId: session.session.id,
169
- referenceId
170
- });
171
- }
172
- async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
173
- const code = generateRandomString(32, "a-z", "A-Z", "0-9");
174
63
  const iat = Math.floor(Date.now() / 1e3);
175
- const exp = iat + (opts.codeExpiresIn ?? 600);
176
- const data = {
177
- identifier: await storeToken(opts.storeTokens, code, "authorization_code"),
64
+ const consent = {
65
+ clientId,
66
+ userId: session?.user.id,
67
+ scopes: requestedScopes ?? originalRequestedScopes,
68
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3),
178
69
  updatedAt: /* @__PURE__ */ new Date(iat * 1e3),
179
- expiresAt: /* @__PURE__ */ new Date(exp * 1e3),
180
- value: JSON.stringify({
181
- type: "authorization_code",
182
- query: ctx.query,
183
- userId: verificationValue.userId,
184
- sessionId: verificationValue?.sessionId,
185
- referenceId: verificationValue.referenceId
186
- })
70
+ referenceId
187
71
  };
188
- ctx.context.verification_id ? await ctx.context.internalAdapter.updateVerificationValue(ctx.context.verification_id, data) : await ctx.context.internalAdapter.createVerificationValue({
189
- ...data,
190
- createdAt: /* @__PURE__ */ new Date(iat * 1e3)
72
+ foundConsent?.id ? await ctx.context.adapter.update({
73
+ model: "oauthConsent",
74
+ where: [{
75
+ field: "id",
76
+ value: foundConsent.id
77
+ }],
78
+ update: {
79
+ scopes: consent.scopes,
80
+ updatedAt: /* @__PURE__ */ new Date(iat * 1e3)
81
+ }
82
+ }) : await ctx.context.adapter.create({
83
+ model: "oauthConsent",
84
+ data: {
85
+ ...consent,
86
+ scopes: consent.scopes
87
+ }
191
88
  });
192
- const redirectUriWithCode = new URL(verificationValue.query.redirect_uri);
193
- redirectUriWithCode.searchParams.set("code", code);
194
- if (verificationValue.query.state) redirectUriWithCode.searchParams.set("state", verificationValue.query.state);
195
- redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
196
- return handleRedirect(ctx, redirectUriWithCode.toString());
197
- }
198
- async function redirectWithPromptCode(ctx, opts, type, page) {
199
- const queryParams = await signParams(ctx, opts);
200
- let path = opts.loginPage;
201
- if (type === "select_account") path = opts.selectAccount?.page ?? opts.loginPage;
202
- else if (type === "post_login") {
203
- if (!opts.postLogin?.page) throw new APIError$1("INTERNAL_SERVER_ERROR", { error_description: "postLogin should have been defined" });
204
- path = opts.postLogin?.page;
205
- } else if (type === "consent") path = opts.consentPage;
206
- else if (type === "create") path = opts.signup?.page ?? opts.loginPage;
207
- return handleRedirect(ctx, `${page ?? path}?${queryParams}`);
208
- }
209
- async function signParams(ctx, opts) {
210
- const exp = Math.floor(Date.now() / 1e3) + (opts.codeExpiresIn ?? 600);
211
- const params = new URLSearchParams(ctx.query);
212
- params.set("exp", String(exp));
213
- const signature = await makeSignature(params.toString(), ctx.context.secret);
214
- params.append("sig", signature);
215
- return params.toString();
89
+ if (requestedScopes) query.set("scope", consent.scopes.join(" "));
90
+ ctx?.headers?.set("accept", "application/json");
91
+ ctx.query = deleteFromPrompt(query, "consent");
92
+ ctx.context.postLogin = true;
93
+ const { url } = await authorizeEndpoint(ctx, opts);
94
+ return {
95
+ redirect: true,
96
+ uri: url
97
+ };
216
98
  }
217
99
 
218
100
  //#endregion
219
- //#region src/metadata.ts
220
- function authServerMetadata(ctx, opts, overrides) {
221
- const baseURL = ctx.context.baseURL;
222
- return {
223
- scopes_supported: overrides?.scopes_supported,
224
- issuer: validateIssuerUrl(opts?.jwt?.issuer ?? baseURL),
225
- authorization_endpoint: `${baseURL}/oauth2/authorize`,
226
- token_endpoint: `${baseURL}/oauth2/token`,
227
- jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
228
- registration_endpoint: `${baseURL}/oauth2/register`,
229
- introspection_endpoint: `${baseURL}/oauth2/introspect`,
230
- revocation_endpoint: `${baseURL}/oauth2/revoke`,
231
- response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
232
- response_modes_supported: ["query"],
233
- grant_types_supported: overrides?.grant_types_supported ?? [
234
- "authorization_code",
235
- "client_credentials",
236
- "refresh_token"
237
- ],
238
- token_endpoint_auth_methods_supported: [
239
- ...overrides?.public_client_supported ? ["none"] : [],
240
- "client_secret_basic",
241
- "client_secret_post"
242
- ],
243
- introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
244
- revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
245
- code_challenge_methods_supported: ["S256"],
246
- authorization_response_iss_parameter_supported: true
247
- };
248
- }
249
- function oidcServerMetadata(ctx, opts) {
250
- const baseURL = ctx.context.baseURL;
251
- const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
252
- return {
253
- ...authServerMetadata(ctx, jwtPluginOptions, {
254
- scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
255
- public_client_supported: opts.allowUnauthenticatedClientRegistration,
256
- grant_types_supported: opts.grantTypes,
257
- jwt_disabled: opts.disableJwtPlugin
258
- }),
259
- claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
260
- userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
261
- subject_types_supported: ["public"],
262
- id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
263
- end_session_endpoint: `${baseURL}/oauth2/end-session`,
264
- acr_values_supported: ["urn:mace:incommon:iap:bronze"],
265
- prompt_values_supported: [
266
- "login",
267
- "consent",
268
- "create",
269
- "select_account"
270
- ]
271
- };
272
- }
273
- /**
274
- * Provides an exportable `/.well-known/oauth-authorization-server`.
275
- *
276
- * Useful when basePath prevents the endpoint from being located at the root
277
- * and must be provided manually.
278
- *
279
- * @external
280
- */
281
- const oauthProviderAuthServerMetadata = (auth, opts) => {
282
- return async (_request) => {
283
- const res = await auth.api.getOAuthServerConfig();
284
- return new Response(JSON.stringify(res), {
285
- status: 200,
286
- headers: {
287
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
288
- ...opts?.headers,
289
- "Content-Type": "application/json"
290
- }
291
- });
292
- };
293
- };
294
- /**
295
- * Provides an exportable `/.well-known/openid-configuration`.
296
- *
297
- * Useful when basePath prevents the endpoint from being located at the root
298
- * and must be provided manually.
299
- *
300
- * @external
301
- */
302
- const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
303
- return async (_request) => {
304
- const res = await auth.api.getOpenIdConfig();
305
- return new Response(JSON.stringify(res), {
306
- status: 200,
307
- headers: {
308
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
309
- ...opts?.headers,
310
- "Content-Type": "application/json"
311
- }
312
- });
313
- };
314
- };
315
-
316
- //#endregion
317
- //#region src/consent.ts
318
- async function consentEndpoint(ctx, opts) {
319
- const _query = (await oAuthState.get())?.query;
320
- if (!_query) throw new APIError("BAD_REQUEST", {
321
- error_description: "missing oauth query",
322
- error: "invalid_request"
323
- });
324
- const query = new URLSearchParams(_query);
325
- const originalRequestedScopes = query.get("scope")?.split(" ") ?? [];
326
- const clientId = query.get("client_id");
327
- if (!clientId) throw new APIError("BAD_REQUEST", {
328
- error_description: "client_id is required",
329
- error: "invalid_client"
330
- });
331
- const requestedScopes = ctx.body.scope?.split(" ");
332
- if (requestedScopes) {
333
- if (!requestedScopes.every((sc) => originalRequestedScopes?.includes(sc))) throw new APIError("BAD_REQUEST", {
334
- error_description: "Scope not originally requested",
335
- error: "invalid_request"
336
- });
337
- }
338
- if (!(ctx.body.accept === true)) return {
339
- redirect: true,
340
- uri: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0, getIssuer(ctx, opts))
341
- };
342
- const session = await getSessionFromCtx(ctx);
343
- const referenceId = await opts.postLogin?.consentReferenceId?.({
344
- user: session?.user,
345
- session: session?.session,
346
- scopes: requestedScopes ?? originalRequestedScopes
347
- });
348
- const foundConsent = await ctx.context.adapter.findOne({
349
- model: "oauthConsent",
350
- where: [
351
- {
352
- field: "clientId",
353
- value: clientId
354
- },
355
- {
356
- field: "userId",
357
- value: session?.user.id
358
- },
359
- ...referenceId ? [{
360
- field: "referenceId",
361
- value: referenceId
362
- }] : []
363
- ]
364
- });
365
- const iat = Math.floor(Date.now() / 1e3);
366
- const consent = {
367
- clientId,
368
- userId: session?.user.id,
369
- scopes: requestedScopes ?? originalRequestedScopes,
370
- createdAt: /* @__PURE__ */ new Date(iat * 1e3),
371
- updatedAt: /* @__PURE__ */ new Date(iat * 1e3),
372
- referenceId
373
- };
374
- foundConsent?.id ? await ctx.context.adapter.update({
375
- model: "oauthConsent",
376
- where: [{
377
- field: "id",
378
- value: foundConsent.id
379
- }],
380
- update: {
381
- scopes: consent.scopes,
382
- updatedAt: /* @__PURE__ */ new Date(iat * 1e3)
383
- }
384
- }) : await ctx.context.adapter.create({
385
- model: "oauthConsent",
386
- data: {
387
- ...consent,
388
- scopes: consent.scopes
389
- }
390
- });
391
- ctx?.headers?.set("accept", "application/json");
392
- ctx.query = deleteFromPrompt(query, "consent");
393
- ctx.context.postLogin = true;
394
- const { url } = await authorizeEndpoint(ctx, opts);
395
- return {
396
- redirect: true,
397
- uri: url
398
- };
399
- }
400
-
401
- //#endregion
402
- //#region src/continue.ts
403
- async function continueEndpoint(ctx, opts) {
404
- if (ctx.body.selected === true) return await selected(ctx, opts);
405
- else if (ctx.body.created === true) return await created(ctx, opts);
406
- else if (ctx.body.postLogin === true) return await postLogin(ctx, opts);
407
- else throw new APIError("BAD_REQUEST", {
408
- error_description: "Missing parameters",
409
- error: "invalid_request"
410
- });
411
- }
412
- async function selected(ctx, opts) {
413
- const _query = (await oAuthState.get())?.query;
414
- if (!_query) throw new APIError("BAD_REQUEST", {
415
- error_description: "missing oauth query",
416
- error: "invalid_request"
417
- });
418
- ctx.headers?.set("accept", "application/json");
419
- ctx.query = deleteFromPrompt(new URLSearchParams(_query), "select_account");
420
- const { url } = await authorizeEndpoint(ctx, opts);
101
+ //#region src/continue.ts
102
+ async function continueEndpoint(ctx, opts) {
103
+ if (ctx.body.selected === true) return await selected(ctx, opts);
104
+ else if (ctx.body.created === true) return await created(ctx, opts);
105
+ else if (ctx.body.postLogin === true) return await postLogin(ctx, opts);
106
+ else throw new APIError("BAD_REQUEST", {
107
+ error_description: "Missing parameters",
108
+ error: "invalid_request"
109
+ });
110
+ }
111
+ async function selected(ctx, opts) {
112
+ const _query = (await oAuthState.get())?.query;
113
+ if (!_query) throw new APIError("BAD_REQUEST", {
114
+ error_description: "missing oauth query",
115
+ error: "invalid_request"
116
+ });
117
+ ctx.headers?.set("accept", "application/json");
118
+ ctx.query = deleteFromPrompt(new URLSearchParams(_query), "select_account");
119
+ const { url } = await authorizeEndpoint(ctx, opts);
421
120
  return {
422
121
  redirect: true,
423
122
  uri: url
@@ -796,8 +495,8 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
796
495
  });
797
496
  const isAuthCodeWithSecret = client_id && client_secret;
798
497
  const isAuthCodeWithPkce = client_id && code && code_verifier;
799
- if (!(isAuthCodeWithPkce || isAuthCodeWithSecret)) throw new APIError("BAD_REQUEST", {
800
- error_description: "Missing a required credential value for authorization_code grant",
498
+ if (!isAuthCodeWithSecret && !isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
499
+ error_description: "Either code_verifier or client_secret is required",
801
500
  error: "invalid_request"
802
501
  });
803
502
  /** Get and check Verification Value */
@@ -809,16 +508,32 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
809
508
  });
810
509
  /** Verify Client */
811
510
  const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes);
812
- /** Check challenge */
813
- const challenge = code_verifier && verificationValue.query?.code_challenge_method === "S256" ? await generateCodeChallenge(code_verifier) : void 0;
814
- if (isAuthCodeWithSecret && (challenge || verificationValue?.query?.code_challenge) && challenge !== verificationValue.query?.code_challenge) throw new APIError("UNAUTHORIZED", {
815
- error_description: "code verification failed",
816
- error: "invalid_request"
817
- });
818
- if (isAuthCodeWithPkce && challenge !== verificationValue.query?.code_challenge) throw new APIError("UNAUTHORIZED", {
819
- error_description: "code verification failed",
511
+ if (isPKCERequired(client, (verificationValue.query?.scope)?.split(" ") || [])) {
512
+ if (!isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
513
+ error_description: "PKCE is required for this client",
514
+ error: "invalid_request"
515
+ });
516
+ } else if (!(isAuthCodeWithPkce || isAuthCodeWithSecret)) throw new APIError("BAD_REQUEST", {
517
+ error_description: "Either PKCE (code_verifier) or client authentication (client_secret) is required",
820
518
  error: "invalid_request"
821
519
  });
520
+ /** Check PKCE challenge if verifier is provided */
521
+ const pkceUsedInAuth = !!verificationValue.query?.code_challenge;
522
+ const pkceUsedInToken = !!code_verifier;
523
+ if (pkceUsedInAuth || pkceUsedInToken) {
524
+ if (pkceUsedInAuth && !pkceUsedInToken) throw new APIError("UNAUTHORIZED", {
525
+ error_description: "code_verifier required because PKCE was used in authorization",
526
+ error: "invalid_request"
527
+ });
528
+ if (!pkceUsedInAuth && pkceUsedInToken) throw new APIError("UNAUTHORIZED", {
529
+ error_description: "code_verifier provided but PKCE was not used in authorization",
530
+ error: "invalid_request"
531
+ });
532
+ if ((verificationValue.query?.code_challenge_method === "S256" ? await generateCodeChallenge(code_verifier) : void 0) !== verificationValue.query?.code_challenge) throw new APIError("UNAUTHORIZED", {
533
+ error_description: "code verification failed",
534
+ error: "invalid_request"
535
+ });
536
+ }
822
537
  /** Get user */
823
538
  if (!verificationValue.userId) throw new APIError("BAD_REQUEST", {
824
539
  error_description: "missing user, user may have been deleted",
@@ -1115,8 +830,8 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
1115
830
  client_id: accessToken.clientId,
1116
831
  sub: user?.id,
1117
832
  sid: sessionId,
1118
- exp: Math.floor(accessToken.expiresAt.getTime() / 1e3),
1119
- iat: Math.floor(accessToken.createdAt.getTime() / 1e3),
833
+ exp: Math.floor(new Date(accessToken.expiresAt).getTime() / 1e3),
834
+ iat: Math.floor(new Date(accessToken.createdAt).getTime() / 1e3),
1120
835
  scope: accessToken.scopes?.join(" ")
1121
836
  };
1122
837
  }
@@ -1159,8 +874,8 @@ async function validateRefreshToken(ctx, opts, token, clientId) {
1159
874
  iss: ((opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options)?.jwt?.issuer ?? ctx.context.baseURL,
1160
875
  sub: user?.id,
1161
876
  sid: sessionId,
1162
- exp: Math.floor(refreshToken.expiresAt.getTime() / 1e3),
1163
- iat: Math.floor(refreshToken.createdAt.getTime() / 1e3),
877
+ exp: Math.floor(new Date(refreshToken.expiresAt).getTime() / 1e3),
878
+ iat: Math.floor(new Date(refreshToken.createdAt).getTime() / 1e3),
1164
879
  scope: refreshToken.scopes?.join(" ")
1165
880
  };
1166
881
  }
@@ -1407,6 +1122,10 @@ async function checkOAuthClient(client, opts, settings) {
1407
1122
  error_description: `cannot request scope ${requestedScope}`
1408
1123
  });
1409
1124
  }
1125
+ if (settings?.isRegister && client.require_pkce === false) throw new APIError("BAD_REQUEST", {
1126
+ error: "invalid_client_metadata",
1127
+ error_description: `pkce is required for registered clients.`
1128
+ });
1410
1129
  }
1411
1130
  async function createOAuthClientEndpoint(ctx, opts, settings) {
1412
1131
  const body = ctx.body;
@@ -1456,7 +1175,7 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
1456
1175
  * @returns
1457
1176
  */
1458
1177
  function oauthToSchema(input) {
1459
- const { client_id: clientId, client_secret: clientSecret, client_secret_expires_at: _expiresAt, scope: _scope, user_id: userId, client_id_issued_at: _createdAt, client_name: name, client_uri: uri, logo_uri: icon, contacts, tos_uri: tos, policy_uri: policy, jwks: _jwks, jwks_uri: _jwksUri, software_id: softwareId, software_version: softwareVersion, software_statement: softwareStatement, redirect_uris: redirectUris, post_logout_redirect_uris: postLogoutRedirectUris, token_endpoint_auth_method: tokenEndpointAuthMethod, grant_types: grantTypes, response_types: responseTypes, public: _public, type, disabled, skip_consent: skipConsent, enable_end_session: enableEndSession, reference_id: referenceId, metadata: inputMetadata, ...rest } = input;
1178
+ const { client_id: clientId, client_secret: clientSecret, client_secret_expires_at: _expiresAt, scope: _scope, user_id: userId, client_id_issued_at: _createdAt, client_name: name, client_uri: uri, logo_uri: icon, contacts, tos_uri: tos, policy_uri: policy, jwks: _jwks, jwks_uri: _jwksUri, software_id: softwareId, software_version: softwareVersion, software_statement: softwareStatement, redirect_uris: redirectUris, post_logout_redirect_uris: postLogoutRedirectUris, token_endpoint_auth_method: tokenEndpointAuthMethod, grant_types: grantTypes, response_types: responseTypes, public: _public, type, disabled, skip_consent: skipConsent, enable_end_session: enableEndSession, require_pkce: requirePKCE, reference_id: referenceId, metadata: inputMetadata, ...rest } = input;
1460
1179
  const expiresAt = _expiresAt ? /* @__PURE__ */ new Date(_expiresAt * 1e3) : void 0;
1461
1180
  const createdAt = _createdAt ? /* @__PURE__ */ new Date(_createdAt * 1e3) : void 0;
1462
1181
  const scopes = _scope?.split(" ");
@@ -1490,6 +1209,7 @@ function oauthToSchema(input) {
1490
1209
  type,
1491
1210
  skipConsent,
1492
1211
  enableEndSession,
1212
+ requirePKCE,
1493
1213
  referenceId,
1494
1214
  metadata: Object.keys(metadataObj).length ? JSON.stringify(metadataObj) : void 0
1495
1215
  };
@@ -1501,9 +1221,9 @@ function oauthToSchema(input) {
1501
1221
  * @returns
1502
1222
  */
1503
1223
  function schemaToOAuth(input) {
1504
- const { clientId, clientSecret, disabled, scopes, userId, createdAt, updatedAt: _updatedAt, expiresAt, name, uri, icon, contacts, tos, policy, softwareId, softwareVersion, softwareStatement, redirectUris, postLogoutRedirectUris, tokenEndpointAuthMethod, grantTypes, responseTypes, public: _public, type, skipConsent, enableEndSession, referenceId, metadata } = input;
1505
- const _expiresAt = expiresAt ? Math.round(expiresAt.getTime() / 1e3) : void 0;
1506
- const _createdAt = createdAt ? Math.round(createdAt.getTime() / 1e3) : void 0;
1224
+ const { clientId, clientSecret, disabled, scopes, userId, createdAt, updatedAt: _updatedAt, expiresAt, name, uri, icon, contacts, tos, policy, softwareId, softwareVersion, softwareStatement, redirectUris, postLogoutRedirectUris, tokenEndpointAuthMethod, grantTypes, responseTypes, public: _public, type, skipConsent, enableEndSession, requirePKCE, referenceId, metadata } = input;
1225
+ const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
1226
+ const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
1507
1227
  const _scopes = scopes?.join(" ");
1508
1228
  return {
1509
1229
  ...parseClientMetadata(metadata),
@@ -1532,6 +1252,7 @@ function schemaToOAuth(input) {
1532
1252
  disabled: disabled ?? void 0,
1533
1253
  skip_consent: skipConsent ?? void 0,
1534
1254
  enable_end_session: enableEndSession ?? void 0,
1255
+ require_pkce: requirePKCE ?? void 0,
1535
1256
  reference_id: referenceId ?? void 0
1536
1257
  };
1537
1258
  }
@@ -1840,6 +1561,7 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
1840
1561
  client_secret_expires_at: z.union([z.string(), z.number()]).optional().default(0),
1841
1562
  skip_consent: z.boolean().optional(),
1842
1563
  enable_end_session: z.boolean().optional(),
1564
+ require_pkce: z.boolean().optional(),
1843
1565
  metadata: z.record(z.string(), z.unknown()).optional()
1844
1566
  }),
1845
1567
  metadata: {
@@ -1966,6 +1688,11 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
1966
1688
  type: "boolean",
1967
1689
  description: "Whether the client is disabled"
1968
1690
  },
1691
+ require_pkce: {
1692
+ type: "boolean",
1693
+ description: "Whether the client requires PKCE",
1694
+ default: true
1695
+ },
1969
1696
  metadata: {
1970
1697
  type: "object",
1971
1698
  additionalProperties: true,
@@ -2713,6 +2440,10 @@ const schema = {
2713
2440
  type: "string",
2714
2441
  required: false
2715
2442
  },
2443
+ requirePKCE: {
2444
+ type: "boolean",
2445
+ required: false
2446
+ },
2716
2447
  referenceId: {
2717
2448
  type: "string",
2718
2449
  required: false
@@ -2856,6 +2587,7 @@ const schema = {
2856
2587
  //#endregion
2857
2588
  //#region src/oauth.ts
2858
2589
  const oAuthState = defineRequestState(() => null);
2590
+ const getOAuthProviderState = oAuthState.get;
2859
2591
  /**
2860
2592
  * oAuth 2.1 provider plugin for Better Auth.
2861
2593
  *
@@ -3811,5 +3543,314 @@ const oauthProvider = (options) => {
3811
3543
  };
3812
3544
 
3813
3545
  //#endregion
3814
- export { authServerMetadata, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
3546
+ //#region src/authorize.ts
3547
+ /**
3548
+ * Formats an error url
3549
+ */
3550
+ function formatErrorURL(url, error, description, state, iss) {
3551
+ const searchParams = new URLSearchParams({
3552
+ error,
3553
+ error_description: description
3554
+ });
3555
+ state && searchParams.append("state", state);
3556
+ iss && searchParams.append("iss", iss);
3557
+ return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
3558
+ }
3559
+ const handleRedirect = (ctx, uri) => {
3560
+ if (ctx.headers?.get("accept")?.includes("application/json")) return {
3561
+ redirect: true,
3562
+ url: uri.toString()
3563
+ };
3564
+ else throw ctx.redirect(uri);
3565
+ };
3566
+ /**
3567
+ * Validates that the issuer URL
3568
+ * - MUST use HTTPS scheme (HTTP allowed for localhost in dev)
3569
+ * - MUST NOT contain query components
3570
+ * - MUST NOT contain fragment components
3571
+ *
3572
+ * @returns The validated issuer URL, or a sanitized version if invalid
3573
+ */
3574
+ function validateIssuerUrl(issuer) {
3575
+ try {
3576
+ const url = new URL(issuer);
3577
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
3578
+ if (url.protocol !== "https:" && !isLocalhost) url.protocol = "https:";
3579
+ url.search = "";
3580
+ url.hash = "";
3581
+ return url.toString().replace(/\/$/, "");
3582
+ } catch {
3583
+ return issuer;
3584
+ }
3585
+ }
3586
+ /**
3587
+ * Gets the issuer identifier
3588
+ */
3589
+ function getIssuer(ctx, opts) {
3590
+ let issuer;
3591
+ if (opts.disableJwtPlugin) issuer = ctx.context.baseURL;
3592
+ else try {
3593
+ issuer = getJwtPlugin(ctx.context).options?.jwt?.issuer ?? ctx.context.baseURL;
3594
+ } catch {
3595
+ issuer = ctx.context.baseURL;
3596
+ }
3597
+ return validateIssuerUrl(issuer);
3598
+ }
3599
+ /**
3600
+ * Error page url if redirect_uri has not been verified yet
3601
+ * Generates Url for custom error page
3602
+ */
3603
+ function getErrorURL(ctx, error, description) {
3604
+ return formatErrorURL(ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`, error, description);
3605
+ }
3606
+ async function authorizeEndpoint(ctx, opts, settings) {
3607
+ if (opts.grantTypes && !opts.grantTypes.includes("authorization_code")) throw new APIError$1("NOT_FOUND");
3608
+ if (!ctx.request) throw new APIError$1("UNAUTHORIZED", {
3609
+ error_description: "request not found",
3610
+ error: "invalid_request"
3611
+ });
3612
+ const query = ctx.query;
3613
+ await oAuthState.set({ query: query.toString() });
3614
+ if (!query.client_id) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
3615
+ if (!query.response_type) throw ctx.redirect(getErrorURL(ctx, "invalid_request", "response_type is required"));
3616
+ const promptSet = ctx.query?.prompt ? parsePrompt(ctx.query?.prompt) : void 0;
3617
+ if (promptSet?.has("select_account") && !opts.selectAccount?.page) throw ctx.redirect(getErrorURL(ctx, `unsupported_prompt_select_account`, "unsupported prompt type"));
3618
+ if (!(query.response_type === "code")) throw ctx.redirect(getErrorURL(ctx, "unsupported_response_type", "unsupported response type"));
3619
+ const client = await getClient(ctx, opts, query.client_id);
3620
+ if (!client) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
3621
+ if (client.disabled) throw ctx.redirect(getErrorURL(ctx, "client_disabled", "client is disabled"));
3622
+ if (!client.redirectUris?.find((url) => url === query.redirect_uri) || !query.redirect_uri) throw ctx.redirect(getErrorURL(ctx, "invalid_redirect", "invalid redirect uri"));
3623
+ let requestedScopes = query.scope?.split(" ").filter((s) => s);
3624
+ if (requestedScopes) {
3625
+ const validScopes = new Set(client.scopes ?? opts.scopes);
3626
+ const invalidScopes = requestedScopes.filter((scope) => {
3627
+ return !validScopes?.has(scope);
3628
+ });
3629
+ if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state, getIssuer(ctx, opts)));
3630
+ }
3631
+ if (!requestedScopes) {
3632
+ requestedScopes = client.scopes ?? opts.scopes ?? [];
3633
+ query.scope = requestedScopes.join(" ");
3634
+ }
3635
+ const pkceRequired = isPKCERequired(client, requestedScopes);
3636
+ if (pkceRequired) {
3637
+ if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", pkceRequired.valueOf(), query.state, getIssuer(ctx, opts)));
3638
+ }
3639
+ if (query.code_challenge || query.code_challenge_method) {
3640
+ if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "code_challenge and code_challenge_method must both be provided", query.state, getIssuer(ctx, opts)));
3641
+ if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method, only S256 is supported", query.state, getIssuer(ctx, opts)));
3642
+ }
3643
+ const session = await getSessionFromCtx(ctx);
3644
+ if (!session || promptSet?.has("login") || promptSet?.has("create")) return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
3645
+ if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
3646
+ if (settings?.isAuthorize && opts.selectAccount) {
3647
+ if (await opts.selectAccount.shouldRedirect({
3648
+ headers: ctx.request.headers,
3649
+ user: session.user,
3650
+ session: session.session,
3651
+ scopes: requestedScopes
3652
+ })) return redirectWithPromptCode(ctx, opts, "select_account");
3653
+ }
3654
+ if (opts.signup?.shouldRedirect) {
3655
+ const signupRedirect = await opts.signup.shouldRedirect({
3656
+ headers: ctx.request.headers,
3657
+ user: session.user,
3658
+ session: session.session,
3659
+ scopes: requestedScopes
3660
+ });
3661
+ if (signupRedirect) return redirectWithPromptCode(ctx, opts, "create", typeof signupRedirect === "string" ? signupRedirect : void 0);
3662
+ }
3663
+ if (!settings?.postLogin && opts.postLogin) {
3664
+ if (await opts.postLogin.shouldRedirect({
3665
+ headers: ctx.request.headers,
3666
+ user: session.user,
3667
+ session: session.session,
3668
+ scopes: requestedScopes
3669
+ })) return redirectWithPromptCode(ctx, opts, "post_login");
3670
+ }
3671
+ if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent");
3672
+ const referenceId = await opts.postLogin?.consentReferenceId?.({
3673
+ user: session.user,
3674
+ session: session.session,
3675
+ scopes: requestedScopes
3676
+ });
3677
+ if (client.skipConsent) return redirectWithAuthorizationCode(ctx, opts, {
3678
+ query,
3679
+ clientId: client.clientId,
3680
+ userId: session.user.id,
3681
+ sessionId: session.session.id,
3682
+ referenceId
3683
+ });
3684
+ const consent = await ctx.context.adapter.findOne({
3685
+ model: "oauthConsent",
3686
+ where: [
3687
+ {
3688
+ field: "clientId",
3689
+ value: client.clientId
3690
+ },
3691
+ {
3692
+ field: "userId",
3693
+ value: session.user.id
3694
+ },
3695
+ ...referenceId ? [{
3696
+ field: "referenceId",
3697
+ value: referenceId
3698
+ }] : []
3699
+ ]
3700
+ });
3701
+ if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val))) return redirectWithPromptCode(ctx, opts, "consent");
3702
+ return redirectWithAuthorizationCode(ctx, opts, {
3703
+ query,
3704
+ clientId: client.clientId,
3705
+ userId: session.user.id,
3706
+ sessionId: session.session.id,
3707
+ referenceId
3708
+ });
3709
+ }
3710
+ async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
3711
+ const code = generateRandomString(32, "a-z", "A-Z", "0-9");
3712
+ const iat = Math.floor(Date.now() / 1e3);
3713
+ const exp = iat + (opts.codeExpiresIn ?? 600);
3714
+ const data = {
3715
+ identifier: await storeToken(opts.storeTokens, code, "authorization_code"),
3716
+ updatedAt: /* @__PURE__ */ new Date(iat * 1e3),
3717
+ expiresAt: /* @__PURE__ */ new Date(exp * 1e3),
3718
+ value: JSON.stringify({
3719
+ type: "authorization_code",
3720
+ query: ctx.query,
3721
+ userId: verificationValue.userId,
3722
+ sessionId: verificationValue?.sessionId,
3723
+ referenceId: verificationValue.referenceId
3724
+ })
3725
+ };
3726
+ ctx.context.verification_id ? await ctx.context.internalAdapter.updateVerificationValue(ctx.context.verification_id, data) : await ctx.context.internalAdapter.createVerificationValue({
3727
+ ...data,
3728
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3)
3729
+ });
3730
+ const redirectUriWithCode = new URL(verificationValue.query.redirect_uri);
3731
+ redirectUriWithCode.searchParams.set("code", code);
3732
+ if (verificationValue.query.state) redirectUriWithCode.searchParams.set("state", verificationValue.query.state);
3733
+ redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
3734
+ return handleRedirect(ctx, redirectUriWithCode.toString());
3735
+ }
3736
+ async function redirectWithPromptCode(ctx, opts, type, page) {
3737
+ const queryParams = await signParams(ctx, opts);
3738
+ let path = opts.loginPage;
3739
+ if (type === "select_account") path = opts.selectAccount?.page ?? opts.loginPage;
3740
+ else if (type === "post_login") {
3741
+ if (!opts.postLogin?.page) throw new APIError$1("INTERNAL_SERVER_ERROR", { error_description: "postLogin should have been defined" });
3742
+ path = opts.postLogin?.page;
3743
+ } else if (type === "consent") path = opts.consentPage;
3744
+ else if (type === "create") path = opts.signup?.page ?? opts.loginPage;
3745
+ return handleRedirect(ctx, `${page ?? path}?${queryParams}`);
3746
+ }
3747
+ async function signParams(ctx, opts) {
3748
+ const exp = Math.floor(Date.now() / 1e3) + (opts.codeExpiresIn ?? 600);
3749
+ const params = new URLSearchParams(ctx.query);
3750
+ params.set("exp", String(exp));
3751
+ const signature = await makeSignature(params.toString(), ctx.context.secret);
3752
+ params.append("sig", signature);
3753
+ return params.toString();
3754
+ }
3755
+
3756
+ //#endregion
3757
+ //#region src/metadata.ts
3758
+ function authServerMetadata(ctx, opts, overrides) {
3759
+ const baseURL = ctx.context.baseURL;
3760
+ return {
3761
+ scopes_supported: overrides?.scopes_supported,
3762
+ issuer: validateIssuerUrl(opts?.jwt?.issuer ?? baseURL),
3763
+ authorization_endpoint: `${baseURL}/oauth2/authorize`,
3764
+ token_endpoint: `${baseURL}/oauth2/token`,
3765
+ jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
3766
+ registration_endpoint: `${baseURL}/oauth2/register`,
3767
+ introspection_endpoint: `${baseURL}/oauth2/introspect`,
3768
+ revocation_endpoint: `${baseURL}/oauth2/revoke`,
3769
+ response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
3770
+ response_modes_supported: ["query"],
3771
+ grant_types_supported: overrides?.grant_types_supported ?? [
3772
+ "authorization_code",
3773
+ "client_credentials",
3774
+ "refresh_token"
3775
+ ],
3776
+ token_endpoint_auth_methods_supported: [
3777
+ ...overrides?.public_client_supported ? ["none"] : [],
3778
+ "client_secret_basic",
3779
+ "client_secret_post"
3780
+ ],
3781
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
3782
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
3783
+ code_challenge_methods_supported: ["S256"],
3784
+ authorization_response_iss_parameter_supported: true
3785
+ };
3786
+ }
3787
+ function oidcServerMetadata(ctx, opts) {
3788
+ const baseURL = ctx.context.baseURL;
3789
+ const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
3790
+ return {
3791
+ ...authServerMetadata(ctx, jwtPluginOptions, {
3792
+ scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
3793
+ public_client_supported: opts.allowUnauthenticatedClientRegistration,
3794
+ grant_types_supported: opts.grantTypes,
3795
+ jwt_disabled: opts.disableJwtPlugin
3796
+ }),
3797
+ claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
3798
+ userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
3799
+ subject_types_supported: ["public"],
3800
+ id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
3801
+ end_session_endpoint: `${baseURL}/oauth2/end-session`,
3802
+ acr_values_supported: ["urn:mace:incommon:iap:bronze"],
3803
+ prompt_values_supported: [
3804
+ "login",
3805
+ "consent",
3806
+ "create",
3807
+ "select_account"
3808
+ ]
3809
+ };
3810
+ }
3811
+ /**
3812
+ * Provides an exportable `/.well-known/oauth-authorization-server`.
3813
+ *
3814
+ * Useful when basePath prevents the endpoint from being located at the root
3815
+ * and must be provided manually.
3816
+ *
3817
+ * @external
3818
+ */
3819
+ const oauthProviderAuthServerMetadata = (auth, opts) => {
3820
+ return async (_request) => {
3821
+ const res = await auth.api.getOAuthServerConfig();
3822
+ return new Response(JSON.stringify(res), {
3823
+ status: 200,
3824
+ headers: {
3825
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
3826
+ ...opts?.headers,
3827
+ "Content-Type": "application/json"
3828
+ }
3829
+ });
3830
+ };
3831
+ };
3832
+ /**
3833
+ * Provides an exportable `/.well-known/openid-configuration`.
3834
+ *
3835
+ * Useful when basePath prevents the endpoint from being located at the root
3836
+ * and must be provided manually.
3837
+ *
3838
+ * @external
3839
+ */
3840
+ const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
3841
+ return async (_request) => {
3842
+ const res = await auth.api.getOpenIdConfig();
3843
+ return new Response(JSON.stringify(res), {
3844
+ status: 200,
3845
+ headers: {
3846
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
3847
+ ...opts?.headers,
3848
+ "Content-Type": "application/json"
3849
+ }
3850
+ });
3851
+ };
3852
+ };
3853
+
3854
+ //#endregion
3855
+ export { authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
3815
3856
  //# sourceMappingURL=index.mjs.map