better-auth 1.7.19 → 1.7.21

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,6 +1,7 @@
1
1
  import { OverrideMerge, Prettify as Prettify$1, UnionToIntersection } from "../types/helper.mjs";
2
2
  import { AdditionalSessionFieldsInput, AdditionalUserFieldsInput } from "../types/models.mjs";
3
3
  import "../types/index.mjs";
4
+ import { generateGenericState, parseGenericState } from "../state.mjs";
4
5
  import "../index.mjs";
5
6
  import { isAPIError } from "../utils/is-api-error.mjs";
6
7
  import { DispatchContext, dispatchAuthEndpoint } from "./dispatch.mjs";
@@ -4049,4 +4050,4 @@ export declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext
4049
4050
  } extends (infer T_2) ? { [K in keyof T_2 as K extends keyof T_1 ? never : K]: T_2[K]; } : never) & T_1> : never : never : never;
4050
4051
  };
4051
4052
  //#endregion
4052
- export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, markSessionOutsideStore, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
4053
+ export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, generateGenericState, getAccessToken, getAuthoritativeSessionFromCtx, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, markSessionOutsideStore, ok, optionsMiddleware, originCheck, originCheckMiddleware, parseGenericState, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
@@ -5,6 +5,7 @@ import { onRequestRateLimit } from "./rate-limiter/index.mjs";
5
5
  import { getShouldSkipSessionRefresh, setShouldSkipSessionRefresh } from "./state/should-session-refresh.mjs";
6
6
  import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
7
7
  import { addOAuthServerContext, getOAuthState } from "./state/oauth.mjs";
8
+ import { generateGenericState, parseGenericState } from "../state.mjs";
8
9
  import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs";
9
10
  import { callbackOAuth } from "./routes/callback.mjs";
10
11
  import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./routes/email-verification.mjs";
@@ -223,4 +224,4 @@ const router = (ctx, options) => {
223
224
  });
224
225
  };
225
226
  //#endregion
226
- export { APIError, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, markSessionOutsideStore, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
227
+ export { APIError, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, generateGenericState, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, markSessionOutsideStore, ok, optionsMiddleware, originCheck, originCheckMiddleware, parseGenericState, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
@@ -5,7 +5,7 @@ import { setSessionCookie } from "../../cookies/index.mjs";
5
5
  import { safeCloneRequest } from "../../utils/request.mjs";
6
6
  import { createEmailVerificationToken } from "./email-verification.mjs";
7
7
  import { assertPasswordNotTooLong, assertPasswordNotTooShort } from "../../utils/password.mjs";
8
- import { runWithTransaction } from "@better-auth/core/context";
8
+ import { queueAfterTransactionHook, runWithTransaction } from "@better-auth/core/context";
9
9
  import { isDevelopment } from "@better-auth/core/env";
10
10
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
11
11
  import { generateId } from "@better-auth/core/utils/id";
@@ -198,7 +198,13 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
198
198
  * between existing and non-existing emails.
199
199
  */
200
200
  await ctx.context.password.hash(password);
201
- if (ctx.context.options.emailAndPassword?.onExistingUserSignUp) await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailAndPassword.onExistingUserSignUp({ user: dbUser.user }, safeCloneRequest(ctx.request)));
201
+ const onExistingUserSignUp = ctx.context.options.emailAndPassword?.onExistingUserSignUp;
202
+ if (onExistingUserSignUp) {
203
+ const request = safeCloneRequest(ctx.request);
204
+ await queueAfterTransactionHook(async () => {
205
+ await ctx.context.runInBackgroundOrAwait(onExistingUserSignUp({ user: dbUser.user }, request));
206
+ }, { onError: (error) => ctx.context.logger.error("Failed to run onExistingUserSignUp after sign-up", error) });
207
+ }
202
208
  return buildGenericDuplicateResponse();
203
209
  }
204
210
  throw APIError.from("UNPROCESSABLE_ENTITY", BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL);
@@ -238,15 +244,20 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
238
244
  accountId: createdUser.id,
239
245
  password: hash
240
246
  });
