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

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-DqGkkPq2.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
+ url: 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,267 +60,66 @@ 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
+ 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
- uri: url
122
+ url
424
123
  };
425
124
  }
426
125
  async function created(ctx, opts) {
@@ -433,7 +132,7 @@ async function created(ctx, opts) {
433
132
  const { url } = await authorizeEndpoint(ctx, opts);
434
133
  return {
435
134
  redirect: true,
436
- uri: url
135
+ url
437
136
  };
438
137
  }
439
138
  async function postLogin(ctx, opts) {
@@ -448,7 +147,7 @@ async function postLogin(ctx, opts) {
448
147
  const { url } = await authorizeEndpoint(ctx, opts, { postLogin: true });
449
148
  return {
450
149
  redirect: true,
451
- uri: url
150
+ url
452
151
  };
453
152
  }
454
153
 
@@ -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;
@@ -1436,7 +1155,11 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
1436
1155
  });
1437
1156
  const client = await ctx.context.adapter.create({
1438
1157
  model: "oauthClient",
1439
- data: schema
1158
+ data: {
1159
+ ...schema,
1160
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3),
1161
+ updatedAt: /* @__PURE__ */ new Date(iat * 1e3)
1162
+ }
1440
1163
  });
1441
1164
  return ctx.json(schemaToOAuth({
1442
1165
  ...client,
@@ -1456,7 +1179,7 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
1456
1179
  * @returns
1457
1180
  */
1458
1181
  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;
1182
+ 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
1183
  const expiresAt = _expiresAt ? /* @__PURE__ */ new Date(_expiresAt * 1e3) : void 0;
1461
1184
  const createdAt = _createdAt ? /* @__PURE__ */ new Date(_createdAt * 1e3) : void 0;
1462
1185
  const scopes = _scope?.split(" ");
@@ -1490,6 +1213,7 @@ function oauthToSchema(input) {
1490
1213
  type,
1491
1214
  skipConsent,
1492
1215
  enableEndSession,
1216
+ requirePKCE,
1493
1217
  referenceId,
1494
1218
  metadata: Object.keys(metadataObj).length ? JSON.stringify(metadataObj) : void 0
1495
1219
  };
@@ -1501,9 +1225,9 @@ function oauthToSchema(input) {
1501
1225
  * @returns
1502
1226
  */
1503
1227
  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;
1228
+ 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;
1229
+ const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
1230
+ const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
1507
1231
  const _scopes = scopes?.join(" ");
1508
1232
  return {
1509
1233
  ...parseClientMetadata(metadata),
@@ -1532,6 +1256,7 @@ function schemaToOAuth(input) {
1532
1256
  disabled: disabled ?? void 0,
1533
1257
  skip_consent: skipConsent ?? void 0,
1534
1258
  enable_end_session: enableEndSession ?? void 0,
1259
+ require_pkce: requirePKCE ?? void 0,
1535
1260
  reference_id: referenceId ?? void 0
1536
1261
  };
1537
1262
  }
@@ -1742,7 +1467,10 @@ async function updateClientEndpoint(ctx, opts) {
1742
1467
  field: "clientId",
1743
1468
  value: clientId
1744
1469
  }],
1745
- update: oauthToSchema(updates)
1470
+ update: {
1471
+ ...oauthToSchema(updates),
1472
+ updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
1473
+ }
1746
1474
  });
1747
1475
  if (!updatedClient) throw new APIError("INTERNAL_SERVER_ERROR", {
1748
1476
  error_description: "unable to update client",
@@ -1790,8 +1518,8 @@ async function rotateClientSecretEndpoint(ctx, opts) {
1790
1518
  value: clientId
1791
1519
  }],
1792
1520
  update: {
1793
- ...schemaToOAuth(client),
1794
- clientSecret: storedClientSecret
1521
+ clientSecret: storedClientSecret,
1522
+ updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
1795
1523
  }
1796
1524
  });
1797
1525
  if (!updatedClient) throw new APIError("INTERNAL_SERVER_ERROR", {
@@ -1840,6 +1568,7 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
1840
1568
  client_secret_expires_at: z.union([z.string(), z.number()]).optional().default(0),
1841
1569
  skip_consent: z.boolean().optional(),
1842
1570
  enable_end_session: z.boolean().optional(),
1571
+ require_pkce: z.boolean().optional(),
1843
1572
  metadata: z.record(z.string(), z.unknown()).optional()
1844
1573
  }),
