better-auth 1.7.16 → 1.7.17

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,11 +1,12 @@
1
1
  import { wildcardMatch } from "../utils/wildcard.mjs";
2
2
  import { getHost, getOrigin, getProtocol } from "../utils/url.mjs";
3
+ import * as z from "zod";
3
4
  //#region src/auth/trusted-origins.ts
4
5
  const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f-\u009f]/;
5
6
  const RELATIVE_URL_PARSER_ORIGIN = "https://better-auth.invalid";
6
7
  const ENCODED_PATH_SEPARATOR_PATTERN = /%2[fF]|%5[cC]/;
7
8
  const IP_WILDCARD_HOST_PATTERN = /^(?=.*\d)[\d*?.]+$/;
8
- const IPV4_HOST_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/;
9
+ const ipv4Schema = z.ipv4();
9
10
  /**
10
11
  * A wildcard host made only of digits, dots and wildcards (`192.168.*.*`,
11
12
  * `10.*`) names an address range, so it admits only an IPv4 address. A plain
@@ -13,9 +14,10 @@ const IPV4_HOST_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/;
13
14
  * hosts keep the documented subdomain matching.
14
15
  */
15
16
  const isAllowedIPv4Host = (patternAuthority, authority) => {
16
- const patternHost = patternAuthority.split(":")[0];
17
+ const [patternHost = ""] = patternAuthority.split(":");
17
18
  if (!IP_WILDCARD_HOST_PATTERN.test(patternHost)) return true;
18
- return IPV4_HOST_PATTERN.test(authority.split(":")[0]);
19
+ const [host = ""] = authority.split(":");
20
+ return z.validate(ipv4Schema, host);
19
21
  };