241
- if (ctx.context.options.emailVerification?.sendOnSignUp ?? ctx.context.options.emailAndPassword.requireEmailVerification) {
242
- const token = await createEmailVerificationToken(ctx.context.secret, createdUser.email, void 0, ctx.context.options.emailVerification?.expiresIn, { userId: createdUser.id });
247
+ const shouldSendVerificationEmail = ctx.context.options.emailVerification?.sendOnSignUp ?? ctx.context.options.emailAndPassword.requireEmailVerification;
248
+ const sendVerificationEmail = ctx.context.options.emailVerification?.sendVerificationEmail;
249
+ if (shouldSendVerificationEmail && sendVerificationEmail) {
243
250
  const callbackURL = body.callbackURL ? encodeURIComponent(body.callbackURL) : encodeURIComponent("/");
244
- const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;
245
- if (ctx.context.options.emailVerification?.sendVerificationEmail) await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailVerification.sendVerificationEmail({
246
- user: createdUser,
247
- url,
248
- token
249
- }, safeCloneRequest(ctx.request)));
251
+ const request = safeCloneRequest(ctx.request);
252
+ await queueAfterTransactionHook(async () => {
253
+ const token = await createEmailVerificationToken(ctx.context.secret, createdUser.email, void 0, ctx.context.options.emailVerification?.expiresIn, { userId: createdUser.id });
254
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;
255
+ await ctx.context.runInBackgroundOrAwait(sendVerificationEmail({
256
+ user: createdUser,
257
+ url,
258
+ token
259
+ }, request));
260
+ }, { onError: (error) => ctx.context.logger.error("Failed to send the sign-up verification email", error) });
250
261
  }
251
262
  if (shouldSkipAutoSignIn) return ctx.json({
252
263
  token: null,
@@ -1,7 +1,7 @@
1
1
  import { buildSchema } from "./get-schema.mjs";
2
2
  import { createLogger } from "@better-auth/core/env";
3
3
  import { BetterAuthError } from "@better-auth/core/error";
4
- import { createKyselyAdapter, getMssqlSchema, getPostgresSchema, toIntrospectedTables, toPhysicalSchema } from "@better-auth/kysely-adapter";
4
+ import { createKyselyAdapter, getMssqlSchema, getPostgresSchema, introspectPostgresTables, toIntrospectedTables, toPhysicalSchema } from "@better-auth/kysely-adapter";
5
5
  import { initGetFieldName, initGetModelName } from "@better-auth/core/db/adapter";
6
6
  import { diffSchema, formatSchemaFinding, getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, invalidateSchemaChecks } from "@better-auth/core/db/internal";
7
7
  import { sql } from "kysely";
@@ -354,9 +354,8 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
354
354
  logger.error("Only kysely adapter is supported for migrations. You can use `generate` command to generate the schema, if you're using a different adapter.");
355
355
  process.exit(1);
356
356
  }
357
- const allTableMetadata = await db.introspection.getTables();
358
357
  let target;
359
- let tableMetadata = allTableMetadata;
358
+ let tableMetadata;
360
359
  switch (dbType) {
361
360
  case "postgres": {
362
361
  const schema = schemaName ?? await getPostgresSchema(db);
@@ -373,11 +372,7 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
373
372
  } catch (error) {
374
373
  logger.debug(`Could not verify schema existence: ${error instanceof Error ? error.message : String(error)}`);
375
374
  }
376
- /**
377
- * Kysely 0.28 does not expose `isForeign`, while 0.29 adds foreign table metadata.
378
- * @see https://github.com/kysely-org/kysely/pull/1494
379
- */
380
- tableMetadata = allTableMetadata.filter((table) => table.schema === schema && !table.isView && !("isForeign" in table && table.isForeign));
375
+ tableMetadata = (await introspectPostgresTables(db, [schema])).filter((table) => !table.isView && !table.isForeign);
381
376
  logger.debug(`Found ${tableMetadata.length} table(s) in schema '${schema}': ${tableMetadata.map((table) => table.name).join(", ") || "(none)"}`);
382
377
  break;
383
378
  }
@@ -388,15 +383,18 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
388
383
  schema
389
384
  };
390
385
  logger.debug(`SQL Server migration: Using schema '${schema}' (from the current user's default schema)`);
