@better-auth/oauth-provider 1.4.18 → 1.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { a as ResourceServerMetadata } from "./oauth-BrFoF22H.mjs";
1
+ import { a as ResourceServerMetadata } from "./oauth-1jow-gRL.mjs";
2
2
  import { JWTPayload, JWTVerifyOptions } from "jose";
3
3
  import { Auth } from "better-auth/types";
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as getJwtPlugin, o as getOAuthProviderPlugin, p as handleMcpErrors } from "./utils-DnfreTWo.mjs";
1
+ import { a as getJwtPlugin, o as getOAuthProviderPlugin, p as handleMcpErrors } from "./utils-CThxvHKd.mjs";
2
2
  import { verifyAccessToken } from "better-auth/oauth2";
3
3
  import { APIError } from "better-call";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
package/dist/client.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import "./oauth-BrFoF22H.mjs";
2
- import { t as oauthProvider } from "./oauth-BxSSTB3p.mjs";
1
+ import "./oauth-1jow-gRL.mjs";
2
+ import { t as oauthProvider } from "./oauth-DC4oauK7.mjs";
3
3
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
4
4
 
5
5
  //#region src/client.d.ts
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-BrFoF22H.mjs";
2
- import { t as oauthProvider } from "./oauth-BxSSTB3p.mjs";
1
+ import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-1jow-gRL.mjs";
2
+ import { t as oauthProvider } from "./oauth-DC4oauK7.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { JWTPayload } from "jose";
package/dist/index.mjs CHANGED
@@ -1,124 +1,28 @@
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-DnfreTWo.mjs";
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-CThxvHKd.mjs";
2
2
  import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
3
3
  import { APIError } from "better-call";
4
- import { BetterAuthError } from "@better-auth/core/error";
4
+ import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
5
5
  import { constantTimeEqual, generateRandomString, makeSignature } from "better-auth/crypto";
6
+ import { BetterAuthError } from "@better-auth/core/error";
6
7
  import { defineRequestState } from "@better-auth/core/context";
7
8
  import { logger } from "@better-auth/core/env";
8
- import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
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/metadata.ts
16
- function authServerMetadata(ctx, opts, overrides) {
17
- const baseURL = ctx.context.baseURL;
18
- return {
19
- scopes_supported: overrides?.scopes_supported,
20
- issuer: opts?.jwt?.issuer ?? baseURL,
21
- authorization_endpoint: `${baseURL}/oauth2/authorize`,
22
- token_endpoint: `${baseURL}/oauth2/token`,
23
- jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
24
- registration_endpoint: `${baseURL}/oauth2/register`,
25
- introspection_endpoint: `${baseURL}/oauth2/introspect`,
26
- revocation_endpoint: `${baseURL}/oauth2/revoke`,
27
- response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
28
- response_modes_supported: ["query"],
29
- grant_types_supported: overrides?.grant_types_supported ?? [
30
- "authorization_code",
31
- "client_credentials",
32
- "refresh_token"
33
- ],
34
- token_endpoint_auth_methods_supported: [
35
- ...overrides?.public_client_supported ? ["none"] : [],
36
- "client_secret_basic",
37
- "client_secret_post"
38
- ],
39
- introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
40
- revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
41
- code_challenge_methods_supported: ["S256"]
42
- };
43
- }
44
- function oidcServerMetadata(ctx, opts) {
45
- const baseURL = ctx.context.baseURL;
46
- const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
47
- return {
48
- ...authServerMetadata(ctx, jwtPluginOptions, {
49
- scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
50
- public_client_supported: opts.allowUnauthenticatedClientRegistration,
51
- grant_types_supported: opts.grantTypes,
52
- jwt_disabled: opts.disableJwtPlugin
53
- }),
54
- claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
55
- userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
56
- subject_types_supported: ["public"],
57
- id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
58
- end_session_endpoint: `${baseURL}/oauth2/end-session`,
59
- acr_values_supported: ["urn:mace:incommon:iap:bronze"],
60
- prompt_values_supported: [
61
- "login",
62
- "consent",
63
- "create",
64
- "select_account"
65
- ]
66
- };
67
- }
68
- /**
69
- * Provides an exportable `/.well-known/oauth-authorization-server`.
70
- *
71
- * Useful when basePath prevents the endpoint from being located at the root
72
- * and must be provided manually.
73
- *
74
- * @external
75
- */
76
- const oauthProviderAuthServerMetadata = (auth, opts) => {
77
- return async (_request) => {
78
- const res = await auth.api.getOAuthServerConfig();
79
- return new Response(JSON.stringify(res), {
80
- status: 200,
81
- headers: {
82
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
83
- ...opts?.headers,
84
- "Content-Type": "application/json"
85
- }
86
- });
87
- };
88
- };
89
- /**
90
- * Provides an exportable `/.well-known/openid-configuration`.
91
- *
92
- * Useful when basePath prevents the endpoint from being located at the root
93
- * and must be provided manually.
94
- *
95
- * @external
96
- */
97
- const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
98
- return async (_request) => {
99
- const res = await auth.api.getOpenIdConfig();
100
- return new Response(JSON.stringify(res), {
101
- status: 200,
102
- headers: {
103
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
104
- ...opts?.headers,
105
- "Content-Type": "application/json"
106
- }
107
- });
108
- };
109
- };
110
-
111
- //#endregion
112
15
  //#region src/authorize.ts
