better-auth 1.6.2 → 1.6.4

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.
Files changed (46) hide show
  1. package/dist/api/index.d.mts +0 -2
  2. package/dist/api/routes/account.mjs +1 -1
  3. package/dist/api/routes/callback.mjs +1 -1
  4. package/dist/api/routes/email-verification.mjs +1 -1
  5. package/dist/api/routes/password.mjs +1 -1
  6. package/dist/api/routes/session.d.mts +0 -1
  7. package/dist/api/routes/session.mjs +3 -4
  8. package/dist/api/routes/sign-in.mjs +1 -1
  9. package/dist/api/to-auth-endpoints.mjs +27 -3
  10. package/dist/auth/base.mjs +5 -24
  11. package/dist/client/plugins/index.d.mts +2 -2
  12. package/dist/client/query.mjs +4 -4
  13. package/dist/context/create-context.mjs +2 -2
  14. package/dist/context/helpers.mjs +61 -3
  15. package/dist/cookies/index.mjs +3 -3
  16. package/dist/crypto/index.mjs +2 -2
  17. package/dist/db/index.mjs +1 -1
  18. package/dist/db/internal-adapter.mjs +1 -1
  19. package/dist/index.d.mts +2 -2
  20. package/dist/index.mjs +2 -2
  21. package/dist/package.mjs +1 -1
  22. package/dist/plugins/admin/admin.d.mts +1 -3
  23. package/dist/plugins/admin/routes.mjs +2 -8
  24. package/dist/plugins/device-authorization/routes.mjs +1 -1
  25. package/dist/plugins/email-otp/routes.mjs +1 -1
  26. package/dist/plugins/index.d.mts +2 -2
  27. package/dist/plugins/jwt/utils.mjs +1 -1
  28. package/dist/plugins/mcp/index.mjs +20 -8
  29. package/dist/plugins/oauth-proxy/index.mjs +1 -1
  30. package/dist/plugins/oidc-provider/index.mjs +2 -2
  31. package/dist/plugins/organization/organization.d.mts +1 -3
  32. package/dist/plugins/organization/organization.mjs +1 -7
  33. package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
  34. package/dist/plugins/organization/routes/crud-org.mjs +1 -1
  35. package/dist/plugins/organization/routes/crud-team.mjs +1 -1
  36. package/dist/plugins/phone-number/routes.mjs +1 -1
  37. package/dist/plugins/two-factor/backup-codes/index.d.mts +2 -1
  38. package/dist/plugins/two-factor/backup-codes/index.mjs +12 -17
  39. package/dist/plugins/two-factor/client.d.mts +1 -1
  40. package/dist/plugins/two-factor/index.d.mts +2 -2
  41. package/dist/test-utils/test-instance.d.mts +0 -6
  42. package/dist/test-utils/test-instance.mjs +7 -1
  43. package/dist/utils/index.d.mts +1 -1
  44. package/dist/utils/url.d.mts +22 -15
  45. package/dist/utils/url.mjs +54 -28
  46. package/package.json +11 -11