1845
1574
  metadata: {
@@ -1966,6 +1695,11 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
1966
1695
  type: "boolean",
1967
1696
  description: "Whether the client is disabled"
1968
1697
  },
1698
+ require_pkce: {
1699
+ type: "boolean",
1700
+ description: "Whether the client requires PKCE",
1701
+ default: true
1702
+ },
1969
1703
  metadata: {
1970
1704
  type: "object",
1971
1705
  additionalProperties: true,
@@ -2713,6 +2447,10 @@ const schema = {
2713
2447
  type: "string",
2714
2448
  required: false
2715
2449
  },
2450
+ requirePKCE: {
2451
+ type: "boolean",
2452
+ required: false
2453
+ },
2716
2454
  referenceId: {
2717
2455
  type: "string",
2718
2456
  required: false
@@ -2856,6 +2594,7 @@ const schema = {
2856
2594
  //#endregion
2857
2595
  //#region src/oauth.ts
2858
2596
  const oAuthState = defineRequestState(() => null);
2597
+ const getOAuthProviderState = oAuthState.get;
2859
2598
  /**
2860
2599
  * oAuth 2.1 provider plugin for Better Auth.
2861
2600
  *
@@ -2978,7 +2717,12 @@ const oauthProvider = (options) => {
2978
2717
  metadata: { SERVER_ONLY: true }
2979
2718
  }, async (ctx) => {
2980
2719
  if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
2981
- else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context)?.options, { scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes });
2720
+ else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context)?.options, {
2721
+ scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
2722
+ public_client_supported: opts.allowUnauthenticatedClientRegistration,
2723
+ grant_types_supported: opts.grantTypes,
2724
+ jwt_disabled: opts.disableJwtPlugin
2725
+ });
2982
2726
  }),
2983
2727
  getOpenIdConfig: createAuthEndpoint("/.well-known/openid-configuration", {
2984
2728
  method: "GET",
@@ -3811,5 +3555,314 @@ const oauthProvider = (options) => {
3811
3555
  };
3812
3556
 
3813
3557
  //#endregion
3814
- export { authServerMetadata, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
3558
+ //#region src/authorize.ts
3559
+ /**
3560
+ * Formats an error url
3561
+ */
3562
+ function formatErrorURL(url, error, description, state, iss) {
3563
+ const searchParams = new URLSearchParams({
3564
+ error,
3565
+ error_description: description
3566
+ });
3567
+ state && searchParams.append("state", state);
3568
+ iss && searchParams.append("iss", iss);
3569
+ return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
3570
+ }
3571
+ const handleRedirect = (ctx, uri) => {
3572
+ if (ctx.headers?.get("accept")?.includes("application/json")) return {
3573
+ redirect: true,
3574
+ url: uri.toString()
3575
+ };
3576
+ else throw ctx.redirect(uri);
3577
+ };
3578
+ /**
3579
+ * Validates that the issuer URL
3580
+ * - MUST use HTTPS scheme (HTTP allowed for localhost in dev)
3581
+ * - MUST NOT contain query components
3582
+ * - MUST NOT contain fragment components
3583
+ *
3584
+ * @returns The validated issuer URL, or a sanitized version if invalid
3585
+ */
3586
+ function validateIssuerUrl(issuer) {
3587
+ try {
3588
+ const url = new URL(issuer);
3589
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
3590
+ if (url.protocol !== "https:" && !isLocalhost) url.protocol = "https:";
3591
+ url.search = "";
3592
+ url.hash = "";
3593
+ return url.toString().replace(/\/$/, "");
3594
+ } catch {
3595
+ return issuer;
3596
+ }
3597
+ }
3598
+ /**
3599
+ * Gets the issuer identifier
3600
+ */
3601
+ function getIssuer(ctx, opts) {
3602
+ let issuer;
3603
+ if (opts.disableJwtPlugin) issuer = ctx.context.baseURL;
3604
+ else try {
3605
+ issuer = getJwtPlugin(ctx.context).options?.jwt?.issuer ?? ctx.context.baseURL;
3606
+ } catch {
3607
+ issuer = ctx.context.baseURL;
3608
+ }
3609
+ return validateIssuerUrl(issuer);
3610
+ }
3611
+ /**
3612
+ * Error page url if redirect_uri has not been verified yet
3613
+ * Generates Url for custom error page
3614
+ */
3615
+ function getErrorURL(ctx, error, description) {
3616
+ return formatErrorURL(ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`, error, description);
3617
+ }
3618
+ async function authorizeEndpoint(ctx, opts, settings) {
3619
+ if (opts.grantTypes && !opts.grantTypes.includes("authorization_code")) throw new APIError$1("NOT_FOUND");
3620
+ if (!ctx.request) throw new APIError$1("UNAUTHORIZED", {
3621
+ error_description: "request not found",
3622
+ error: "invalid_request"
3623
+ });
3624
+ const query = ctx.query;
3625
+ await oAuthState.set({ query: query.toString() });
3626
+ if (!query.client_id) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
3627
+ if (!query.response_type) throw ctx.redirect(getErrorURL(ctx, "invalid_request", "response_type is required"));
3628
+ const promptSet = ctx.query?.prompt ? parsePrompt(ctx.query?.prompt) : void 0;
3629
+ if (promptSet?.has("select_account") && !opts.selectAccount?.page) throw ctx.redirect(getErrorURL(ctx, `unsupported_prompt_select_account`, "unsupported prompt type"));
3630
+ if (!(query.response_type === "code")) throw ctx.redirect(getErrorURL(ctx, "unsupported_response_type", "unsupported response type"));
3631
+ const client = await getClient(ctx, opts, query.client_id);
3632
+ if (!client) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
3633
+ if (client.disabled) throw ctx.redirect(getErrorURL(ctx, "client_disabled", "client is disabled"));
3634
+ if (!client.redirectUris?.find((url) => url === query.redirect_uri) || !query.redirect_uri) throw ctx.redirect(getErrorURL(ctx, "invalid_redirect", "invalid redirect uri"));
3635
+ let requestedScopes = query.scope?.split(" ").filter((s) => s);
3636
+ if (requestedScopes) {
3637
+ const validScopes = new Set(client.scopes ?? opts.scopes);
3638
+ const invalidScopes = requestedScopes.filter((scope) => {
3639
+ return !validScopes?.has(scope);
3640
+ });
3641
+ if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state, getIssuer(ctx, opts)));
3642
+ }
3643
+ if (!requestedScopes) {
3644
+ requestedScopes = client.scopes ?? opts.scopes ?? [];
3645
+ query.scope = requestedScopes.join(" ");
3646
+ }
3647
+ const pkceRequired = isPKCERequired(client, requestedScopes);
3648
+ if (pkceRequired) {
3649
+ if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", pkceRequired.valueOf(), query.state, getIssuer(ctx, opts)));
3650
+ }
3651
+ if (query.code_challenge || query.code_challenge_method) {
3652
+ 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)));
3653
+ 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)));
3654
+ }
3655
+ const session = await getSessionFromCtx(ctx);
3656
+ if (!session || promptSet?.has("login") || promptSet?.has("create")) return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
3657
+ if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
3658
+ if (settings?.isAuthorize && opts.selectAccount) {
3659
+ if (await opts.selectAccount.shouldRedirect({
3660
+ headers: ctx.request.headers,
3661
+ user: session.user,
3662
+ session: session.session,
3663
+ scopes: requestedScopes
3664
+ })) return redirectWithPromptCode(ctx, opts, "select_account");
3665
+ }
3666
+ if (opts.signup?.shouldRedirect) {
3667
+ const signupRedirect = await opts.signup.shouldRedirect({
3668
+ headers: ctx.request.headers,
3669
+ user: session.user,
3670
+ session: session.session,
3671
+ scopes: requestedScopes
3672
+ });
3673
+ if (signupRedirect) return redirectWithPromptCode(ctx, opts, "create", typeof signupRedirect === "string" ? signupRedirect : void 0);
3674
+ }
3675
+ if (!settings?.postLogin && opts.postLogin) {
3676
+ if (await opts.postLogin.shouldRedirect({
3677
+ headers: ctx.request.headers,
3678
+ user: session.user,
3679
+ session: session.session,
3680
+ scopes: requestedScopes
3681
+ })) return redirectWithPromptCode(ctx, opts, "post_login");
3682
+ }
3683
+ if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent");
3684
+ const referenceId = await opts.postLogin?.consentReferenceId?.({
3685
+ user: session.user,
3686
+ session: session.session,
3687
+ scopes: requestedScopes
3688
+ });
3689
+ if (client.skipConsent) return redirectWithAuthorizationCode(ctx, opts, {
3690
+ query,
3691
+ clientId: client.clientId,
3692
+ userId: session.user.id,
3693
+ sessionId: session.session.id,
3694
+ referenceId
3695
+ });
3696
+ const consent = await ctx.context.adapter.findOne({
3697
+ model: "oauthConsent",
3698
+ where: [
3699
+ {
3700
+ field: "clientId",
3701
+ value: client.clientId
3702
+ },
3703
+ {
3704
+ field: "userId",
3705
+ value: session.user.id
3706
+ },
3707
+ ...referenceId ? [{
3708
+ field: "referenceId",
3709
+ value: referenceId
3710
+ }] : []
3711
+ ]
3712
+ });
3713
+ if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val))) return redirectWithPromptCode(ctx, opts, "consent");
3714
+ return redirectWithAuthorizationCode(ctx, opts, {
3715
+ query,
3716
+ clientId: client.clientId,
3717
+ userId: session.user.id,
3718
+ sessionId: session.session.id,
3719
+ referenceId
3720
+ });
3721
+ }
3722
+ async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
3723
+ const code = generateRandomString(32, "a-z", "A-Z", "0-9");
3724
+ const iat = Math.floor(Date.now() / 1e3);
3725
+ const exp = iat + (opts.codeExpiresIn ?? 600);
3726
+ const data = {
3727
+ identifier: await storeToken(opts.storeTokens, code, "authorization_code"),
3728
+ updatedAt: /* @__PURE__ */ new Date(iat * 1e3),
3729
+ expiresAt: /* @__PURE__ */ new Date(exp * 1e3),
3730
+ value: JSON.stringify({
3731
+ type: "authorization_code",
3732
+ query: ctx.query,
3733
+ userId: verificationValue.userId,
3734
+ sessionId: verificationValue?.sessionId,
3735
+ referenceId: verificationValue.referenceId
3736
+ })
3737
+ };
3738
+ ctx.context.verification_id ? await ctx.context.internalAdapter.updateVerificationValue(ctx.context.verification_id, data) : await ctx.context.internalAdapter.createVerificationValue({
3739
+ ...data,
3740
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3)
3741
+ });
3742
+ const redirectUriWithCode = new URL(verificationValue.query.redirect_uri);
3743
+ redirectUriWithCode.searchParams.set("code", code);
3744
+ if (verificationValue.query.state) redirectUriWithCode.searchParams.set("state", verificationValue.query.state);
3745
+ redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
3746
+ return handleRedirect(ctx, redirectUriWithCode.toString());
3747
+ }
3748
+ async function redirectWithPromptCode(ctx, opts, type, page) {
3749
+ const queryParams = await signParams(ctx, opts);
3750
+ let path = opts.loginPage;
3751
+ if (type === "select_account") path = opts.selectAccount?.page ?? opts.loginPage;
3752
+ else if (type === "post_login") {
3753
+ if (!opts.postLogin?.page) throw new APIError$1("INTERNAL_SERVER_ERROR", { error_description: "postLogin should have been defined" });
3754
+ path = opts.postLogin?.page;
3755
+ } else if (type === "consent") path = opts.consentPage;
3756
+ else if (type === "create") path = opts.signup?.page ?? opts.loginPage;
3757
+ return handleRedirect(ctx, `${page ?? path}?${queryParams}`);
3758
+ }
3759
+ async function signParams(ctx, opts) {
3760
+ const exp = Math.floor(Date.now() / 1e3) + (opts.codeExpiresIn ?? 600);
3761
+ const params = new URLSearchParams(ctx.query);
3762
+ params.set("exp", String(exp));
3763
+ const signature = await makeSignature(params.toString(), ctx.context.secret);
3764
+ params.append("sig", signature);
3765
+ return params.toString();
3766
+ }
3767
+
3768
+ //#endregion
3769
+ //#region src/metadata.ts
3770
+ function authServerMetadata(ctx, opts, overrides) {
3771
+ const baseURL = ctx.context.baseURL;
3772
+ return {
3773
+ scopes_supported: overrides?.scopes_supported,
3774
+ issuer: validateIssuerUrl(opts?.jwt?.issuer ?? baseURL),
3775
+ authorization_endpoint: `${baseURL}/oauth2/authorize`,
3776
+ token_endpoint: `${baseURL}/oauth2/token`,
3777
+ jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
3778
+ registration_endpoint: `${baseURL}/oauth2/register`,
3779
+ introspection_endpoint: `${baseURL}/oauth2/introspect`,
3780
+ revocation_endpoint: `${baseURL}/oauth2/revoke`,
3781
+ response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
3782
+ response_modes_supported: ["query"],
3783
+ grant_types_supported: overrides?.grant_types_supported ?? [
3784
+ "authorization_code",
3785
+ "client_credentials",
3786
+ "refresh_token"
3787
+ ],
3788
+ token_endpoint_auth_methods_supported: [
3789
+ ...overrides?.public_client_supported ? ["none"] : [],
3790
+ "client_secret_basic",
3791
+ "client_secret_post"
3792
+ ],
3793
+ introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
3794
+ revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
3795
+ code_challenge_methods_supported: ["S256"],
3796
+ authorization_response_iss_parameter_supported: true
3797
+ };
3798
+ }
3799
+ function oidcServerMetadata(ctx, opts) {
3800
+ const baseURL = ctx.context.baseURL;
3801
+ const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
3802
+ return {
3803
+ ...authServerMetadata(ctx, jwtPluginOptions, {
3804
+ scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
3805
+ public_client_supported: opts.allowUnauthenticatedClientRegistration,
3806
+ grant_types_supported: opts.grantTypes,
3807
+ jwt_disabled: opts.disableJwtPlugin
3808
+ }),
3809
+ claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
3810
+ userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
3811
+ subject_types_supported: ["public"],
3812
+ id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
3813
+ end_session_endpoint: `${baseURL}/oauth2/end-session`,
3814
+ acr_values_supported: ["urn:mace:incommon:iap:bronze"],
3815
+ prompt_values_supported: [
3816
+ "login",
3817
+ "consent",
3818
+ "create",
3819
+ "select_account"
3820
+ ]
3821
+ };
3822
+ }
3823
+ /**
3824
+ * Provides an exportable `/.well-known/oauth-authorization-server`.
3825
+ *
3826
+ * Useful when basePath prevents the endpoint from being located at the root
3827
+ * and must be provided manually.
3828
+ *
3829
+ * @external
3830
+ */
3831
+ const oauthProviderAuthServerMetadata = (auth, opts) => {
3832
+ return async (_request) => {
3833
+ const res = await auth.api.getOAuthServerConfig();
3834
+ return new Response(JSON.stringify(res), {
3835
+ status: 200,
3836
+ headers: {
3837
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
3838
+ ...opts?.headers,
3839
+ "Content-Type": "application/json"
3840
+ }
3841
+ });
3842
+ };
3843
+ };
3844
+ /**
3845
+ * Provides an exportable `/.well-known/openid-configuration`.
3846
+ *
3847
+ * Useful when basePath prevents the endpoint from being located at the root
3848
+ * and must be provided manually.
3849
+ *
3850
+ * @external
3851
+ */
3852
+ const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
3853
+ return async (_request) => {
3854
+ const res = await auth.api.getOpenIdConfig();
3855
+ return new Response(JSON.stringify(res), {
3856
+ status: 200,
3857
+ headers: {
3858
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
3859
+ ...opts?.headers,
3860
+ "Content-Type": "application/json"
3861
+ }
3862
+ });
3863
+ };
3864
+ };
3865
+
3866
+ //#endregion
3867
+ export { authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
3815
3868
  //# sourceMappingURL=index.mjs.map