113
16
  /**
114
17
  * Formats an error url
115
18
  */
116
- function formatErrorURL(url, error, description, state) {
19
+ function formatErrorURL(url, error, description, state, iss) {
117
20
  const searchParams = new URLSearchParams({
118
21
  error,
119
22
  error_description: description
120
23
  });
121
24
  state && searchParams.append("state", state);
25
+ iss && searchParams.append("iss", iss);
122
26
  return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
123
27
  }
124
28
  const handleRedirect = (ctx, uri) => {
@@ -129,6 +33,39 @@ const handleRedirect = (ctx, uri) => {
129
33
  else throw ctx.redirect(uri);
130
34
  };
131
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$1 = url.hostname === "localhost" || url.hostname === "127.0.0.1";
47
+ if (url.protocol !== "https:" && !isLocalhost$1) 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
+ /**
132
69
  * Error page url if redirect_uri has not been verified yet
133
70
  * Generates Url for custom error page
134
71
  */
@@ -157,14 +94,14 @@ async function authorizeEndpoint(ctx, opts, settings) {
157
94
  const invalidScopes = requestedScopes.filter((scope) => {
158
95
  return !validScopes?.has(scope) || scope === "offline_access" && (query.code_challenge_method !== "S256" || !query.code_challenge);
159
96
  });
160
- if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state));
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)));
161
98
  }
162
99
  if (!requestedScopes) {
163
100
  requestedScopes = client.scopes ?? opts.scopes ?? [];
164
101
  query.scope = requestedScopes.join(" ");
165
102
  }
166
- if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "pkce is required", query.state));
167
- if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method", query.state));
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)));
168
105
  const session = await getSessionFromCtx(ctx);
169
106
  if (!session || promptSet?.has("login") || promptSet?.has("create")) return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
170
107
  if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
@@ -255,6 +192,7 @@ async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
255
192
  const redirectUriWithCode = new URL(verificationValue.query.redirect_uri);
256
193
  redirectUriWithCode.searchParams.set("code", code);
257
194
  if (verificationValue.query.state) redirectUriWithCode.searchParams.set("state", verificationValue.query.state);
195
+ redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
258
196
  return handleRedirect(ctx, redirectUriWithCode.toString());
259
197
  }
260
198
  async function redirectWithPromptCode(ctx, opts, type, page) {
@@ -277,6 +215,104 @@ async function signParams(ctx, opts) {
277
215
  return params.toString();
278
216
  }
279
217
 
218
+ //#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
+
280
316
  //#endregion
281
317
  //#region src/consent.ts
282
318
  async function consentEndpoint(ctx, opts) {
@@ -301,7 +337,7 @@ async function consentEndpoint(ctx, opts) {
301
337
  }
302
338
  if (!(ctx.body.accept === true)) return {
303
339
  redirect: true,
304
- uri: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0)
340
+ url: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0, getIssuer(ctx, opts))
305
341
  };
306
342
  const session = await getSessionFromCtx(ctx);