@@ -249,7 +249,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
249
249
  "application/json": {
250
250
  schema: {
251
251
  type: "object";
252
- nullable: boolean;
253
252
  properties: {
254
253
  session: {
255
254
  $ref: string;
@@ -2239,7 +2238,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2239
2238
  "application/json": {
2240
2239
  schema: {
2241
2240
  type: "object";
2242
- nullable: boolean;
2243
2241
  properties: {
2244
2242
  session: {
2245
2243
  $ref: string;
@@ -1,6 +1,6 @@
1
1
  import { parseAccountOutput } from "../../db/schema.mjs";
2
- import { getAwaitableValue } from "../../context/helpers.mjs";
3
2
  import { getAccountCookie, setAccountCookie } from "../../cookies/session-store.mjs";
3
+ import { getAwaitableValue } from "../../context/helpers.mjs";
4
4
  import { generateState } from "../../oauth2/state.mjs";
5
5
  import { decryptOAuthToken, setTokenUtil } from "../../oauth2/utils.mjs";
6
6
  import { freshSessionMiddleware, getSessionFromCtx, sessionMiddleware } from "./session.mjs";
@@ -1,5 +1,5 @@
1
- import { getAwaitableValue } from "../../context/helpers.mjs";
2
1
  import { setSessionCookie } from "../../cookies/index.mjs";
2
+ import { getAwaitableValue } from "../../context/helpers.mjs";
3
3
  import { parseState } from "../../oauth2/state.mjs";
4
4
  import { setTokenUtil } from "../../oauth2/utils.mjs";
5
5
  import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
@@ -1,6 +1,6 @@
1
1
  import { originCheck } from "../middlewares/origin-check.mjs";
2
- import { parseUserOutput } from "../../db/schema.mjs";
3
2
  import { signJWT } from "../../crypto/jwt.mjs";
3
+ import { parseUserOutput } from "../../db/schema.mjs";
4
4
  import { setSessionCookie } from "../../cookies/index.mjs";
5
5
  import { getSessionFromCtx } from "./session.mjs";
6
6
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
@@ -82,7 +82,7 @@ const requestPasswordReset = createAuthEndpoint("/request-password-reset", {
82
82
  });
83
83
  const requestPasswordResetCallback = createAuthEndpoint("/reset-password/:token", {
84
84
  method: "GET",
85
- operationId: "forgetPasswordCallback",
85
+ operationId: "resetPasswordCallback",
86
86
  query: z.object({ callbackURL: z.string().meta({ description: "The URL to redirect the user to reset their password" }) }),
87
87
  use: [originCheck((ctx) => ctx.query.callbackURL)],
88
88
  metadata: { openapi: {
@@ -25,7 +25,6 @@ declare const getSession: <Option extends BetterAuthOptions>() => better_call0.S
25
25
  "application/json": {
26
26
  schema: {
27
27
  type: "object";
28
- nullable: boolean;
29
28
  properties: {
30
29
  session: {
31
30
  $ref: string;
@@ -1,7 +1,7 @@
1
1
  import { isAPIError } from "../../utils/is-api-error.mjs";
2
- import { getDate } from "../../utils/date.mjs";
3
- import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
4
2
  import { symmetricDecodeJWT, verifyJWT } from "../../crypto/jwt.mjs";
3
+ import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
4
+ import { getDate } from "../../utils/date.mjs";
5
5
  import { getChunkedCookie, getSessionQuerySchema } from "../../cookies/session-store.mjs";
6
6
  import { deleteSessionCookie, expireCookie, setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
7
7
  import { getShouldSkipSessionRefresh } from "../state/should-session-refresh.mjs";
@@ -24,8 +24,7 @@ const getSession = () => createAuthEndpoint("/get-session", {
24
24
  responses: { "200": {
25
25
  description: "Success",
26
26
  content: { "application/json": { schema: {
27
- type: "object",
28
- nullable: true,
27
+ type: ["object", "null"],
29
28
  properties: {
30
29
  session: { $ref: "#/components/schemas/Session" },
31
30
  user: { $ref: "#/components/schemas/User" }
@@ -1,7 +1,7 @@
1
1
  import { formCsrfMiddleware } from "../middlewares/origin-check.mjs";
2
2
  import { parseUserOutput } from "../../db/schema.mjs";
3
- import { getAwaitableValue } from "../../context/helpers.mjs";
4
3
  import { setSessionCookie } from "../../cookies/index.mjs";
4
+ import { getAwaitableValue } from "../../context/helpers.mjs";
5
5
  import { generateState } from "../../oauth2/state.mjs";
6
6
  import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
7
7
  import { createEmailVerificationToken } from "./email-verification.mjs";
@@ -1,7 +1,9 @@
1
1
  import { isAPIError } from "../utils/is-api-error.mjs";
2
+ import { isDynamicBaseURLConfig, isRequestLike } from "../utils/url.mjs";
3
+ import { pickSource, resolveDynamicTrustedProxyHeaders, resolveRequestContext } from "../context/helpers.mjs";
2
4
  import { hasRequestState, runWithEndpointContext, runWithRequestState } from "@better-auth/core/context";
3
5
  import { shouldPublishLog } from "@better-auth/core/env";
4
- import { APIError } from "@better-auth/core/error";
6
+ import { APIError, BetterAuthError } from "@better-auth/core/error";
5
7
  import { createDefu } from "defu";
6
8
  import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan } from "@better-auth/core/instrumentation";
7
9
  import { kAPIErrorHeaderSymbol, toResponse } from "better-call";
@@ -18,6 +20,27 @@ function getOperationId(endpoint, key) {
18
20
  const opts = endpoint.options;
19
21
  return opts.operationId ?? opts.metadata?.openapi?.operationId ?? key;
20
22
  }
23
+ /**
24
+ * Resolves the per-call `AuthContext` for endpoints with a dynamic `baseURL`.
25
+ *
26
+ * - `rawCtx.baseURL` already set: HTTP handler rehydrated upstream; return as-is.
27
+ * - Direct `auth.api` call with a source or a configured `fallback`: resolve here.
28
+ * - Neither: throw `APIError` with a helpful message. Leaving `baseURL = ""`
29
+ * would let plugins build `new URL("")` and crash cryptically downstream.
30
+ */
31
+ async function resolveDynamicContext(rawCtx, input) {
32
+ if (rawCtx.baseURL) return rawCtx;
33
+ const source = pickSource(input);
34
+ const config = rawCtx.options.baseURL;
35
+ const hasFallback = isDynamicBaseURLConfig(config) && Boolean(config.fallback);
36
+ if (source === void 0 && !hasFallback) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Dynamic baseURL could not be resolved for this direct auth.api call. Pass `headers: request.headers` (or `request`) to the call, or add `fallback` to your baseURL config." });
37
+ try {
38
+ return await resolveRequestContext(rawCtx, source, resolveDynamicTrustedProxyHeaders(rawCtx.options));
39
+ } catch (err) {
40
+ if (err instanceof BetterAuthError) throw new APIError("INTERNAL_SERVER_ERROR", { message: err.message });
41
+ throw err;
42
+ }
43
+ }
21
44
  function toAuthEndpoints(endpoints, ctx) {
22
45
  const api = {};
23
46
  for (const [key, endpoint] of Object.entries(endpoints)) {
@@ -26,9 +49,10 @@ function toAuthEndpoints(endpoints, ctx) {
26
49
  const endpointMethod = endpoint?.options?.method;
27
50
  const defaultMethod = Array.isArray(endpointMethod) ? endpointMethod[0] : endpointMethod;
28
51
  const run = async () => {
29
- const authContext = await ctx;
52
+ const rawContext = await ctx;
30
53
  const methodName = context?.method ?? context?.request?.method ?? defaultMethod ?? "?";
31
54
  const route = endpoint.path ?? "/:virtual";
55
+ const authContext = isDynamicBaseURLConfig(rawContext.options.baseURL) ? await resolveDynamicContext(rawContext, context) : rawContext;
32
56
  let internalContext = {
33
57
  ...context,
34
58
  context: {
@@ -40,7 +64,7 @@ function toAuthEndpoints(endpoints, ctx) {
40
64
  path: endpoint.path,
41
65
  headers: context?.headers ? new Headers(context?.headers) : void 0
42
66
  };
43
- const hasRequest = context?.request instanceof Request;
67
+ const hasRequest = isRequestLike(context?.request);
44
68
  const shouldReturnResponse = context?.asResponse ?? hasRequest;
45
69
  return withSpan(`${methodName} ${route}`, {
46
70
  [ATTR_HTTP_ROUTE]: route,
@@ -1,6 +1,5 @@
1
- import { getBaseURL, getOrigin, isDynamicBaseURLConfig, resolveBaseURL } from "../utils/url.mjs";
2
- import { getTrustedOrigins, getTrustedProviders } from "../context/helpers.mjs";
3
- import { createCookieGetter, getCookies } from "../cookies/index.mjs";
1
+ import { getBaseURL, getOrigin, isDynamicBaseURLConfig } from "../utils/url.mjs";
2
+ import { getTrustedOrigins, getTrustedProviders, resolveDynamicTrustedProxyHeaders, resolveRequestContext } from "../context/helpers.mjs";
4
3
  import { getEndpoints, router } from "../api/index.mjs";
5
4
  import { runWithAdapter } from "@better-auth/core/context";
6
5
  import { BASE_ERROR_CODES, BetterAuthError } from "@better-auth/core/error";
@@ -13,26 +12,8 @@ const createBetterAuth = (options, initFn) => {
13
12
  const ctx = await authContext;
14
13
  const basePath = ctx.options.basePath || "/api/auth";
15
14
  let handlerCtx;
16
- if (isDynamicBaseURLConfig(options.baseURL)) {
17
- handlerCtx = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
18
- const baseURL = resolveBaseURL(options.baseURL, basePath, request);
19
- if (baseURL) {
20
- handlerCtx.baseURL = baseURL;
21
- handlerCtx.options = {
22
- ...ctx.options,
23
- baseURL: getOrigin(baseURL) || void 0
24
- };
25
- } else throw new BetterAuthError("Could not resolve base URL from request. Check your allowedHosts config.");
26
- const trustedOriginOptions = {
27
- ...handlerCtx.options,
28
- baseURL: options.baseURL
29
- };
30
- handlerCtx.trustedOrigins = await getTrustedOrigins(trustedOriginOptions, request);
31
- if (options.advanced?.crossSubDomainCookies?.enabled) {
32
- handlerCtx.authCookies = getCookies(handlerCtx.options);
33
- handlerCtx.createAuthCookie = createCookieGetter(handlerCtx.options);
34
- }
35
- } else {
15
+ if (isDynamicBaseURLConfig(options.baseURL)) handlerCtx = await resolveRequestContext(ctx, request, resolveDynamicTrustedProxyHeaders(ctx.options));
16
+ else {
36
17
  handlerCtx = ctx;
37
18
  if (!ctx.options.baseURL) {
38
19
  const baseURL = getBaseURL(void 0, basePath, request, void 0, ctx.options.advanced?.trustedProxyHeaders);
@@ -42,8 +23,8 @@ const createBetterAuth = (options, initFn) => {
42
23
  } else throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
43
24
  }
44
25
  handlerCtx.trustedOrigins = await getTrustedOrigins(ctx.options, request);
26
+ handlerCtx.trustedProviders = await getTrustedProviders(ctx.options, request);
45
27
  }
46
- handlerCtx.trustedProviders = await getTrustedProviders(handlerCtx.options, request);
47
28
  const { handler } = router(handlerCtx, options);
48
29
  return runWithAdapter(handlerCtx.adapter, () => handler(request));
49
30
  },
@@ -20,7 +20,7 @@ import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-cod
20
20
  import { MultiSessionConfig } from "../../plugins/multi-session/index.mjs";
21
21
  import { OneTimeTokenOptions } from "../../plugins/one-time-token/index.mjs";
22
22
  import { PhoneNumberOptions, UserWithPhoneNumber } from "../../plugins/phone-number/types.mjs";
23
- import { BackupCodeOptions, backupCode2fa, generateBackupCodes, getBackupCodes, verifyBackupCode } from "../../plugins/two-factor/backup-codes/index.mjs";
23
+ import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "../../plugins/two-factor/backup-codes/index.mjs";
24
24
  import { OTPOptions, otp2fa } from "../../plugins/two-factor/otp/index.mjs";
25
25
  import { TOTPOptions, totp2fa } from "../../plugins/two-factor/totp/index.mjs";
26
26
  import { TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor } from "../../plugins/two-factor/types.mjs";
@@ -52,4 +52,4 @@ import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
52
52
  import { siweClient } from "../../plugins/siwe/client.mjs";
53
53
  import { usernameClient } from "../../plugins/username/client.mjs";
54
54
  import { InferServerPlugin } from "./infer-plugin.mjs";
55
- export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
55
+ export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
@@ -72,15 +72,15 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
72
72
  });
73
73
  };
74
74
  initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
75
- let isMounted = false;
75
+ let isInitialized = false;
76
76
  for (const initAtom of initializedAtom) initAtom.subscribe(async () => {
77
77
  if (isServer()) return;
78
- if (isMounted) await fn();
78
+ if (isInitialized) await fn();
79
79
  else onMount(value, () => {
80
80
  const timeoutId = setTimeout(async () => {
81
- if (!isMounted) {
81
+ if (!isInitialized) {
82
+ isInitialized = true;
82
83
  await fn();
83
- isMounted = true;
84
84
  }
85
85
  }, 0);
86
86
  return () => {
@@ -1,10 +1,10 @@
1
1
  import { getBaseURL, isDynamicBaseURLConfig } from "../utils/url.mjs";
2
2
  import { matchesOriginPattern } from "../auth/trusted-origins.mjs";
3
- import { createInternalAdapter } from "../db/internal-adapter.mjs";
4
3
  import { isPromise } from "../utils/is-promise.mjs";
5
- import { getInternalPlugins, getTrustedOrigins, getTrustedProviders, runPluginInit } from "./helpers.mjs";
6
4
  import { hashPassword, verifyPassword } from "../crypto/password.mjs";
7
5
  import { createCookieGetter, getCookies } from "../cookies/index.mjs";
6
+ import { createInternalAdapter } from "../db/internal-adapter.mjs";
7
+ import { getInternalPlugins, getTrustedOrigins, getTrustedProviders, runPluginInit } from "./helpers.mjs";
8
8
  import { checkPassword } from "../utils/password.mjs";
9
9
  import { checkEndpointConflicts } from "../api/index.mjs";
10
10
  import { DEFAULT_SECRET } from "../utils/constants.mjs";
@@ -1,7 +1,9 @@
1
- import { getBaseURL, isDynamicBaseURLConfig } from "../utils/url.mjs";
2
- import { createInternalAdapter } from "../db/internal-adapter.mjs";
1
+ import { getBaseURL, getOrigin, isDynamicBaseURLConfig, isRequestLike, resolveBaseURL } from "../utils/url.mjs";
3
2
  import { isPromise } from "../utils/is-promise.mjs";
3
+ import { createCookieGetter, getCookies } from "../cookies/index.mjs";
4
+ import { createInternalAdapter } from "../db/internal-adapter.mjs";
4
5
  import { env } from "@better-auth/core/env";
6
+ import { BetterAuthError } from "@better-auth/core/error";
5
7
  import { defu } from "defu";
6
8
  //#region src/context/helpers.ts
7
9
  async function runPluginInit(context) {
@@ -80,6 +82,62 @@ async function getTrustedOrigins(options, request) {
80
82
  if (envTrustedOrigins) trustedOrigins.push(...envTrustedOrigins.split(","));
81
83
  return trustedOrigins.filter((v) => Boolean(v));
82
84
  }
85
+ /**
86
+ * Picks a `Request`-like or `Headers` value from a direct `auth.api` call.
87
+ * Headers are only accepted when they carry a host: without one, host
88
+ * resolution would fall back to `null` and the caller should use `fallback`
89
+ * or pass a `Request` instead.
90
+ */
91
+ function pickSource(input) {
92
+ if (isRequestLike(input?.request)) return input.request;
93
+ if (!input?.headers) return void 0;
94
+ const headers = input.headers instanceof Headers ? input.headers : new Headers(input.headers);
95
+ if (!headers.has("host") && !headers.has("x-forwarded-host")) return;
96
+ return headers;
97
+ }
98
+ /**
99
+ * Returns the effective `trustedProxyHeaders` value for dynamic `baseURL`
100
+ * resolution. When the user hasn't set `advanced.trustedProxyHeaders`,
101
+ * proxy headers (`x-forwarded-host` / `x-forwarded-proto`) are trusted by
102
+ * default so deployments behind a reverse proxy work without extra config.
103
+ */
104
+ function resolveDynamicTrustedProxyHeaders(options) {
105
+ return options.advanced?.trustedProxyHeaders ?? true;
106
+ }
107
+ /**
108
+ * Per-request clone with `baseURL`, `trustedOrigins`, `trustedProviders`
109
+ * and cookies rehydrated for the resolved host. Throws `BetterAuthError`
110
+ * when the URL cannot be resolved; callers on the direct-API path convert
111
+ * this to `APIError`.
112
+ */
113
+ async function resolveRequestContext(ctx, source, trustedProxyHeaders) {
114
+ const dynamicBaseURLConfig = ctx.options.baseURL;
115
+ const baseURL = resolveBaseURL(dynamicBaseURLConfig, ctx.options.basePath || "/api/auth", source, void 0, trustedProxyHeaders);
116
+ if (!baseURL) throw new BetterAuthError("Could not resolve base URL from request. Check your allowedHosts config.");
117
+ const resolved = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
118
+ resolved.baseURL = baseURL;
119
+ resolved.options = {
120
+ ...ctx.options,
121
+ baseURL: getOrigin(baseURL) || void 0
122
+ };
123
+ const trustedOriginOptions = {
124
+ ...resolved.options,
125
+ baseURL: dynamicBaseURLConfig
126
+ };
127
+ const needsRequest = typeof ctx.options.trustedOrigins === "function" || typeof ctx.options.account?.accountLinking?.trustedProviders === "function";
128
+ let callbackRequest;
129
+ if (needsRequest) if (isRequestLike(source)) callbackRequest = source;
130
+ else if (source) callbackRequest = new Request(baseURL, { headers: source });
131
+ else callbackRequest = void 0;
132
+ else callbackRequest = void 0;
133
+ resolved.trustedOrigins = await getTrustedOrigins(trustedOriginOptions, callbackRequest);
134
+ resolved.trustedProviders = await getTrustedProviders(resolved.options, callbackRequest);
135
+ if (ctx.options.advanced?.crossSubDomainCookies?.enabled) {
136
+ resolved.authCookies = getCookies(resolved.options);
137
+ resolved.createAuthCookie = createCookieGetter(resolved.options);
138
+ }
139
+ return resolved;
140
+ }
83
141
  async function getAwaitableValue(arr, item) {
84
142
  if (!arr) return void 0;
85
143
  for (const val of arr) {
@@ -94,4 +152,4 @@ async function getTrustedProviders(options, request) {
94
152
  return (await trustedProviders(request) ?? []).filter((v) => Boolean(v));
95
153
  }
96
154
  //#endregion
97
- export { getAwaitableValue, getInternalPlugins, getTrustedOrigins, getTrustedProviders, runPluginInit };
155
+ export { getAwaitableValue, getInternalPlugins, getTrustedOrigins, getTrustedProviders, pickSource, resolveDynamicTrustedProxyHeaders, resolveRequestContext, runPluginInit };
@@ -1,11 +1,11 @@
1
1
  import { isDynamicBaseURLConfig } from "../utils/url.mjs";
2
- import { getDate } from "../utils/date.mjs";
2
+ import { signJWT, symmetricDecodeJWT, symmetricEncodeJWT, verifyJWT } from "../crypto/jwt.mjs";
3
3
  import { parseUserOutput } from "../db/schema.mjs";
4
+ import { getDate } from "../utils/date.mjs";
4
5
  import { isPromise } from "../utils/is-promise.mjs";
5
- import { signJWT, symmetricDecodeJWT, symmetricEncodeJWT, verifyJWT } from "../crypto/jwt.mjs";
6
- import { createAccountStore, createSessionStore, getAccountCookie, getChunkedCookie, setAccountCookie } from "./session-store.mjs";
7
6
  import { sec } from "../utils/time.mjs";
8
7
  import { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, splitSetCookieHeader, stripSecureCookiePrefix } from "./cookie-utils.mjs";
8
+ import { createAccountStore, createSessionStore, getAccountCookie, getChunkedCookie, setAccountCookie } from "./session-store.mjs";
9
9
  import { env, isProduction } from "@better-auth/core/env";
10
10
  import { BetterAuthError } from "@better-auth/core/error";
11
11
  import { safeJSONParse } from "@better-auth/core/utils/json";
@@ -1,9 +1,9 @@
1
- import { constantTimeEqual } from "./buffer.mjs";
2
1
  import { signJWT, symmetricDecodeJWT, symmetricEncodeJWT, verifyJWT } from "./jwt.mjs";
2
+ import { constantTimeEqual } from "./buffer.mjs";
3
3
  import { hashPassword, verifyPassword } from "./password.mjs";
4
4
  import { generateRandomString } from "./random.mjs";
5
- import { createHash } from "@better-auth/utils/hash";
6
5
  import { getWebcryptoSubtle } from "@better-auth/utils";
6
+ import { createHash } from "@better-auth/utils/hash";
7
7
  import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
8
8
  import { bytesToHex, hexToBytes, managedNonce, utf8ToBytes } from "@noble/ciphers/utils.js";
9
9
  //#region src/crypto/index.ts
package/dist/db/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs";
2
2
  import { getSchema } from "./get-schema.mjs";
3
- import { convertFromDB, convertToDB } from "./field-converter.mjs";
4
3
  import { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
4
+ import { convertFromDB, convertToDB } from "./field-converter.mjs";
5
5
  import { getWithHooks } from "./with-hooks.mjs";
6
6
  import { createInternalAdapter } from "./internal-adapter.mjs";
7
7
  import { toZodSchema } from "./to-zod.mjs";
@@ -1,6 +1,6 @@
1
1
  import { getIp } from "../utils/get-request-ip.mjs";
2
- import { getDate } from "../utils/date.mjs";
3
2
  import { getSessionDefaultFields, parseSessionOutput, parseUserOutput } from "./schema.mjs";
3
+ import { getDate } from "../utils/date.mjs";
4
4
  import { getStorageOption, processIdentifier } from "./verification-token-storage.mjs";
5
5
  import { getWithHooks } from "./with-hooks.mjs";
6
6
  import { getCurrentAdapter, getCurrentAuthContext, runWithTransaction } from "@better-auth/core/context";
package/dist/index.d.mts CHANGED
@@ -10,7 +10,7 @@ import { betterAuth } from "./auth/full.mjs";
10
10
  import { generateState, parseState } from "./oauth2/state.mjs";
11
11
  import { StateData, generateGenericState, parseGenericState } from "./state.mjs";
12
12
  import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
13
- import { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
13
+ import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
14
14
  import { APIError } from "./api/index.mjs";
15
15
  import { StandardSchemaV1 } from "@better-auth/core";
16
16
  import { getCurrentAdapter } from "@better-auth/core/context";
@@ -27,4 +27,4 @@ export * from "@better-auth/core/utils/json";
27
27
  export * from "@better-auth/core/social-providers";
28
28
  export * from "better-call";
29
29
  export * from "zod";
30
- export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, getTelemetryAuthConfig, isDynamicBaseURLConfig, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
30
+ export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
1
+ import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
2
2
  import { generateGenericState, parseGenericState } from "./state.mjs";
3
3
  import { generateState, parseState } from "./oauth2/state.mjs";
4
4
  import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
@@ -14,4 +14,4 @@ export * from "@better-auth/core/oauth2";
14
14
  export * from "@better-auth/core/utils/error-codes";
15
15
  export * from "@better-auth/core/utils/id";
16
16
  export * from "@better-auth/core/utils/json";
17
- export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, getTelemetryAuthConfig, isDynamicBaseURLConfig, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
17
+ export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.6.2";
2
+ var version = "1.6.4";
3
3
  //#endregion
4
4
  export { version };
@@ -775,11 +775,9 @@ declare const admin: <O extends AdminOptions>(options?: O | undefined) => {
775
775
  body: zod.ZodIntersection<zod.ZodObject<{
776
776
  userId: zod.ZodOptional<zod.ZodCoercedString<unknown>>;
777
777
  role: zod.ZodOptional<zod.ZodString>;
778
- }, zod_v4_core0.$strip>, zod.ZodUnion<readonly [zod.ZodObject<{
778
+ }, zod_v4_core0.$strip>, zod.ZodXor<readonly [zod.ZodObject<{
779
779
  permission: zod.ZodRecord<zod.ZodString, zod.ZodArray<zod.ZodString>>;
780
- permissions: zod.ZodUndefined;
781
780
  }, zod_v4_core0.$strip>, zod.ZodObject<{
782
- permission: zod.ZodUndefined;
783
781
  permissions: zod.ZodRecord<zod.ZodString, zod.ZodArray<zod.ZodString>>;
784
782
  }, zod_v4_core0.$strip>]>>;
785
783
  metadata: {
@@ -1,5 +1,5 @@
1
- import { getDate } from "../../utils/date.mjs";
2
1
  import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
2
+ import { getDate } from "../../utils/date.mjs";
3
3
  import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs";
4
4
  import { getSessionFromCtx } from "../../api/routes/session.mjs";
5
5
  import { ADMIN_ERROR_CODES } from "./error-codes.mjs";
@@ -766,13 +766,7 @@ const setUserPassword = (opts) => createAuthEndpoint("/admin/set-user-password",
766
766
  const userHasPermissionBodySchema = z.object({
767
767
  userId: z.coerce.string().optional().meta({ description: `The user id. Eg: "user-id"` }),
768
768
  role: z.string().optional().meta({ description: `The role to check permission for. Eg: "admin"` })
769
- }).and(z.union([z.object({
770
- permission: z.record(z.string(), z.array(z.string())),
771
- permissions: z.undefined()
772
- }), z.object({
773
- permission: z.undefined(),
774
- permissions: z.record(z.string(), z.array(z.string()))
775
- })]));
769
+ }).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
776
770
  /**
777
771
  * ### Endpoint
778
772
  *
@@ -1,5 +1,5 @@
1
- import { generateRandomString } from "../../crypto/random.mjs";
2
1
  import { ms } from "../../utils/time.mjs";
2
+ import { generateRandomString } from "../../crypto/random.mjs";
3
3
  import { getSessionFromCtx } from "../../api/routes/session.mjs";
4
4
  import { DEVICE_AUTHORIZATION_ERROR_CODES } from "./error-codes.mjs";
5
5
  import { APIError } from "@better-auth/core/error";
@@ -1,5 +1,5 @@
1
- import { getDate } from "../../utils/date.mjs";
2
1
  import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
2
+ import { getDate } from "../../utils/date.mjs";
3
3
  import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { symmetricDecrypt } from "../../crypto/index.mjs";
5
5
  import { setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
@@ -51,7 +51,7 @@ import { phoneNumber } from "./phone-number/index.mjs";
51
51
  import { SIWEPluginOptions, siwe } from "./siwe/index.mjs";
52
52
  import { LoginResult, TestCookie, TestHelpers, TestUtilsOptions } from "./test-utils/types.mjs";
53
53
  import { testUtils } from "./test-utils/index.mjs";
54
- import { BackupCodeOptions, backupCode2fa, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./two-factor/backup-codes/index.mjs";
54
+ import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./two-factor/backup-codes/index.mjs";
55
55
  import { OTPOptions, otp2fa } from "./two-factor/otp/index.mjs";
56
56
  import { TOTPOptions, totp2fa } from "./two-factor/totp/index.mjs";
57
57
  import { TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor } from "./two-factor/types.mjs";
@@ -62,4 +62,4 @@ import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
62
62
  import { UsernameOptions, username } from "./username/index.mjs";
63
63
  import { hasPermission } from "./organization/has-permission.mjs";
64
64
  import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization, parseRoles } from "./organization/organization.mjs";
65
- export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, Role, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
65
+ export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, Role, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
@@ -1,5 +1,5 @@
1
- import { symmetricEncrypt } from "../../crypto/index.mjs";
2
1
  import { sec } from "../../utils/time.mjs";
2
+ import { symmetricEncrypt } from "../../crypto/index.mjs";
3
3
  import { getJwksAdapter } from "./adapter.mjs";
4
4
  import { exportJWK, generateKeyPair } from "jose";
5
5
  //#region src/plugins/jwt/utils.ts
@@ -1,7 +1,8 @@
1
1
  import { getBaseURL, isDynamicBaseURLConfig, resolveBaseURL } from "../../utils/url.mjs";
2
- import { generateRandomString } from "../../crypto/random.mjs";
3
2
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
3
+ import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { expireCookie } from "../../cookies/index.mjs";
5
+ import { resolveDynamicTrustedProxyHeaders } from "../../context/helpers.mjs";
5
6
  import { getSessionFromCtx } from "../../api/routes/session.mjs";
6
7
  import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
7
8
  import { APIError } from "../../api/index.mjs";
@@ -15,9 +16,9 @@ import { safeJSONParse } from "@better-auth/core/utils/json";
15
16
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
16
17
  import * as z from "zod";
17
18
  import { base64 } from "@better-auth/utils/base64";
18
- import { createHash } from "@better-auth/utils/hash";
19
- import { getWebcryptoSubtle } from "@better-auth/utils";
20
19
  import { SignJWT } from "jose";
20
+ import { getWebcryptoSubtle } from "@better-auth/utils";
21
+ import { createHash } from "@better-auth/utils/hash";
21
22
  //#region src/plugins/mcp/index.ts
22
23
  const getMCPProviderMetadata = (ctx, options) => {
23
24
  const issuer = typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : "";
@@ -662,10 +663,15 @@ const mcp = (options) => {
662
663
  const withMcpAuth = (auth, handler) => {
663
664
  return async (req) => {
664
665
  const basePath = auth.options.basePath || "/api/auth";
665
- const baseURL = isDynamicBaseURLConfig(auth.options.baseURL) ? resolveBaseURL(auth.options.baseURL, basePath, req) : getBaseURL(typeof auth.options.baseURL === "string" ? auth.options.baseURL : void 0, basePath);
666
+ const trustedProxyHeaders = resolveDynamicTrustedProxyHeaders(auth.options);
667
+ const baseURL = isDynamicBaseURLConfig(auth.options.baseURL) ? resolveBaseURL(auth.options.baseURL, basePath, req, void 0, trustedProxyHeaders) : getBaseURL(typeof auth.options.baseURL === "string" ? auth.options.baseURL : void 0, basePath);
666
668
  if (!baseURL && !isProduction) logger.warn("Unable to get the baseURL, please check your config!");
667
- const session = await auth.api.getMcpSession({ headers: req.headers });
668
- const wwwAuthenticateValue = `Bearer resource_metadata="${baseURL}/.well-known/oauth-protected-resource"`;
669
+ const session = await auth.api.getMcpSession({
670
+ request: req,
671
+ headers: req.headers,
672
+ asResponse: false
673
+ });
674
+ const wwwAuthenticateValue = baseURL ? `Bearer resource_metadata="${baseURL}/.well-known/oauth-protected-resource"` : "Bearer";
669
675
  if (!session) return Response.json({
670
676
  jsonrpc: "2.0",
671
677
  error: {
@@ -686,7 +692,10 @@ const withMcpAuth = (auth, handler) => {
686
692
  };
687
693
  const oAuthDiscoveryMetadata = (auth) => {
688
694
  return async (request) => {
689
- const res = await auth.api.getMcpOAuthConfig();
695
+ const res = await auth.api.getMcpOAuthConfig({
696
+ request,
697
+ asResponse: false
698
+ });
690
699
  return new Response(JSON.stringify(res), {
691
700
  status: 200,
692
701
  headers: {
@@ -701,7 +710,10 @@ const oAuthDiscoveryMetadata = (auth) => {
701
710
  };
702
711
  const oAuthProtectedResourceMetadata = (auth) => {
703
712
  return async (request) => {
704
- const res = await auth.api.getMCPProtectedResource();
713
+ const res = await auth.api.getMCPProtectedResource({
714
+ request,
715
+ asResponse: false
716
+ });
705
717
  return new Response(JSON.stringify(res), {
706
718
  status: 200,
707
719
  headers: {
@@ -1,7 +1,7 @@
1
1
  import { getOrigin } from "../../utils/url.mjs";
2
2
  import { originCheck } from "../../api/middlewares/origin-check.mjs";
3
- import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
4
3
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
4
+ import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
5
5
  import { setSessionCookie } from "../../cookies/index.mjs";
6
6
  import { parseGenericState } from "../../state.mjs";
7
7
  import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
@@ -1,7 +1,7 @@
1
1
  import { mergeSchema } from "../../db/schema.mjs";
2
+ import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
2
3
  import { generateRandomString } from "../../crypto/random.mjs";
3
4
  import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
4
- import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
5
5
  import { expireCookie } from "../../cookies/index.mjs";
6
6
  import { getSessionFromCtx, sessionMiddleware } from "../../api/routes/session.mjs";
7
7
  import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
@@ -18,8 +18,8 @@ import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"
18
18
  import { deprecate } from "@better-auth/core/utils/deprecate";
19
19
  import * as z from "zod";
20
20
  import { base64 } from "@better-auth/utils/base64";
21
- import { createHash } from "@better-auth/utils/hash";
22
21
  import { SignJWT, jwtVerify } from "jose";
22
+ import { createHash } from "@better-auth/utils/hash";
23
23
  //#region src/plugins/oidc-provider/index.ts
24
24
  /**
25
25
  * Get a client by ID, checking trusted clients first, then database
@@ -99,11 +99,9 @@ declare const createHasPermission: <O extends OrganizationOptions>(options: O) =
99
99
  requireHeaders: true;
100
100
  body: z.ZodIntersection<z.ZodObject<{
101
101
  organizationId: z.ZodOptional<z.ZodString>;
102
- }, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
102
+ }, z.core.$strip>, z.ZodXor<readonly [z.ZodObject<{
103
103
  permission: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
104
- permissions: z.ZodUndefined;
105
104
  }, z.core.$strip>, z.ZodObject<{
106
- permission: z.ZodUndefined;
107
105
  permissions: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
108
106
  }, z.core.$strip>]>>;
109
107
  use: ((inputContext: better_call0.MiddlewareInputContext<{
@@ -18,13 +18,7 @@ import * as z from "zod";
18
18
  function parseRoles(roles) {
19
19
  return Array.isArray(roles) ? roles.join(",") : roles;
20
20
  }
21
- const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.union([z.object({
22
- permission: z.record(z.string(), z.array(z.string())),
23
- permissions: z.undefined()
24
- }), z.object({
25
- permission: z.undefined(),
26
- permissions: z.record(z.string(), z.array(z.string()))
27
- })]));
21
+ const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
28
22
  const createHasPermission = (options) => {
29
23
  return createAuthEndpoint("/organization/has-permission", {
30
24
  method: "POST",
@@ -1,6 +1,6 @@
1
1
  import { getDate } from "../../../utils/date.mjs";
2
- import { toZodSchema } from "../../../db/to-zod.mjs";
3
2
  import { setSessionCookie } from "../../../cookies/index.mjs";
3
+ import { toZodSchema } from "../../../db/to-zod.mjs";
4
4
  import { getSessionFromCtx } from "../../../api/routes/session.mjs";
5
5
  import { defaultRoles } from "../access/statement.mjs";
6
6
  import { ORGANIZATION_ERROR_CODES } from "../error-codes.mjs";
@@ -1,5 +1,5 @@
1
- import { toZodSchema } from "../../../db/to-zod.mjs";
2
1
  import { setSessionCookie } from "../../../cookies/index.mjs";
2
+ import { toZodSchema } from "../../../db/to-zod.mjs";
3
3
  import { getSessionFromCtx, requestOnlySessionMiddleware } from "../../../api/routes/session.mjs";
4
4
  import { ORGANIZATION_ERROR_CODES } from "../error-codes.mjs";
5
5
  import { getOrgAdapter } from "../adapter.mjs";
@@ -1,5 +1,5 @@
1
- import { toZodSchema } from "../../../db/to-zod.mjs";
2
1
  import { setSessionCookie } from "../../../cookies/index.mjs";
2
+ import { toZodSchema } from "../../../db/to-zod.mjs";
3
3
  import { getSessionFromCtx } from "../../../api/routes/session.mjs";
4
4
  import { ORGANIZATION_ERROR_CODES } from "../error-codes.mjs";
5
5
  import { getOrgAdapter } from "../adapter.mjs";
@@ -1,5 +1,5 @@
1
- import { getDate } from "../../utils/date.mjs";
2
1
  import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
2
+ import { getDate } from "../../utils/date.mjs";
3
3
  import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { setSessionCookie } from "../../cookies/index.mjs";
5
5
  import { getSessionFromCtx } from "../../api/routes/session.mjs";
@@ -36,6 +36,7 @@ interface BackupCodeOptions {
36
36
  */
37
37
  allowPasswordless?: boolean | undefined;
38
38
  }
39
+ declare function encodeBackupCodes(codes: string[], secret: string | SecretConfig, options?: BackupCodeOptions | undefined): Promise<string>;
39
40
  declare function generateBackupCodes(secret: string | SecretConfig, options?: BackupCodeOptions | undefined): Promise<{
40
41
  backupCodes: string[];
41
42
  encryptedBackupCodes: string;
@@ -286,4 +287,4 @@ declare const backupCode2fa: (opts: BackupCodeOptions) => {
286
287
  };
287
288
  };
288
289
  //#endregion
289
- export { BackupCodeOptions, backupCode2fa, generateBackupCodes, getBackupCodes, verifyBackupCode };
290
+ export { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode };
@@ -14,22 +14,20 @@ import * as z from "zod";
14
14
  function generateBackupCodesFn(options) {
15
15
  return Array.from({ length: options?.amount ?? 10 }).fill(null).map(() => generateRandomString(options?.length ?? 10, "a-z", "0-9", "A-Z")).map((code) => `${code.slice(0, 5)}-${code.slice(5)}`);
16
16
  }
17
+ async function encodeBackupCodes(codes, secret, options) {
18
+ const json = JSON.stringify(codes);
19
+ if (options?.storeBackupCodes === "encrypted") return symmetricEncrypt({
20
+ data: json,
21
+ key: secret
22
+ });
23
+ if (typeof options?.storeBackupCodes === "object" && "encrypt" in options?.storeBackupCodes) return options.storeBackupCodes.encrypt(json);
24
+ return json;
25
+ }
17
26
  async function generateBackupCodes(secret, options) {
18
27
  const backupCodes = options?.customBackupCodesGenerate ? options.customBackupCodesGenerate() : generateBackupCodesFn(options);
19
- if (options?.storeBackupCodes === "encrypted") return {
20
- backupCodes,
21
- encryptedBackupCodes: await symmetricEncrypt({
22
- data: JSON.stringify(backupCodes),
23
- key: secret
24
- })
25
- };
26
- if (typeof options?.storeBackupCodes === "object" && "encrypt" in options?.storeBackupCodes) return {
27
- backupCodes,
28
- encryptedBackupCodes: await options?.storeBackupCodes.encrypt(JSON.stringify(backupCodes))
29
- };
30
28
  return {
31
29
  backupCodes,
32
- encryptedBackupCodes: JSON.stringify(backupCodes)
30
+ encryptedBackupCodes: await encodeBackupCodes(backupCodes, secret, options)
33
31
  };
34
32
  }
35
33
  async function verifyBackupCode(data, key, options) {
@@ -177,11 +175,8 @@ const backupCode2fa = (opts) => {
177
175
  backupCodes: twoFactor.backupCodes,
178
176
  code: ctx.body.code
179
177
  }, ctx.context.secretConfig, opts);
180
- if (!validate.status) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE);
181
- const updatedBackupCodes = await symmetricEncrypt({
182
- key: ctx.context.secretConfig,
183
- data: JSON.stringify(validate.updated)
184
- });
178
+ if (!validate.status || !validate.updated) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE);
179
+ const updatedBackupCodes = await encodeBackupCodes(validate.updated, ctx.context.secretConfig, opts);
185
180
  if (!await ctx.context.adapter.update({
186
181
  model: twoFactorTable,
187
182
  update: { backupCodes: updatedBackupCodes },
@@ -1,4 +1,4 @@
1
- import { BackupCodeOptions, backupCode2fa, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./backup-codes/index.mjs";
1
+ import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./backup-codes/index.mjs";
2
2
  import { OTPOptions, otp2fa } from "./otp/index.mjs";
3
3
  import { TOTPOptions, totp2fa } from "./totp/index.mjs";
4
4
  import { TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor } from "./types.mjs";
@@ -1,4 +1,4 @@
1
- import { BackupCodeOptions, backupCode2fa, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./backup-codes/index.mjs";
1
+ import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "./backup-codes/index.mjs";
2
2
  import { OTPOptions, otp2fa } from "./otp/index.mjs";
3
3
  import { TOTPOptions, totp2fa } from "./totp/index.mjs";
4
4
  import { TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor } from "./types.mjs";
@@ -683,4 +683,4 @@ declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
683
683
  };
684
684
  };
685
685
  //#endregion
686
- export { BackupCodeOptions, OTPOptions, TOTPOptions, TWO_FACTOR_ERROR_CODES, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor, backupCode2fa, generateBackupCodes, getBackupCodes, otp2fa, totp2fa, twoFactor, twoFactorClient, verifyBackupCode };
686
+ export { BackupCodeOptions, OTPOptions, TOTPOptions, TWO_FACTOR_ERROR_CODES, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, UserWithTwoFactor, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, otp2fa, totp2fa, twoFactor, twoFactorClient, verifyBackupCode };
@@ -261,7 +261,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
261
261
  "application/json": {
262
262
  schema: {
263
263
  type: "object";
264
- nullable: boolean;
265
264
  properties: {
266
265
  session: {
267
266
  $ref: string;
@@ -2265,7 +2264,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
2265
2264
  "application/json": {
2266
2265
  schema: {
2267
2266
  type: "object";
2268
- nullable: boolean;
2269
2267
  properties: {
2270
2268
  session: {
2271
2269
  $ref: string;
@@ -4272,7 +4270,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
4272
4270
  "application/json": {
4273
4271
  schema: {
4274
4272
  type: "object";
4275
- nullable: boolean;
4276
4273
  properties: {
4277
4274
  session: {
4278
4275
  $ref: string;
@@ -6276,7 +6273,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
6276
6273
  "application/json": {
6277
6274
  schema: {
6278
6275
  type: "object";
6279
- nullable: boolean;
6280
6276
  properties: {
6281
6277
  session: {
6282
6278
  $ref: string;
@@ -8354,7 +8350,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
8354
8350
  "application/json": {
8355
8351
  schema: {
8356
8352
  type: "object";
8357
- nullable: boolean;
8358
8353
  properties: {
8359
8354
  session: {
8360
8355
  $ref: string;
@@ -10358,7 +10353,6 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
10358
10353
  "application/json": {
10359
10354
  schema: {
10360
10355
  type: "object";
10361
- nullable: boolean;
10362
10356
  properties: {
10363
10357
  session: {
10364
10358
  $ref: string;
@@ -80,7 +80,13 @@ async function getTestInstance(options, config) {
80
80
  };
81
81
  async function createTestUser() {
82
82
  if (config?.disableTestUser) return;
83
- await auth.api.signUpEmail({ body: testUser });
83
+ const dynamicBaseURL = isDynamicBaseURLConfig(auth.options.baseURL) ? auth.options.baseURL : void 0;
84
+ const host = (dynamicBaseURL?.allowedHosts.find((h) => !h.includes("*") && !h.includes("?")) ?? dynamicBaseURL?.allowedHosts[0])?.replace(/^https?:\/\//, "").split(/[/#]/)[0]?.replace(/\*/g, "test").replace(/\?/g, "x");
85
+ const headers = host ? new Headers({ host }) : void 0;
86
+ await auth.api.signUpEmail({
87
+ body: testUser,
88
+ headers
89
+ });
84
90
  }
85
91
  if (testWith !== "mongodb") {
86
92
  const { runMigrations } = await getMigrations({
@@ -1,4 +1,4 @@
1
1
  import { generateState, parseState } from "../oauth2/state.mjs";
2
2
  import { StateData, generateGenericState, parseGenericState } from "../state.mjs";
3
3
  import { HIDE_METADATA } from "./hide-metadata.mjs";
4
- import { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./url.mjs";
4
+ import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./url.mjs";
@@ -10,22 +10,29 @@ declare function getHost(url: string): string | null;
10
10
  */
11
11
  declare function isDynamicBaseURLConfig(config: BaseURLConfig | undefined): config is DynamicBaseURLConfig;
12
12
  /**
13
- * Extracts the host from the request headers.
14
- * Tries x-forwarded-host first (for proxy setups), then falls back to host header.
13
+ * Check if a value is a `Request`
14
+ * - `instanceof`: works for native Request instances
15
+ * - `toString`: handles where instanceof check fails but the object is still a
16
+ * valid Request (e.g. cross-realm, polyfills). Paired with a shape check so
17
+ * an object that only spoofs `Symbol.toStringTag` without the real shape is
18
+ * rejected before downstream code tries to read `.headers` / `.url`.
15
19
  *
16
- * @param request The incoming request
17
- * @returns The host string or null if not found
20
+ * @param value The value to check
21
+ * @returns `true` if the value is a Request instance
18
22
  */
19
- declare function getHostFromRequest(request: Request): string | null;
23
+ declare function isRequestLike(value: unknown): value is Request;
20
24
  /**
21
- * Extracts the protocol from the request headers.
22
- * Tries x-forwarded-proto first (for proxy setups), then infers from request URL.
23
- *
24
- * @param request The incoming request
25
- * @param configProtocol Protocol override from config
26
- * @returns The protocol ("http" or "https")
25
+ * Extracts the host from a `Request` or `Headers`.
26
+ * Honors `x-forwarded-host` only when `trustedProxyHeaders` is enabled,
27
+ * then falls back to the `host` header and finally the request URL.
28
+ */
29
+ declare function getHostFromSource(source: Request | Headers, trustedProxyHeaders?: boolean): string | null;
30
+ /**
31
+ * Extracts the protocol from a `Request` or `Headers`.
32
+ * Honors `x-forwarded-proto` only when `trustedProxyHeaders` is enabled,
33
+ * then falls back to the request URL, then to "https".
27
34
  */
28
- declare function getProtocolFromRequest(request: Request, configProtocol?: "http" | "https" | "auto" | undefined): "http" | "https";
35
+ declare function getProtocolFromSource(source: Request | Headers, configProtocol?: "http" | "https" | "auto" | undefined, trustedProxyHeaders?: boolean): "http" | "https";
29
36
  /**
30
37
  * Matches a hostname against a host pattern.
31
38
  * Supports wildcard patterns like `*.vercel.app` or `preview-*.myapp.com`.
@@ -53,7 +60,7 @@ declare const matchesHostPattern: (host: string, pattern: string) => boolean;
53
60
  * @returns The resolved base URL with path
54
61
  * @throws BetterAuthError if host is not in allowedHosts and no fallback is set
55
62
  */
56
- declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, request: Request, basePath: string): string;
63
+ declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, source: Request | Headers, basePath: string, trustedProxyHeaders?: boolean): string;
57
64
  /**
58
65
  * Resolves the base URL from any config type (static string or dynamic object).
59
66
  * This is the main entry point for base URL resolution.
@@ -65,6 +72,6 @@ declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, request: Re
65
72
  * @param trustedProxyHeaders Whether to trust proxy headers (for legacy behavior)
66
73
  * @returns The resolved base URL with path
67
74
  */
68
- declare function resolveBaseURL(config: BaseURLConfig | undefined, basePath: string, request?: Request, loadEnv?: boolean, trustedProxyHeaders?: boolean): string | undefined;
75
+ declare function resolveBaseURL(config: BaseURLConfig | undefined, basePath: string, source?: Request | Headers, loadEnv?: boolean, trustedProxyHeaders?: boolean): string | undefined;
69
76
  //#endregion
70
- export { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
77
+ export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
@@ -93,41 +93,66 @@ function isDynamicBaseURLConfig(config) {
93
93
  return typeof config === "object" && config !== null && "allowedHosts" in config && Array.isArray(config.allowedHosts);
94
94
  }
95
95
  /**
96
- * Extracts the host from the request headers.
97
- * Tries x-forwarded-host first (for proxy setups), then falls back to host header.
96
+ * Check if a value is a `Request`
97
+ * - `instanceof`: works for native Request instances
98
+ * - `toString`: handles where instanceof check fails but the object is still a
99
+ * valid Request (e.g. cross-realm, polyfills). Paired with a shape check so
100
+ * an object that only spoofs `Symbol.toStringTag` without the real shape is
101
+ * rejected before downstream code tries to read `.headers` / `.url`.
98
102
  *
99
- * @param request The incoming request
100
- * @returns The host string or null if not found
103
+ * @param value The value to check
104
+ * @returns `true` if the value is a Request instance
105
+ */
106
+ function isRequestLike(value) {
107
+ if (value instanceof Request) return true;
108
+ if (typeof value !== "object" || value === null || Object.prototype.toString.call(value) !== "[object Request]") return false;
109
+ const v = value;
110
+ return typeof v.url === "string" && typeof v.headers === "object" && v.headers !== null && typeof v.headers.get === "function";
111
+ }
112
+ /**
113
+ * Extracts the host from a `Request` or `Headers`.
114
+ * Honors `x-forwarded-host` only when `trustedProxyHeaders` is enabled,
115
+ * then falls back to the `host` header and finally the request URL.
101
116
  */
102
- function getHostFromRequest(request) {
103
- const forwardedHost = request.headers.get("x-forwarded-host");
104
- if (forwardedHost && validateProxyHeader(forwardedHost, "host")) return forwardedHost;
105
- const host = request.headers.get("host");
117
+ function getHostFromSource(source, trustedProxyHeaders) {
118
+ const headers = isRequestLike(source) ? source.headers : source;
119
+ if (trustedProxyHeaders) {
120
+ const forwardedHost = headers.get("x-forwarded-host");
121
+ if (forwardedHost && validateProxyHeader(forwardedHost, "host")) return forwardedHost;
122
+ }
123
+ const host = headers.get("host");
106
124
  if (host && validateProxyHeader(host, "host")) return host;
107
- try {
108
- return new URL(request.url).host;
125
+ if (isRequestLike(source)) try {
126
+ return new URL(source.url).host;
109
127
  } catch {
110
128
  return null;
111
129
  }
130
+ return null;
112
131
  }
113
132
  /**
114
- * Extracts the protocol from the request headers.
115
- * Tries x-forwarded-proto first (for proxy setups), then infers from request URL.
116
- *
117
- * @param request The incoming request
118
- * @param configProtocol Protocol override from config
119
- * @returns The protocol ("http" or "https")
133
+ * Extracts the protocol from a `Request` or `Headers`.
134
+ * Honors `x-forwarded-proto` only when `trustedProxyHeaders` is enabled,
135
+ * then falls back to the request URL, then to "https".
120
136
  */
121
- function getProtocolFromRequest(request, configProtocol) {
137
+ function getProtocolFromSource(source, configProtocol, trustedProxyHeaders) {
122
138
  if (configProtocol === "http" || configProtocol === "https") return configProtocol;
123
- const forwardedProto = request.headers.get("x-forwarded-proto");
124
- if (forwardedProto && validateProxyHeader(forwardedProto, "proto")) return forwardedProto;
125
- try {
126
- const url = new URL(request.url);
139
+ const headers = isRequestLike(source) ? source.headers : source;
140
+ if (trustedProxyHeaders) {
141
+ const forwardedProto = headers.get("x-forwarded-proto");
142
+ if (forwardedProto && validateProxyHeader(forwardedProto, "proto")) return forwardedProto;
143
+ }
144
+ if (isRequestLike(source)) try {
145
+ const url = new URL(source.url);
127
146
  if (url.protocol === "http:" || url.protocol === "https:") return url.protocol.slice(0, -1);
128
147
  } catch {}
148
+ const host = getHostFromSource(source, trustedProxyHeaders);
149
+ if (host && isLoopbackHost(host)) return "http";
129
150
  return "https";
130
151
  }
152
+ function isLoopbackHost(host) {
153
+ const h = host.toLowerCase();
154
+ return h === "localhost" || h.startsWith("localhost:") || h === "127.0.0.1" || h.startsWith("127.0.0.1:") || h === "[::1]" || h.startsWith("[::1]:") || h === "0.0.0.0" || h.startsWith("0.0.0.0:");
155
+ }
131
156
  /**
132
157
  * Matches a hostname against a host pattern.
133
158
  * Supports wildcard patterns like `*.vercel.app` or `preview-*.myapp.com`.
@@ -161,13 +186,13 @@ const matchesHostPattern = (host, pattern) => {
161
186
  * @returns The resolved base URL with path
162
187
  * @throws BetterAuthError if host is not in allowedHosts and no fallback is set
163
188
  */
164
- function resolveDynamicBaseURL(config, request, basePath) {
165
- const host = getHostFromRequest(request);
189
+ function resolveDynamicBaseURL(config, source, basePath, trustedProxyHeaders) {
190
+ const host = getHostFromSource(source, trustedProxyHeaders);
166
191
  if (!host) {
167
192
  if (config.fallback) return withPath(config.fallback, basePath);
168
193
  throw new BetterAuthError("Could not determine host from request headers. Please provide a fallback URL in your baseURL config.");
169
194
  }
170
- if (config.allowedHosts.some((pattern) => matchesHostPattern(host, pattern))) return withPath(`${getProtocolFromRequest(request, config.protocol)}://${host}`, basePath);
195
+ if (config.allowedHosts.some((pattern) => matchesHostPattern(host, pattern))) return withPath(`${getProtocolFromSource(source, config.protocol, trustedProxyHeaders)}://${host}`, basePath);
171
196
  if (config.fallback) return withPath(config.fallback, basePath);
172
197
  throw new BetterAuthError(`Host "${host}" is not in the allowed hosts list. Allowed hosts: ${config.allowedHosts.join(", ")}. Add this host to your allowedHosts config or provide a fallback URL.`);
173
198
  }
@@ -182,14 +207,15 @@ function resolveDynamicBaseURL(config, request, basePath) {
182
207
  * @param trustedProxyHeaders Whether to trust proxy headers (for legacy behavior)
183
208
  * @returns The resolved base URL with path
184
209
  */
185
- function resolveBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders) {
210
+ function resolveBaseURL(config, basePath, source, loadEnv, trustedProxyHeaders) {
186
211
  if (isDynamicBaseURLConfig(config)) {
187
- if (request) return resolveDynamicBaseURL(config, request, basePath);
212
+ if (source) return resolveDynamicBaseURL(config, source, basePath, trustedProxyHeaders);
188
213
  if (config.fallback) return withPath(config.fallback, basePath);
189
- return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
214
+ return getBaseURL(void 0, basePath, void 0, loadEnv, trustedProxyHeaders);
190
215
  }
216
+ const request = isRequestLike(source) ? source : void 0;
191
217
  if (typeof config === "string") return getBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders);
192
218
  return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
193
219
  }
194
220
  //#endregion
195
- export { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
221
+ export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -489,17 +489,17 @@
489
489
  "kysely": "^0.28.14",
490
490
  "nanostores": "^1.1.1",
491
491
  "zod": "^4.3.6",
492
- "@better-auth/core": "1.6.2",
493
- "@better-auth/drizzle-adapter": "1.6.2",
494
- "@better-auth/kysely-adapter": "1.6.2",
495
- "@better-auth/memory-adapter": "1.6.2",
496
- "@better-auth/mongo-adapter": "1.6.2",
497
- "@better-auth/prisma-adapter": "1.6.2",
498
- "@better-auth/telemetry": "1.6.2"
492
+ "@better-auth/core": "1.6.4",
493
+ "@better-auth/drizzle-adapter": "1.6.4",
494
+ "@better-auth/kysely-adapter": "1.6.4",
495
+ "@better-auth/memory-adapter": "1.6.4",
496
+ "@better-auth/mongo-adapter": "1.6.4",
497
+ "@better-auth/prisma-adapter": "1.6.4",
498
+ "@better-auth/telemetry": "1.6.4"
499
499
  },
500
500
  "devDependencies": {
501
501
  "@lynx-js/react": "^0.116.3",
502
- "@sveltejs/kit": "^2.53.3",
502
+ "@sveltejs/kit": "^2.57.1",
503
503
  "@tanstack/react-start": "^1.163.2",
504
504
  "@tanstack/solid-start": "^1.163.2",
505
505
  "@types/bun": "^1.3.9",
@@ -512,7 +512,7 @@
512
512
  "happy-dom": "^20.8.9",
513
513
  "listhen": "^1.9.0",
514
514
  "msw": "^2.12.10",
515
- "next": "^16.2.0",
515
+ "next": "^16.2.3",
516
516
  "oauth2-mock-server": "^8.2.2",
517
517
  "react": "^19.2.4",
518
518
  "react-dom": "^19.2.4",
@@ -531,7 +531,7 @@
531
531
  "@tanstack/solid-start": "^1.0.0",
532
532
  "better-sqlite3": "^12.0.0",
533
533
  "drizzle-kit": ">=0.31.4",
534
- "drizzle-orm": ">=0.41.0",
534
+ "drizzle-orm": "^0.45.2",
535
535
  "mongodb": "^6.0.0 || ^7.0.0",
536
536
  "mysql2": "^3.0.0",
537
537
  "next": "^14.0.0 || ^15.0.0 || ^16.0.0",