20
22
  /**
21
23
  * Resolves `.` and `..` segments in a path after percent-decoding so a
@@ -177,6 +177,7 @@ async function setSessionCookie(ctx, session, dontRememberMe, overrides) {
177
177
  ...overrides
178
178
  });
179
179
  if (dontRememberMe) await ctx.setSignedCookie(ctx.context.authCookies.dontRememberToken.name, "true", ctx.context.secret, ctx.context.authCookies.dontRememberToken.attributes);
180
+ else if (dontRememberMeCookie) expireCookie(ctx, ctx.context.authCookies.dontRememberToken);
180
181
  await setCookieCache(ctx, session, dontRememberMe);
181
182
  ctx.context.setNewSession(session);
182
183
  }
@@ -104,10 +104,9 @@ const createInternalAdapter = (adapter, ctx) => {
104
104
  }
105
105
  await secondaryStorage.delete(activeSessionsKey);
106
106
  }
107
- async function queueCachedUserSessionDeletion(userId, sessionReferences) {
107
+ async function queueCachedUserSessionDeletion(userId, sessionReferences, revokedAt = Date.now()) {
108
108
  if (!secondaryStorage) return;
109
109
  const references = sessionReferences ?? await getActiveSessionReferences(userId);
110
- const revokedAt = Date.now();
111
110
  await queueAfterTransactionHook(() => deleteCachedUserSessions(userId, references, revokedAt), { onError(error) {
112
111
  logger.error("Failed to delete committed user sessions from secondary storage", error);
113
112
  } });
@@ -532,24 +531,25 @@ const createInternalAdapter = (adapter, ctx) => {
532
531
  }], "account", void 0);
533
532
  },
534
533
  deleteUserSessions: async (userId) => {
534
+ const revokedAt = Date.now();
535
535
  const sessionReferences = await getActiveSessionReferences(userId);
536
536
  if (secondaryStorage) {
537
537
  if (!options.session?.storeSessionInDatabase) {
538
- await queueCachedUserSessionDeletion(userId, sessionReferences);
538
+ await queueCachedUserSessionDeletion(userId, sessionReferences, revokedAt);
539
539
  return;
540
540
  }
541
541
  if (ctx.options.session?.preserveSessionInDatabase) {
542
542
  if (await endPreservedSessions([{
543
543
  field: "userId",
544
544
  value: userId
545
- }]) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
545
+ }]) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences, revokedAt);
546
546
  return;
547
547
  }
548
548
  }
549
549
  if (await deleteManyWithHooks([{
550
550
  field: "userId",
551
551
  value: userId
552
- }], "session", void 0) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
552
+ }], "session", void 0) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences, revokedAt);
553
553
  },
554
554
  deleteSessions: async (sessionTokens) => {
555
555
  if (secondaryStorage) {
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.7.16";
2
+ var version = "1.7.17";
3
3
  //#endregion
4
4
  export { version };
@@ -1,6 +1,6 @@
1
1
  import { ms } from "../../utils/time.mjs";
2
2
  import { generateRandomString } from "../../crypto/random.mjs";
3
- import { getAuthoritativeSessionFromCtx, getSessionFromCtx } from "../../api/routes/session.mjs";
3
+ import { getAuthoritativeSessionFromCtx } 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";
6
6
  import { createAuthEndpoint } from "@better-auth/core/api";
@@ -651,7 +651,7 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
651
651
  } }
652
652
  } }
653
653
  }, async (ctx) => {
654
- const session = await getSessionFromCtx(ctx);
654
+ const session = await getAuthoritativeSessionFromCtx(ctx);
655
655
  if (!session) throw new APIError("UNAUTHORIZED", {
656
656
  error: "unauthorized",
657
657
  error_description: DEVICE_AUTHORIZATION_ERROR_CODES.AUTHENTICATION_REQUIRED.message
@@ -22,7 +22,7 @@ const phoneNumber = (options) => {
22
22
  init() {
23
23
  return { options: { databaseHooks: { user: {
24
24
  create: { async before(data, ctx) {
25
- if (!data[opts.phoneNumber] || data[opts.phoneNumberVerified] === true) return;
25
+ if (data[opts.phoneNumber] == null || data[opts.phoneNumberVerified] === true) return;
26
26
  if (ctx?.body?.[opts.phoneNumber]) throw APIError.from("BAD_REQUEST", PHONE_NUMBER_ERROR_CODES.PHONE_NUMBER_CANNOT_BE_SET);
27
27
  return { data: {
28
28
  ...data,
@@ -35,7 +35,7 @@ const phoneNumber = (options) => {
35
35
  ...data,
36
36
  [opts.phoneNumberVerified]: false
37
37
  } };
38
- if (!ctx || !data[opts.phoneNumber] || data[opts.phoneNumberVerified] === true || ctx.path === "/admin/update-user") return;
38
+ if (!ctx || data[opts.phoneNumber] == null || data[opts.phoneNumberVerified] === true || ctx.path === "/admin/update-user") return;
39
39
  return { data: {
40
40
  ...data,
41
41
  [opts.phoneNumber]: void 0
@@ -170,7 +170,7 @@ export declare const backupCode2fa: (opts: BackupCodeOptions) => {
170
170
  };
171
171
  }, {
172
172
  token: string | undefined;
173
- user: (Record<string, any> & {
173
+ user: ({
174
174
  id: string;
175
175
  createdAt: Date;
176
176
  updatedAt: Date;
@@ -178,7 +178,7 @@ export declare const backupCode2fa: (opts: BackupCodeOptions) => {
178
178
  emailVerified: boolean;
179
179
  name: string;
180
180
  image?: string | null | undefined;
181
- }) | UserWithTwoFactor;
181
+ } & Record<string, any>) | UserWithTwoFactor;
182
182
  }>;
183
183
  /**
184
184
  * ### Endpoint
@@ -295,7 +295,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
295
295
  };
296
296
  }, {
297
297
  token: string | undefined;
298
- user: (Record<string, any> & {
298
+ user: ({
299
299
  id: string;
300
300
  createdAt: Date;
301
301
  updatedAt: Date;
@@ -303,7 +303,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
303
303
  emailVerified: boolean;
304
304
  name: string;
305
305
  image?: string | null | undefined;
306
- }) | UserWithTwoFactor;
306
+ } & Record<string, any>) | UserWithTwoFactor;
307
307
  }>;
308
308
  generateBackupCodes: import("better-call").StrictEndpoint<"/two-factor/generate-backup-codes", {
309
309
  method: "POST";
@@ -490,7 +490,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
490
490
  user: UserWithTwoFactor;
491
491
  } | {
492
492
  token: string;
493
- user: Record<string, any> & {
493
+ user: {
494
494
  id: string;
495
495
  createdAt: Date;
496
496
  updatedAt: Date;
@@ -498,7 +498,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
498
498
  emailVerified: boolean;
499
499
  name: string;
500
500
  image?: string | null | undefined;
501
- };
501
+ } & Record<string, any>;
502
502
  }>;
503
503
  generateTOTP: import("better-call").StrictEndpoint<string, {
504
504
  method: "POST";
@@ -621,7 +621,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
621
621
  user: UserWithTwoFactor;
622
622
  } | {
623
623
  token: string;
624
- user: Record<string, any> & {
624
+ user: {
625
625
  id: string;
626
626
  createdAt: Date;
627
627
  updatedAt: Date;
@@ -629,7 +629,7 @@ export declare const twoFactor: <O extends TwoFactorOptions>(options?: O) => {
629
629
  emailVerified: boolean;
630
630
  name: string;
631
631
  image?: string | null | undefined;
632
- };
632
+ } & Record<string, any>;
633
633
  }>;
634
634
  };
635
635
  options: NoInfer<O>;
@@ -27,7 +27,7 @@ const signInPaths = /* @__PURE__ */ new Set([
27
27
  const signInTwoFactorStates = /* @__PURE__ */ new WeakMap();
28
28
  async function readSignInTwoFactorState(ctx, trustDeviceMaxAge) {
29
29
  const data = ctx.context.newSession;
30
- if (!signInPaths.has(ctx.path) || !data?.user.twoFactorEnabled) return {
30
+ if (!data?.user.twoFactorEnabled) return {
31
31
  requiresTwoFactor: false,
32
32
  trustDeviceCookie: null
33
33
  };
@@ -90,6 +90,7 @@ const twoFactor = (options) => {
90
90
  return state;
91
91
  }
92
92
  async function shouldRequireTwoFactor(ctx) {
93
+ if (!signInPaths.has(ctx.path)) return false;
93
94
  return (await getSignInTwoFactorState(ctx)).requiresTwoFactor;
94
95
  }
95
96
  return {
@@ -198,7 +198,7 @@ export declare const otp2fa: (options?: OTPOptions | undefined) => {
198
198
  user: UserWithTwoFactor;
199
199
  } | {
200
200
  token: string;
201
- user: Record<string, any> & {
201
+ user: {
202
202
  id: string;
203
203
  createdAt: Date;
204
204
  updatedAt: Date;
@@ -206,7 +206,7 @@ export declare const otp2fa: (options?: OTPOptions | undefined) => {
206
206
  emailVerified: boolean;
207
207
  name: string;
208
208
  image?: string | null | undefined;
209
- };
209
+ } & Record<string, any>;
210
210
  }>;
211
211
  };
212
212
  };
@@ -201,7 +201,7 @@ export declare const totp2fa: (options?: TOTPOptions | undefined) => {
201
201
  user: UserWithTwoFactor;
202
202
  } | {
203
203
  token: string;
204
- user: Record<string, any> & {
204
+ user: {
205
205
  id: string;
206
206
  createdAt: Date;
207
207
  updatedAt: Date;
@@ -209,7 +209,7 @@ export declare const totp2fa: (options?: TOTPOptions | undefined) => {
209
209
  emailVerified: boolean;
210
210
  name: string;
211
211
  image?: string | null | undefined;
212
- };
212
+ } & Record<string, any>;
213
213
  }>;
214
214
  };
215
215
  };
@@ -2,6 +2,7 @@ import { parseUserOutput } from "../../db/schema.mjs";
2
2
  import { generateRandomString } from "../../crypto/random.mjs";
3
3
  import { expireCookie, setSessionCookie } from "../../cookies/index.mjs";
4
4
  import { getAuthoritativeSessionFromCtx } from "../../api/routes/session.mjs";
5
+ import { isSessionOutsideStore } from "../../api/state/session-source.mjs";
5
6
  import { TWO_FACTOR_ERROR_CODES } from "./error-code.mjs";
6
7
  import { TRUST_DEVICE_COOKIE_NAME, TWO_FACTOR_COOKIE_NAME } from "./constant.mjs";
7
8
  import { APIError } from "@better-auth/core/error";
@@ -11,7 +12,8 @@ async function verifyTwoFactor(ctx) {
11
12
  const invalid = (errorKey) => {
12
13
  throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES[errorKey]);
13
14
  };
14
- const session = await getAuthoritativeSessionFromCtx(ctx);
15
+ const requestSession = ctx.context.session;
16
+ const session = requestSession && await isSessionOutsideStore(requestSession.session.token) ? requestSession : await getAuthoritativeSessionFromCtx(ctx);
15
17
  if (!session) {
16
18
  const twoFactorCookie = ctx.context.createAuthCookie(TWO_FACTOR_COOKIE_NAME);
17
19
  const signedTwoFactorCookie = await ctx.getSignedCookie(twoFactorCookie.name, ctx.context.secret);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.7.16",
3
+ "version": "1.7.17",
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.16",
507
- "@better-auth/drizzle-adapter": "1.7.16",
508
- "@better-auth/kysely-adapter": "1.7.16",
509
- "@better-auth/memory-adapter": "1.7.16",
510
- "@better-auth/mongo-adapter": "1.7.16",
511
- "@better-auth/prisma-adapter": "1.7.16",
512
- "@better-auth/telemetry": "1.7.16"
506
+ "@better-auth/core": "1.7.17",
507
+ "@better-auth/drizzle-adapter": "1.7.17",
508
+ "@better-auth/kysely-adapter": "1.7.17",
509
+ "@better-auth/memory-adapter": "1.7.17",
510
+ "@better-auth/mongo-adapter": "1.7.17",
511
+ "@better-auth/prisma-adapter": "1.7.17",
512
+ "@better-auth/telemetry": "1.7.17"
513
513
  },
514
514
  "devDependencies": {
515
515
  "@lynx-js/react": "^0.121.2",
@@ -529,8 +529,8 @@
529
529
  "msw": "^2.14.6",
530
530
  "next": "^16.4.2",
531
531
  "oauth2-mock-server": "^9.0.0",
532
- "react": "^19.2.7",
533
- "react-dom": "^19.2.7",
532
+ "react": "^19.3.1",
533
+ "react-dom": "^19.3.1",
534
534
  "solid-js": "^1.9.11",
535
535
  "tsdown": "0.23.1",
536
536
  "type-fest": "^5.7.0",