better-auth 1.6.25 → 1.6.26

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.
@@ -125,7 +125,7 @@ function createDatabaseStorageWrapper(ctx) {
125
125
  lastRequest: now
126
126
  }
127
127
  })) {
128
- deleteExpiredRows(now);
128
+ await deleteExpiredRows(now);
129
129
  return {
130
130
  allowed: true,
131
131
  retryAfter: null
@@ -166,9 +166,9 @@ function createDatabaseStorageWrapper(ctx) {
166
166
  retryAfter: getRetryAfter(fresh.lastRequest, rule.window)
167
167
  };
168
168
  };
169
- const deleteExpiredRows = (now) => {
169
+ const deleteExpiredRows = async (now) => {
170
170
  const cutoff = now - Math.max(ctx.rateLimit.window, ...getDefaultSpecialRules().map((r) => r.window)) * 1e3;
171
- ctx.runInBackground(db.deleteMany({
171
+ await ctx.runInBackgroundOrAwait(db.deleteMany({
172
172
  model,
173
173
  where: [{
174
174
  field: "lastRequest",
@@ -17,6 +17,8 @@ const createInternalAdapter = (adapter, ctx) => {
17
17
  const logger = ctx.logger;
18
18
  const options = ctx.options;
19
19
  const secondaryStorage = options.secondaryStorage;
20
+ const databaseStoresSessions = !secondaryStorage || options.session?.storeSessionInDatabase === true;
21
+ const preservesDatabaseSessions = secondaryStorage !== void 0 && options.session?.preserveSessionInDatabase === true;
20
22
  const verificationConsumeLocks = /* @__PURE__ */ new Map();
21
23
  let warnedNonAtomicConsume = false;
22
24
  const sessionExpiration = options.session?.expiresIn || 3600 * 24 * 7;
@@ -55,6 +57,20 @@ const createInternalAdapter = (adapter, ctx) => {
55
57
  if (verificationConsumeLocks.get(key) === next) verificationConsumeLocks.delete(key);
56
58
  }
57
59
  }
60
+ const deleteSecondaryStorageSessions = async (userId) => {
61
+ if (!secondaryStorage) return;
62
+ const activeSession = await secondaryStorage.get(`active-sessions-${userId}`);
63
+ const sessions = activeSession ? safeJSONParse(activeSession) : [];
64
+ if (!sessions) return;
65
+ for (const session of sessions) await secondaryStorage.delete(session.token);
66
+ await secondaryStorage.delete(`active-sessions-${userId}`);
67
+ };
68
+ const deleteDatabaseSessions = async (userId) => {
69
+ await deleteManyWithHooks([{
70
+ field: "userId",
71
+ value: userId
72
+ }], "session", void 0);
73
+ };
58
74
  return {
59
75
  createOAuthUser: async (user, account) => {
60
76
  return runWithTransaction(adapter, async () => {
@@ -146,10 +162,8 @@ const createInternalAdapter = (adapter, ctx) => {
146
162
  return total;
147
163
  },
148
164
  deleteUser: async (userId) => {
149
- if (!secondaryStorage || options.session?.storeSessionInDatabase) await deleteManyWithHooks([{
150
- field: "userId",
151
- value: userId
152
- }], "session", void 0);
165
+ await deleteSecondaryStorageSessions(userId);
166
+ if (databaseStoresSessions) await deleteDatabaseSessions(userId);
153
167
  await deleteManyWithHooks([{
154
168
  field: "userId",
155
169
  value: userId
@@ -271,7 +285,7 @@ const createInternalAdapter = (adapter, ctx) => {
271
285
  const sessionStringified = await secondaryStorage.get(sessionToken);
272
286
  if (sessionStringified) try {
273
287
  const s = typeof sessionStringified === "string" ? JSON.parse(sessionStringified) : sessionStringified;
274
- if (!s) return [];
288
+ if (!s) continue;
275
289
  const expiresAt = new Date(s.session.expiresAt);
276
290
  if (options?.onlyActiveSessions && expiresAt <= /* @__PURE__ */ new Date()) continue;
277
291
  const session = {
@@ -377,9 +391,8 @@ const createInternalAdapter = (adapter, ctx) => {
377
391
  } else logger.error("Active sessions list not found in secondary storage");
378
392
  }
379
393
  await secondaryStorage.delete(token);
380
- if (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase) return;
381
394
  }
382
- await deleteWithHooks([{
395
+ if (databaseStoresSessions && !preservesDatabaseSessions) await deleteWithHooks([{
383
396
  field: "token",
384
397
  value: token
385
398
  }], "session", void 0);
@@ -402,25 +415,14 @@ const createInternalAdapter = (adapter, ctx) => {
402
415
  }], "account", void 0);
403
416
  },
404
417
  deleteUserSessions: async (userId) => {
405
- if (secondaryStorage) {
406
- const activeSession = await secondaryStorage.get(`active-sessions-${userId}`);
407
- const sessions = activeSession ? safeJSONParse(activeSession) : [];
408
- if (!sessions) return;
409
- for (const session of sessions) await secondaryStorage.delete(session.token);
410
- await secondaryStorage.delete(`active-sessions-${userId}`);
411
- if (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase) return;
412
- }
413
- await deleteManyWithHooks([{
414
- field: "userId",
415
- value: userId
416
- }], "session", void 0);
418
+ await deleteSecondaryStorageSessions(userId);
419
+ if (databaseStoresSessions && !preservesDatabaseSessions) await deleteDatabaseSessions(userId);
417
420
  },
418
421
  deleteSessions: async (sessionTokens) => {
419
422
  if (secondaryStorage) {
420
423
  for (const sessionToken of sessionTokens) if (await secondaryStorage.get(sessionToken)) await secondaryStorage.delete(sessionToken);
421
- if (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase) return;
422
424
  }
423
- await deleteManyWithHooks([{
425
+ if (databaseStoresSessions && !preservesDatabaseSessions) await deleteManyWithHooks([{
424
426
  field: "token",
425
427
  value: sessionTokens,
426
428
  operator: "in"
@@ -16,6 +16,20 @@ function toNextJsHandler(auth) {
16
16
  DELETE: handler
17
17
  };
18
18
  }
19
+ let nextHeadersModulePromise;
20
+ /**
21
+ * Cache ESM resolution while leaving the request-scoped `headers()` and
22
+ * `cookies()` calls uncached.
23
+ *
24
+ * @see https://github.com/better-auth/better-auth/issues/10466
25
+ */
26
+ const loadNextHeadersModule = () => {
27
+ nextHeadersModulePromise ??= import("next/headers.js").catch((error) => {
28
+ nextHeadersModulePromise = void 0;
29
+ throw error;
30
+ });
31
+ return nextHeadersModulePromise;
32
+ };
19
33
  const nextCookies = () => {
20
34
  let hasWarned = false;
21
35
  return {
@@ -34,7 +48,7 @@ const nextCookies = () => {
34
48
  if ("_flag" in ctx && ctx._flag === "router") return;
35
49
  let headersStore;
36
50
  try {
37
- const { headers } = await import("next/headers.js");
51
+ const { headers } = await loadNextHeadersModule();
38
52
  headersStore = await headers();
39
53
  } catch {
40
54
  return;
@@ -68,7 +82,7 @@ const nextCookies = () => {
68
82
  const parsed = parseSetCookieHeader(setCookies);
69
83
  let cookieHelper;
70
84
  try {
71
- const { cookies } = await import("next/headers.js");
85
+ const { cookies } = await loadNextHeadersModule();
72
86
  cookieHelper = await cookies();
73
87
  } catch (error) {
74
88
  if (error instanceof Error && (error.message.startsWith("`cookies` was called outside a request scope.") || error.message.includes("Cannot find module"))) return;
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.6.25";
2
+ var version = "1.6.26";
3
3
  //#endregion
4
4
  export { version };
@@ -154,7 +154,6 @@ const anonymous = (options) => {
154
154
  const newSessionIsAnonymous = Boolean(newSessionUser?.isAnonymous);
155
155
  if (options?.disableDeleteAnonymousUser || isSameUser || newSessionIsAnonymous) return;
156
156
  try {
157
- await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
158
157
  await ctx.context.internalAdapter.deleteUser(session.user.id);
159
158
  } catch (error) {
160
159
  ctx.context.logger.error("Failed to clean up anonymous user during post-link cleanup", {
@@ -56,7 +56,7 @@ const emailOTP = (options) => {
56
56
  if (email) {
57
57
  const otp = opts.generateOTP({
58
58
  email,
59
- type: ctx.body.type
59
+ type: "email-verification"
60
60
  }, ctx) || defaultOTPGenerator(opts);
61
61
  const storedOTP = await storeOTP(ctx, opts, otp);
62
62
  await ctx.context.internalAdapter.createVerificationValue({
@@ -235,7 +235,6 @@ const checkVerificationOTP = (opts) => createAuthEndpoint("/email-otp/check-veri
235
235
  }, async (ctx) => {
236
236
  const email = ctx.body.email.toLowerCase();
237
237
  if (!z.email().safeParse(email).success) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
238
- if (!await ctx.context.internalAdapter.findUserByEmail(email)) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
239
238
  const identifier = toOTPIdentifier(ctx.body.type, email);
240
239
  const verificationValue = await ctx.context.internalAdapter.findVerificationValue(identifier);
241
240
  if (!verificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
@@ -253,6 +252,12 @@ const checkVerificationOTP = (opts) => createAuthEndpoint("/email-otp/check-veri
253
252
  await ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
254
253
  throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
255
254
  }
255
+ if (!await ctx.context.internalAdapter.findUserByEmail(email))
256
+ /**
257
+ * safe to leak the existence of a user, given the user has already the OTP from the
258
+ * email
259
+ */
260
+ throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
256
261
  return ctx.json({ success: true });
257
262
  });
258
263
  const verifyEmailOTPBodySchema = z.object({
@@ -574,13 +579,13 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
574
579
  } }
575
580
  }, async (ctx) => {
576
581
  const email = ctx.body.email.toLowerCase();
577
- await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
578
- const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
579
- if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
580
582
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
581
583
  if (ctx.body.password.length < minPasswordLength) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
582
584
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
583
585
  if (ctx.body.password.length > maxPasswordLength) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
586
+ await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
587
+ const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
588
+ if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
584
589
  const passwordHash = await ctx.context.password.hash(ctx.body.password);
585
590
  if (!user.accounts?.find((account) => account.providerId === "credential")) await ctx.context.internalAdapter.createAccount({
586
591
  userId: user.user.id,
@@ -1,17 +1,18 @@
1
+ import { getCurrentAdapter } from "@better-auth/core/context";
1
2
  //#region src/plugins/jwt/adapter.ts
2
- const getJwksAdapter = (adapter, options) => {
3
+ const getJwksAdapter = (baseAdapter, options) => {
3
4
  return {
4
5
  getAllKeys: async (ctx) => {
5
6
  if (options?.adapter?.getJwks) return await options.adapter.getJwks(ctx);
6
- return await adapter.findMany({ model: "jwks" });
7
+ return await (await getCurrentAdapter(baseAdapter)).findMany({ model: "jwks" });
7
8
  },
8
9
  getLatestKey: async (ctx) => {
9
10
  if (options?.adapter?.getJwks) return (await options.adapter.getJwks(ctx))?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
10
- return (await adapter.findMany({ model: "jwks" }))?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
11
+ return (await (await getCurrentAdapter(baseAdapter)).findMany({ model: "jwks" }))?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
11
12
  },
12
13
  createJwk: async (ctx, webKey) => {
13
14
  if (options?.adapter?.createJwk) return await options.adapter.createJwk(webKey, ctx);
14
- return await adapter.create({
15
+ return await (await getCurrentAdapter(baseAdapter)).create({
15
16
  model: "jwks",
16
17
  data: {
17
18
  ...webKey,
@@ -1,6 +1,8 @@
1
1
  import { JWKOptions, JWSAlgorithms, Jwk, JwtOptions } from "./types.mjs";
2
2
  import { jwt } from "./index.mjs";
3
+ import { BetterAuthClientOptions, ClientFetchOption, ClientStore } from "@better-auth/core";
3
4
  import { JSONWebKeySet } from "jose";
5
+ import { BetterFetch } from "@better-fetch/fetch";
4
6
  //#region src/plugins/jwt/client.d.ts
5
7
  interface JwtClientOptions {
6
8
  jwks?: {
@@ -20,8 +22,8 @@ declare const jwtClient: (options?: JwtClientOptions) => {
20
22
  pathMethods: {
21
23
  [x: string]: "GET";
22
24
  };
23
- getActions: ($fetch: import("@better-fetch/fetch").BetterFetch) => {
24
- jwks: (fetchOptions?: any) => Promise<{
25
+ getActions: ($fetch: BetterFetch, _$store: ClientStore, _options: BetterAuthClientOptions | undefined) => {
26
+ jwks: (fetchOptions?: ClientFetchOption) => Promise<{
25
27
  data: null;
26
28
  error: {
27
29
  message?: string | undefined;
@@ -7,7 +7,7 @@ const jwtClient = (options) => {
7
7
  version: PACKAGE_VERSION,
8
8
  $InferServerPlugin: {},
9
9
  pathMethods: { [jwksPath]: "GET" },
10
- getActions: ($fetch) => ({ jwks: async (fetchOptions) => {
10
+ getActions: ($fetch, _$store, _options) => ({ jwks: async (fetchOptions) => {
11
11
  return await $fetch(jwksPath, {
12
12
  method: "GET",
13
13
  ...fetchOptions
@@ -10,6 +10,7 @@ import { parseGenericState } from "../../state.mjs";
10
10
  import { PACKAGE_VERSION } from "../../version.mjs";
11
11
  import { parseJSON } from "../../client/parser.mjs";
12
12
  import { checkSkipProxy, resolveCurrentURL, stripTrailingSlash } from "./utils.mjs";
13
+ import { safeJSONParse } from "@better-auth/core/utils/json";
13
14
  import { defu as defu$1 } from "defu";
14
15
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
15
16
  import * as z from "zod";
@@ -29,7 +30,8 @@ const oauthProxyQuerySchema = z.object({
29
30
  });
30
31
  const oauthCallbackQuerySchema = z.object({
31
32
  code: z.string().optional(),
32
- error: z.string().optional()
33
+ error: z.string().optional(),
34
+ user: z.string().optional()
33
35
  });
34
36
  const oAuthProxy = (opts) => {
35
37
  const maxAge = opts?.maxAge ?? 60;
@@ -170,7 +172,7 @@ const oAuthProxy = (opts) => {
170
172
  ctx.context.logger.warn("Invalid OAuth callback query", query.error);
171
173
  return;
172
174
  }
173
- const { code, error } = query.data;
175
+ const { code, error, user: userData } = query.data;
174
176
  let stateData;
175
177
  try {
176
178
  stateData = parseJSON(await symmetricDecrypt({
@@ -209,7 +211,15 @@ const oAuthProxy = (opts) => {
209
211
  throw redirectOnError(ctx, errorURL, "invalid_code");
210
212
  }
211
213
  if (!tokens) throw redirectOnError(ctx, errorURL, "invalid_code");
212
- const userInfo = (await provider.getUserInfo(tokens))?.user;
214
+ const parsedUserData = userData ? safeJSONParse(userData) : null;
215
+ const userInfo = (await provider.getUserInfo({
216
+ ...tokens,
217
+ /**
218
+ * The user object from the provider
219
+ * This is only available for some providers like Apple
220
+ */
221
+ user: parsedUserData ?? void 0
222
+ }))?.user;
213
223
  if (!userInfo) {
214
224
  ctx.context.logger.error("Unable to get user info from provider");
215
225
  throw redirectOnError(ctx, errorURL, "unable_to_get_user_info");
@@ -1,4 +1,5 @@
1
- import { ClientFetchOption } from "@better-auth/core";
1
+ import { BetterAuthClientOptions, ClientFetchOption, ClientStore } from "@better-auth/core";
2
+ import { BetterFetch } from "@better-fetch/fetch";
2
3
  //#region src/plugins/one-tap/client.d.ts
3
4
  declare global {
4
5
  interface Window {
@@ -162,10 +163,10 @@ declare const oneTapClient: (options: GoogleOneTapOptions) => {
162
163
  onResponse(ctx: import("@better-fetch/fetch").ResponseContext): Promise<void>;
163
164
  };
164
165
  }[];
165
- getActions: ($fetch: import("@better-fetch/fetch").BetterFetch, _: import("@better-auth/core").ClientStore) => {
166
+ getActions: ($fetch: BetterFetch, _$store: ClientStore, _options: BetterAuthClientOptions | undefined) => {
166
167
  oneTap: (opts?: GoogleOneTapActionOptions | undefined, fetchOptions?: ClientFetchOption | undefined) => Promise<void>;
167
168
  };
168
- getAtoms($fetch: import("@better-fetch/fetch").BetterFetch): {};
169
+ getAtoms($fetch: BetterFetch): {};
169
170
  };
170
171
  //#endregion
171
172
  export { GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, oneTapClient };
@@ -26,7 +26,7 @@ const oneTapClient = (options) => {
26
26
  navigator.credentials.preventSilentAccess();
27
27
  } }
28
28
  }],
29
- getActions: ($fetch, _) => {
29
+ getActions: ($fetch, _$store, _options) => {
30
30
  return { oneTap: async (opts, fetchOptions) => {
31
31
  if (isRequestInProgress) {
32
32
  console.warn("A Google One Tap request is already in progress. Please wait.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.6.25",
3
+ "version": "1.6.26",
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.17 || ^0.29.0",
490
490
  "nanostores": "^1.1.1",
491
491
  "zod": "^4.3.6",
492
- "@better-auth/core": "1.6.25",
493
- "@better-auth/drizzle-adapter": "1.6.25",
494
- "@better-auth/kysely-adapter": "1.6.25",
495
- "@better-auth/memory-adapter": "1.6.25",
496
- "@better-auth/mongo-adapter": "1.6.25",
497
- "@better-auth/prisma-adapter": "1.6.25",
498
- "@better-auth/telemetry": "1.6.25"
492
+ "@better-auth/core": "1.6.26",
493
+ "@better-auth/drizzle-adapter": "1.6.26",
494
+ "@better-auth/kysely-adapter": "1.6.26",
495
+ "@better-auth/memory-adapter": "1.6.26",
496
+ "@better-auth/mongo-adapter": "1.6.26",
497
+ "@better-auth/prisma-adapter": "1.6.26",
498
+ "@better-auth/telemetry": "1.6.26"
499
499
  },
500
500
  "devDependencies": {
501
501
  "@lynx-js/react": "^0.116.3",
502
- "@sveltejs/kit": "^2.60.1",
502
+ "@sveltejs/kit": "^2.70.1",
503
503
  "@tanstack/react-start": "^1.168.4",
504
504
  "@tanstack/solid-start": "^1.168.4",
505
505
  "@types/bun": "^1.3.9",
@@ -507,8 +507,8 @@
507
507
  "@types/pg": "^8.16.0",
508
508
  "@types/react": "^19.2.14",
509
509
  "@opentelemetry/api": "^1.9.0",
510
- "@opentelemetry/sdk-trace-base": "^1.30.0",
511
- "@opentelemetry/sdk-trace-node": "^1.30.0",
510
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
511
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
512
512
  "happy-dom": "^20.8.9",
513
513
  "listhen": "^1.9.0",
514
514
  "msw": "^2.12.10",
@@ -520,8 +520,8 @@
520
520
  "tsdown": "0.22.7",
521
521
  "type-fest": "^5.4.4",
522
522
  "typescript": "^6.0.3",
523
- "vite": "^7.3.2",
524
- "vitest": "^4.1.5",
523
+ "vite": "^8.1.5",
524
+ "vitest": "^4.1.10",
525
525
  "vue": "^3.5.29"
526
526
  },
527
527
  "peerDependencies": {