307
343
  const referenceId = await opts.postLogin?.consentReferenceId?.({
@@ -358,7 +394,7 @@ async function consentEndpoint(ctx, opts) {
358
394
  const { url } = await authorizeEndpoint(ctx, opts);
359
395
  return {
360
396
  redirect: true,
361
- uri: url
397
+ url
362
398
  };
363
399
  }
364
400
 
@@ -384,7 +420,7 @@ async function selected(ctx, opts) {
384
420
  const { url } = await authorizeEndpoint(ctx, opts);
385
421
  return {
386
422
  redirect: true,
387
- uri: url
423
+ url
388
424
  };
389
425
  }
390
426
  async function created(ctx, opts) {
@@ -397,7 +433,7 @@ async function created(ctx, opts) {
397
433
  const { url } = await authorizeEndpoint(ctx, opts);
398
434
  return {
399
435
  redirect: true,
400
- uri: url
436
+ url
401
437
  };
402
438
  }
403
439
  async function postLogin(ctx, opts) {
@@ -412,7 +448,7 @@ async function postLogin(ctx, opts) {
412
448
  const { url } = await authorizeEndpoint(ctx, opts, { postLogin: true });
413
449
  return {
414
450
  redirect: true,
415
- uri: url
451
+ url
416
452
  };
417
453
  }
418
454
 
@@ -1079,8 +1115,8 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
1079
1115
  client_id: accessToken.clientId,
1080
1116
  sub: user?.id,
1081
1117
  sid: sessionId,
1082
- exp: Math.floor(accessToken.expiresAt.getTime() / 1e3),
1083
- iat: Math.floor(accessToken.createdAt.getTime() / 1e3),
1118
+ exp: Math.floor(new Date(accessToken.expiresAt).getTime() / 1e3),
1119
+ iat: Math.floor(new Date(accessToken.createdAt).getTime() / 1e3),
1084
1120
  scope: accessToken.scopes?.join(" ")
1085
1121
  };
1086
1122
  }
@@ -1123,8 +1159,8 @@ async function validateRefreshToken(ctx, opts, token, clientId) {
1123
1159
  iss: ((opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options)?.jwt?.issuer ?? ctx.context.baseURL,
1124
1160
  sub: user?.id,
1125
1161
  sid: sessionId,
1126
- exp: Math.floor(refreshToken.expiresAt.getTime() / 1e3),
1127
- iat: Math.floor(refreshToken.createdAt.getTime() / 1e3),
1162
+ exp: Math.floor(new Date(refreshToken.expiresAt).getTime() / 1e3),
1163
+ iat: Math.floor(new Date(refreshToken.createdAt).getTime() / 1e3),
1128
1164
  scope: refreshToken.scopes?.join(" ")
1129
1165
  };
1130
1166
  }
@@ -1400,7 +1436,11 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
1400
1436
  });
1401
1437
  const client = await ctx.context.adapter.create({
1402
1438
  model: "oauthClient",
1403
- data: schema$1
1439
+ data: {
1440
+ ...schema$1,
1441
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3),
1442
+ updatedAt: /* @__PURE__ */ new Date(iat * 1e3)
1443
+ }
1404
1444
  });
1405
1445
  return ctx.json(schemaToOAuth({
1406
1446
  ...client,
@@ -1466,8 +1506,8 @@ function oauthToSchema(input) {
1466
1506
  */
1467
1507
  function schemaToOAuth(input) {
1468
1508
  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;
1469
- const _expiresAt = expiresAt ? Math.round(expiresAt.getTime() / 1e3) : void 0;
1470
- const _createdAt = createdAt ? Math.round(createdAt.getTime() / 1e3) : void 0;
1509
+ const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
1510
+ const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
1471
1511
  const _scopes = scopes?.join(" ");
1472
1512
  return {
1473
1513
  ...parseClientMetadata(metadata),
@@ -1502,8 +1542,19 @@ function schemaToOAuth(input) {
1502
1542
 
1503
1543
  //#endregion
1504
1544
  //#region src/types/zod.ts
1545
+ const DANGEROUS_SCHEMES = [
1546
+ "javascript:",
1547
+ "data:",
1548
+ "vbscript:"
1549
+ ];
1550
+ function isLocalhost(hostname) {
1551
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
1552
+ }
1505
1553
  /**
1506
- * Reusable URL validation that disallows javascript: scheme
1554
+ * Reusable URL validation for OAuth redirect URIs.
1555
+ * - Blocks dangerous schemes (javascript:, data:, vbscript:)
1556
+ * - For http/https: requires HTTPS (HTTP allowed only for localhost)
1557
+ * - Allows custom schemes for mobile apps (e.g., myapp://callback)
1507
1558
  */
1508
1559
  const SafeUrlSchema = z.url().superRefine((val, ctx) => {
1509
1560
  if (!URL.canParse(val)) {
@@ -1514,10 +1565,21 @@ const SafeUrlSchema = z.url().superRefine((val, ctx) => {
1514
1565
  });
1515
1566
  return z.NEVER;
1516
1567
  }
1517
- }).refine((url) => {
1518
- const u = new URL(url);
1519
- return u.protocol !== "javascript:" && u.protocol !== "data:" && u.protocol !== "vbscript:";
1520
- }, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
1568
+ const u = new URL(val);
1569
+ if (DANGEROUS_SCHEMES.includes(u.protocol)) {
1570
+ ctx.addIssue({
1571
+ code: "custom",
1572
+ message: "URL cannot use javascript:, data:, or vbscript: scheme"
1573
+ });
1574
+ return;
1575
+ }
1576
+ if (u.protocol === "http:" || u.protocol === "https:") {
1577
+ if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
1578
+ code: "custom",
1579
+ message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
1580
+ });
1581
+ }
1582
+ });
1521
1583
 
1522
1584
  //#endregion
1523
1585
  //#region src/oauthClient/endpoints.ts
@@ -1684,7 +1746,10 @@ async function updateClientEndpoint(ctx, opts) {
1684
1746
  field: "clientId",
1685
1747
  value: clientId
1686
1748
  }],
1687
- update: oauthToSchema(updates)
1749
+ update: {
1750
+ ...oauthToSchema(updates),
1751
+ updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
1752
+ }
1688
1753
  });
1689
1754
  if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
1690
1755
  error_description: "unable to update client",
@@ -1732,8 +1797,8 @@ async function rotateClientSecretEndpoint(ctx, opts) {
1732
1797
  value: clientId
1733
1798
  }],