391
- tableMetadata = allTableMetadata.filter((table) => table.schema === schema);
386
+ tableMetadata = (await db.introspection.getTables()).filter((table) => table.schema === schema);
392
387
  break;
393
388
  }
394
389
  case "mysql":
395
390
  target = { type: "mysql" };
391
+ tableMetadata = await db.introspection.getTables();
396
392
  break;
397
- case "sqlite": target = { type: "sqlite" };
393
+ case "sqlite":
394
+ target = { type: "sqlite" };
395
+ tableMetadata = await db.introspection.getTables();
398
396
  }
399
- const databaseIndexMap = await getDatabaseIndexMap(db, target, allTableMetadata.map((table) => table.name), introspectIndexes);
397
+ const databaseIndexMap = await getDatabaseIndexMap(db, target, tableMetadata.map((table) => table.name), introspectIndexes);
400
398
  const databaseColumnBounds = await getDatabaseColumnBounds(db, target);
401
399
  const schemaProblems = diffSchema(toPhysicalSchema(db, betterAuthSchema), toIntrospectedTables(tableMetadata)).filter((finding) => finding.kind === "unexpected-required-column").map((finding) => formatSchemaFinding(finding, "database"));
402
400
  const toBeCreated = [];
@@ -930,7 +930,10 @@ const createInternalAdapter = (adapter, ctx) => {
930
930
  * is best-effort there (it is intended for development and tests).
931
931
  */
932
932
  reserveVerificationValue: async (data) => {
933
- const reservationId = encodeDeterministicId(new Uint8Array(await createHash("SHA-256").digest(new TextEncoder().encode("reserve:" + data.identifier))), options);
933
+ const reservationId = encodeDeterministicId(new Uint8Array(await createHash("SHA-256").digest(new TextEncoder().encode("reserve:" + data.identifier))), options, {
934
+ adapter,
935
+ model: "verification"
936
+ });
934
937
  const storageOption = getStorageOption(data.identifier, options.verification?.storeIdentifier);
935
938
  const storedIdentifier = await processIdentifier(data.identifier, storageOption);
936
939
  if (secondaryStorage && !options.verification?.storeInDatabase) throw new BetterAuthError("reserveVerificationValue requires database-backed verification storage. Set verification.storeInDatabase to true for flows that reserve verification values.");
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.7.19";
2
+ var version = "1.7.21";
3
3
  //#endregion
4
4
  export { version };
@@ -550,6 +550,21 @@ const deviceVerify = (grant) => createAuthEndpoint("/device", {
550
550
  increment: {},
551
551
  set: { userId: session.user.id }
552
552
  })) deviceCodeRecord.userId = session.user.id;
553
+ else {
554
+ const current = await ctx.context.adapter.findOne({
555
+ model: "deviceCode",
556
+ where: [{
557
+ field: "id",
558
+ value: deviceCodeRecord.id
559
+ }]
560
+ });
561
+ if (!current) throw new APIError("BAD_REQUEST", {
562
+ error: "invalid_request",
563
+ error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_USER_CODE.message
564
+ });
565
+ deviceCodeRecord.userId = current.userId;
566
+ deviceCodeRecord.status = current.status;
567
+ }
553
568
  }
554
569
  const canReviewRequest = session?.user.id !== void 0 && deviceCodeRecord.userId === session.user.id;
555
570
  const grantContext = canReviewRequest ? grant?.getVerificationContext(deviceCodeRecord) : void 0;