1734
1799
  update: {
1735
- ...schemaToOAuth(client),
1736
- clientSecret: storedClientSecret
1800
+ clientSecret: storedClientSecret,
1801
+ updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
1737
1802
  }
1738
1803
  });
1739
1804
  if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
@@ -2920,7 +2985,12 @@ const oauthProvider = (options) => {
2920
2985
  metadata: { SERVER_ONLY: true }
2921
2986
  }, async (ctx) => {
2922
2987
  if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
2923
- else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options, { scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes });
2988
+ else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options, {
2989
+ scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
2990
+ public_client_supported: opts.allowUnauthenticatedClientRegistration,
2991
+ grant_types_supported: opts.grantTypes,
2992
+ jwt_disabled: opts.disableJwtPlugin
2993
+ });
2924
2994
  }),
2925
2995
  getOpenIdConfig: createAuthEndpoint("/.well-known/openid-configuration", {
2926
2996
  method: "GET",
@@ -3715,7 +3785,39 @@ const oauthProvider = (options) => {
3715
3785
  updateOAuthConsent: updateOAuthConsent(opts),
3716
3786
  deleteOAuthConsent: deleteOAuthConsent(opts)
3717
3787
  },
3718
- schema: mergeSchema(schema, opts?.schema)
3788
+ schema: mergeSchema(schema, opts?.schema),
3789
+ rateLimit: [
3790
+ ...opts.rateLimit?.token !== false ? [{
3791
+ pathMatcher: (path) => path === "/oauth2/token",
3792
+ window: opts.rateLimit?.token?.window ?? 60,
3793
+ max: opts.rateLimit?.token?.max ?? 20
3794
+ }] : [],
3795
+ ...opts.rateLimit?.authorize !== false ? [{
3796
+ pathMatcher: (path) => path === "/oauth2/authorize",
3797
+ window: opts.rateLimit?.authorize?.window ?? 60,
3798
+ max: opts.rateLimit?.authorize?.max ?? 30
3799
+ }] : [],
3800
+ ...opts.rateLimit?.introspect !== false ? [{
3801
+ pathMatcher: (path) => path === "/oauth2/introspect",
3802
+ window: opts.rateLimit?.introspect?.window ?? 60,
3803
+ max: opts.rateLimit?.introspect?.max ?? 100
3804
+ }] : [],
3805
+ ...opts.rateLimit?.revoke !== false ? [{
3806
+ pathMatcher: (path) => path === "/oauth2/revoke",
3807
+ window: opts.rateLimit?.revoke?.window ?? 60,
3808
+ max: opts.rateLimit?.revoke?.max ?? 30
3809
+ }] : [],
3810
+ ...opts.rateLimit?.register !== false ? [{
3811
+ pathMatcher: (path) => path === "/oauth2/register",
3812
+ window: opts.rateLimit?.register?.window ?? 60,
3813
+ max: opts.rateLimit?.register?.max ?? 5
3814
+ }] : [],
3815
+ ...opts.rateLimit?.userinfo !== false ? [{
3816
+ pathMatcher: (path) => path === "/oauth2/userinfo",
3817
+ window: opts.rateLimit?.userinfo?.window ?? 60,
3818
+ max: opts.rateLimit?.userinfo?.max ?? 60
3819
+ }] : []
3820
+ ]
3719
3821
  };
3720
3822
  };
3721
3823