@@ -25,10 +25,7 @@ declare module "@better-auth/core" {
25
25
  * Extracts common fields from GenericOAuthConfig for provider helpers.
26
26
  */
27
27
  export type BaseOAuthProviderOptions = Pick<GenericOAuthConfig, "clientId" | "clientSecret" | "tokenEndpointAuth" | "scopes" | "redirectURI" | "endSessionEndpoint" | "postLogoutRedirectURI" | "disableProviderLogout" | "pkce" | "disableImplicitSignUp" | "disableSignUp" | "overrideUserInfo">;
28
- type PendingDiscovery = Map<string, {
29
- config: GenericOAuthConfig;
30
- attempt?: Promise<void> | undefined;
31
- }>;
28
+ type PendingDiscovery = Map<string, GenericOAuthConfig>;
32
29
  /**
33
30
  * A generic OAuth plugin that registers any OAuth/OIDC provider
34
31
  * as a first-class social provider.
@@ -25,6 +25,7 @@ function isClientSecretTokenEndpointAuth(tokenEndpointAuth) {
25
25
  }
26
26
  /** A request waits on a discovery retry, so bound it (the SSO plugin uses 10s too). */
27
27
  const DISCOVERY_RETRY_TIMEOUT_MS = 1e4;
28
+ const MAX_DISCOVERY_REDIRECTS = 5;
28
29
  /**
29
30
  * Providers skipped because startup discovery failed. Init stores them on each auth
30
31
  * instance's context, so one plugin object can serve several instances. It is a context
@@ -44,18 +45,35 @@ const readIdTokenClaims = (idToken) => {
44
45
  }
45
46
  };
46
47
  async function fetchDiscovery(url, headers, timeout) {
47
- const result = await betterFetch(url, {
48
- method: "GET",
49
- headers,
50
- signal: timeout ? AbortSignal.timeout(timeout) : void 0
51
- });
52
- if (result.error || !result.data) return null;
53
- if (result.data.issuer) try {
54
- new URL(result.data.issuer);
55
- } catch {
56
- return null;
48
+ const signal = timeout ? AbortSignal.timeout(timeout) : void 0;
49
+ let target = url;
50
+ for (let hop = 0;; hop++) {
51
+ const redirect = {};
52
+ const result = await betterFetch(target, {
53
+ method: "GET",
54
+ headers,
55
+ redirect: headers ? "manual" : void 0,
56
+ signal,
57
+ onError: ({ response }) => {
58
+ if (response.status >= 300 && response.status < 400) redirect.location = response.headers.get("location");
59
+ }
60
+ });
61
+ if (headers && redirect.location) {
62
+ const next = new URL(redirect.location, target);
63
+ if (next.origin !== new URL(url).origin) throw new Error(`discovery redirected to ${next.origin}; with discoveryHeaders set, only same-origin redirects are followed. Set discoveryUrl to the final URL.`);
64
+ if (hop >= MAX_DISCOVERY_REDIRECTS) throw new Error(`discovery redirected more than ${MAX_DISCOVERY_REDIRECTS} times. Set discoveryUrl to the final URL.`);
65
+ target = next.href;
66
+ continue;
67
+ }
68
+ if (headers && result.error?.status === 0) throw new Error("discovery redirected; with discoveryHeaders set, set discoveryUrl to the final URL.");
69
+ if (result.error || !result.data) return null;
70
+ if (result.data.issuer) try {
71
+ new URL(result.data.issuer);
72
+ } catch {
73
+ return null;
74
+ }
75
+ return result.data;
57
76
  }
58
- return result.data;
59
77
  }
60
78
  async function fetchUserInfo(tokens, userInfoUrl, idTokenClaims) {
61
79
  if (tokens.idToken) {
@@ -128,8 +146,9 @@ const genericOAuth = (options) => {
128
146
  ctx.logger.error(`Provider "${c.providerId}": invalid jwks_uri "${discovered.jwks_uri}" in discovery document. Provider skipped.`);
129
147
  continue;
130
148
  }
149
+ const jwksHeaders = jwksUrl.origin === new URL(c.discoveryUrl).origin ? c.discoveryHeaders : void 0;
131
150
  idTokenConfig = {
132
- jwks: createRemoteJWKSet(jwksUrl),
151
+ jwks: createRemoteJWKSet(jwksUrl, jwksHeaders && { headers: jwksHeaders }),
133
152
  issuer: discovered.issuer,
134
153
  audience: c.clientId,
135
154
  algorithms: isOidc ? signingAlgs : void 0
@@ -287,7 +306,8 @@ const genericOAuth = (options) => {
287
306
  authentication: c.authentication,
288
307
  tokenEndpointAuth,
289
308
  tokenEndpoint: tokenUrl,
290
- extraParams: resolvedRefreshParams
309
+ extraParams: resolvedRefreshParams,
310
+ headers: c.authorizationHeaders
291
311
  });
292
312
  return applyDefaultAccessTokenExpiry(tokens, c.accessTokenExpiresIn);
293
313
  },
@@ -303,18 +323,13 @@ const genericOAuth = (options) => {
303
323
  }
304
324
  return genericProviders;
305
325
  };
306
- const retryDiscovery = (ctx, providerId) => {
326
+ const retryDiscovery = async (ctx, providerId) => {
307
327
  const pendingDiscovery = getPendingDiscovery(ctx);
308
328
  const pending = pendingDiscovery?.get(providerId);
309
329
  if (!pending) return;
310
- pending.attempt ??= buildProviders([pending.config], ctx, DISCOVERY_RETRY_TIMEOUT_MS).then(([provider]) => {
311
- if (!provider) return;
312
- pendingDiscovery?.delete(providerId);
313
- ctx.socialProviders.unshift(provider);
314
- }).finally(() => {
315
- pending.attempt = void 0;
316
- });
317
- return pending.attempt;
330
+ const [provider] = await buildProviders([pending], ctx, DISCOVERY_RETRY_TIMEOUT_MS);
331
+ if (!provider || !pendingDiscovery?.delete(providerId)) return;
332
+ ctx.socialProviders.unshift(provider);
318
333
  };
319
334
  const requestedProviderId = (context) => {
320
335
  const id = context.body?.provider ?? context.body?.providerId ?? context.query?.provider ?? context.params?.id;
@@ -326,7 +341,7 @@ const genericOAuth = (options) => {
326
341
  init: async (ctx) => {
327
342
  const genericProviders = await buildProviders(options.config, ctx);
328
343
  const built = new Set(genericProviders.map((p) => p.id));
329
- const genericOAuthPendingDiscovery = new Map(options.config.filter((c) => !built.has(c.providerId)).map((c) => [c.providerId, { config: c }]));
344
+ const genericOAuthPendingDiscovery = new Map(options.config.filter((c) => !built.has(c.providerId)).map((c) => [c.providerId, c]));
330
345
  const existingIds = new Set(ctx.socialProviders.map((p) => p.id));
331
346
  for (const gp of genericProviders) if (existingIds.has(gp.id)) ctx.logger.warn(`Generic OAuth provider "${gp.id}" shadows a built-in social provider with the same ID`);
332
347
  return { context: {
@@ -211,12 +211,15 @@ export interface GenericOAuthConfig<ID extends string = string> {
211
211
  */
212
212
  authentication?: ("basic" | "post") | undefined;
213
213
  /**
214
- * Custom headers to include in the discovery request.
214
+ * Custom headers to include in the discovery request, and in the JWKS request when
215
+ * `jwks_uri` has the same origin as `discoveryUrl`. When set, discovery follows
216
+ * redirects only within that origin and fails on a redirect to another origin.
215
217
  * Useful for providers like Epic that require specific headers (e.g., Epic-Client-ID).
216
218
  */
217
219
  discoveryHeaders?: Record<string, string> | undefined;
218
220
  /**
219
- * Custom headers to include in the authorization request.
221
+ * Custom headers to include in token endpoint requests: the authorization
222
+ * code exchange and token refresh.
220
223
  * Useful for providers like Qonto that require specific headers (e.g., X-Qonto-Staging-Token for local development).
221
224
  */
222
225
  authorizationHeaders?: Record<string, string> | undefined;
@@ -274,9 +274,9 @@ const siwe = (options) => {
274
274
  });
275
275
  } catch (error) {
276
276
  if (isAPIError(error)) throw error;
277
+ ctx.context.logger.error("Failed to verify SIWE message", error);
277
278
  throw APIError.fromStatus("UNAUTHORIZED", {
278
279
  message: "Something went wrong. Please try again later.",
279
- error: error instanceof Error ? error.message : "Unknown error",
280
280
  status: 401
281
281
  });
282
282
  }
package/dist/state.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { GenericEndpointContext } from "@better-auth/core";
2
2
  import { BetterAuthError } from "@better-auth/core/error";
3
3
  import * as z from "zod";
4
+ import { getAuthStateVerificationIdentifier } from "@better-auth/core/utils/state";
4
5
  //#region src/state.d.ts
5
6
  declare const stateDataSchema: z.ZodObject<{
6
7
  callbackURL: z.ZodString;
@@ -19,10 +20,6 @@ declare const stateDataSchema: z.ZodObject<{
19
20
  serverContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
21
  }, z.core.$loose>;
21
22
  export type StateData = z.infer<typeof stateDataSchema>;
22
- /**
23
- * Verification key for database-backed OAuth and SAML relay state.
24
- */
25
- export declare const getAuthStateVerificationIdentifier: (state: string) => string;
26
23
  export declare function generateGenericState(c: GenericEndpointContext, stateData: StateData, settings?: {
27
24
  cookieName: string;
28
25
  }): Promise<{
@@ -50,4 +47,4 @@ export declare function parseGenericState(c: GenericEndpointContext, state: stri
50
47
  serverContext?: Record<string, unknown> | undefined;
51
48
  }>;
52
49
  //#endregion
53
- export {};
50
+ export { getAuthStateVerificationIdentifier };
package/dist/state.mjs CHANGED
@@ -4,6 +4,7 @@ import { expireCookie } from "./cookies/index.mjs";
4
4
  import { derivePurposeKey } from "./crypto/purpose.mjs";
5
5
  import { BetterAuthError } from "@better-auth/core/error";
6
6
  import * as z from "zod";
7
+ import { getAuthStateVerificationIdentifier } from "@better-auth/core/utils/state";
7
8
  //#region src/state.ts
8
9
  const stateDataSchema = z.looseObject({
9
10
  callbackURL: z.string(),
@@ -34,10 +35,6 @@ const stateDataSchema = z.looseObject({
34
35
  */
35
36
  serverContext: z.record(z.string(), z.unknown()).optional()
36
37
  });
37
- /**
38
- * Verification key for database-backed OAuth and SAML relay state.
39
- */
40
- const getAuthStateVerificationIdentifier = (state) => `auth-state:${state}`;
41
38
  const INTERNAL_STATE_KEYS = new Set(Object.keys(stateDataSchema.shape));
42
39
  var StateError = class extends BetterAuthError {
43
40
  code;
@@ -1,5 +1,2 @@
1
- //#region src/utils/hide-metadata.d.ts
2
- export declare const HIDE_METADATA: {
3
- readonly scope: "server";
4
- };
5
- //#endregion
1
+ import { HIDE_METADATA } from "@better-auth/core/api";
2
+ export { HIDE_METADATA };
@@ -1,4 +1 @@
1
- //#region src/utils/hide-metadata.ts
2
- const HIDE_METADATA = { scope: "server" };
3
- //#endregion
4
- export { HIDE_METADATA };
1
+ export { HIDE_METADATA } from "@better-auth/core/api";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.7.19",
3
+ "version": "1.7.21",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -503,13 +503,13 @@
503
503
  "kysely": "^0.28.17 || ^0.29.0",
504
504
  "nanostores": "^1.3.0",
505
505
  "zod": "^4.5.4",
506
- "@better-auth/core": "1.7.19",
507
- "@better-auth/drizzle-adapter": "1.7.19",
508
- "@better-auth/kysely-adapter": "1.7.19",
509
- "@better-auth/memory-adapter": "1.7.19",
510
- "@better-auth/mongo-adapter": "1.7.19",
511
- "@better-auth/prisma-adapter": "1.7.19",
512
- "@better-auth/telemetry": "1.7.19"
506
+ "@better-auth/core": "1.7.21",
507
+ "@better-auth/drizzle-adapter": "1.7.21",
508
+ "@better-auth/kysely-adapter": "1.7.21",
509
+ "@better-auth/memory-adapter": "1.7.21",
510
+ "@better-auth/mongo-adapter": "1.7.21",
511
+ "@better-auth/prisma-adapter": "1.7.21",
512
+ "@better-auth/telemetry": "1.7.21"
513
513
  },
514
514
  "devDependencies": {
515
515
  "@lynx-js/react": "^0.121.2",