better-auth 1.8.0-beta.5 → 1.8.0-beta.6

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
  }
@@ -38,7 +38,7 @@ const createInternalAdapter = (adapter, ctx) => {
38
38
  * or `deleteUserSessions` sweeping a user's accumulated preserved rows) would
39
39
  * re-match it and re-fire the hooks, re-dispatching back-channel logout.
40
40
  */
41
- const endPreservedSessions = (where) => {
41
+ const endPreservedSessions = (where, afterEnd) => {
42
42
  const liveSessions = [...where, {
43
43
  field: "expiresAt",
44
44
  value: /* @__PURE__ */ new Date(),
@@ -51,6 +51,7 @@ const createInternalAdapter = (adapter, ctx) => {
51
51
  where: liveSessions,
52
52
  update: { expiresAt: /* @__PURE__ */ new Date() }
53
53
  });
54
+ await afterEnd?.();
54
55
  },
55
56
  executeMainFn: false
56
57
  });
@@ -91,7 +92,7 @@ const createInternalAdapter = (adapter, ctx) => {
91
92
  const previousRevokedBefore = Number(await secondaryStorage.get(revokedBeforeKey(userId)));
92
93
  await secondaryStorage.set(revokedBeforeKey(userId), String(Math.max(revokedAt, previousRevokedBefore || 0)), revokedBeforeTTL);
93
94
  const deletedTokens = new Set(sessionReferences.map((session) => session.token));
94
- for (const { token } of sessionReferences) await secondaryStorage.delete(token);
95
+ for (const token of deletedTokens) await secondaryStorage.delete(token);
95
96
  const activeSessionsKey = `active-sessions-${userId}`;
96
97
  const currentSessionReferences = await getActiveSessionReferences(userId);
97
98
  const now = Date.now();
@@ -533,23 +534,39 @@ const createInternalAdapter = (adapter, ctx) => {
533
534
  },
534
535
  deleteUserSessions: async (userId) => {
535
536
  const sessionReferences = await getActiveSessionReferences(userId);
536
- if (secondaryStorage) {
537
- if (!options.session?.storeSessionInDatabase) {
538
- await queueCachedUserSessionDeletion(userId, sessionReferences);
539
- return;
540
- }
541
- if (ctx.options.session?.preserveSessionInDatabase) {
542
- if (await endPreservedSessions([{
543
- field: "userId",
544
- value: userId
545
- }]) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
546
- return;
547
- }
537
+ if (!secondaryStorage) {
538
+ await deleteManyWithHooks([{
539
+ field: "userId",
540
+ value: userId
541
+ }], "session", void 0);
542
+ return;
543
+ }
544
+ if (!options.session?.storeSessionInDatabase) {
545
+ await queueCachedUserSessionDeletion(userId, sessionReferences);
546
+ return;
547
+ }
548
+ const revokeCachedSessions = async () => queueCachedUserSessionDeletion(userId, [...sessionReferences, ...await getActiveSessionReferences(userId)]);
549
+ if (ctx.options.session?.preserveSessionInDatabase) {
550
+ await endPreservedSessions([{
551
+ field: "userId",
552
+ value: userId
553
+ }], revokeCachedSessions);
554
+ return;
548
555
  }
549
- if (await deleteManyWithHooks([{
556
+ await deleteManyWithHooks([{
550
557
  field: "userId",
551
558
  value: userId
552
- }], "session", void 0) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
559
+ }], "session", {
560
+ fn: async (where) => {
561
+ const deleted = await (await getCurrentAdapter(adapter)).deleteMany({
562
+ model: "session",
563
+ where
564
+ });
565
+ await revokeCachedSessions();
566
+ return deleted;
567
+ },
568
+ executeMainFn: false
569
+ });
553
570
  },
554
571
  deleteSessions: async (sessionTokens) => {
555
572
  if (secondaryStorage) {
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.8.0-beta.5";
2
+ var version = "1.8.0-beta.6";
3
3
  //#endregion
4
4
  export { version };
@@ -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
@@ -32,6 +32,8 @@ const testPassword = {
32
32
  async function getTestInstance(options, config) {
33
33
  const testWith = config?.testWith || "sqlite";
34
34
  const postgresSchema = testWith === "postgres" ? `ba_test_${randomUUID().replaceAll("-", "_")}` : void 0;
35
+ const mysqlServerURL = "mysql://root:root_password@127.0.0.1:3306";
36
+ const mysqlDatabase = testWith === "mysql" ? `ba_test_${randomUUID().replaceAll("-", "_")}` : void 0;
35
37
  const quotePostgresIdentifier = (identifier) => `"${identifier.replaceAll("\"", "\"\"")}"`;
36
38
  async function getPostgres() {
37
39
  const { Kysely, PostgresDialect } = await import("kysely");
@@ -49,8 +51,14 @@ async function getTestInstance(options, config) {
49
51
  }
50
52
  async function getMysql() {
51
53
  const { Kysely, MysqlDialect } = await import("kysely");
52
- const { createPool } = await import("mysql2/promise");
53
- return new Kysely({ dialect: new MysqlDialect({ pool: createPool("mysql://user:password@localhost:3306/better_auth") }) });
54
+ const { createConnection, createPool } = await import("mysql2");
55
+ const admin = createConnection(mysqlServerURL).promise();
56
+ try {
57
+ await admin.query(`CREATE DATABASE \`${mysqlDatabase}\``);
58
+ } finally {
59
+ await admin.end();
60
+ }
61
+ return new Kysely({ dialect: new MysqlDialect({ pool: createPool(`${mysqlServerURL}/${mysqlDatabase}`) }) });
54
62
  }
55
63
  async function mongodbClient() {
56
64
  const { MongoClient } = await import("mongodb");
@@ -64,6 +72,9 @@ async function getTestInstance(options, config) {
64
72
  };
65
73
  return await dbClient("mongodb://127.0.0.1:27017", "better-auth");
66
74
  }
75
+ const postgres = testWith === "postgres" ? await getPostgres() : void 0;
76
+ const mysql = testWith === "mysql" ? await getMysql() : void 0;
77
+ const sqlite = testWith === "sqlite" ? await getSqlite() : void 0;
67
78
  const opts = {
68
79
  socialProviders: {
69
80
  github: {
@@ -76,20 +87,45 @@ async function getTestInstance(options, config) {
76
87
  }
77
88
  },
78
89
  secret: "better-auth-secret-that-is-long-enough-for-validation-test",
79
- database: testWith === "postgres" ? {
80
- db: await getPostgres(),
90
+ database: postgres ? {
91
+ db: postgres,
81
92
  type: "postgres",
82
93
  transaction: config?.transaction
83
- } : testWith === "mongodb" ? await Promise.all([mongodbClient(), await import("../adapters/mongodb-adapter/index.mjs")]).then(([db, { mongodbAdapter }]) => mongodbAdapter(db)) : testWith === "mysql" ? {
84
- db: await getMysql(),
94
+ } : testWith === "mongodb" ? await Promise.all([mongodbClient(), await import("../adapters/mongodb-adapter/index.mjs")]).then(([db, { mongodbAdapter }]) => mongodbAdapter(db)) : mysql ? {
95
+ db: mysql,
85
96
  type: "mysql",
86
97
  transaction: config?.transaction
87
- } : await getSqlite(),
98
+ } : sqlite,
88
99
  emailAndPassword: { enabled: true },
89
100
  rateLimit: { enabled: false },
90
101
  advanced: { cookies: {} },
91
102
  logger: { level: "debug" }
92
103
  };
104
+ const cleanup = async () => {
105
+ if (testWith === "mongodb") {
106
+ await (await mongodbClient()).dropDatabase();
107
+ return;
108
+ }
109
+ if (postgres) {
110
+ try {
111
+ if (postgresSchema) await sql.raw(`DROP SCHEMA IF EXISTS ${quotePostgresIdentifier(postgresSchema)} CASCADE`).execute(postgres);
112
+ else await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(postgres);
113
+ } finally {
114
+ await postgres.destroy();
115
+ }
116
+ return;
117
+ }
118
+ if (mysql) {
119
+ try {
120
+ await sql.raw(`DROP DATABASE IF EXISTS \`${mysqlDatabase}\``).execute(mysql);
121
+ } finally {
122
+ await mysql.destroy();
123
+ }
124
+ return;
125
+ }
126
+ if (sqlite) sqlite.close();
127
+ };
128
+ cleanupSet.add(cleanup);
93
129
  const authOptions = {
94
130
  baseURL: "http://localhost:" + (config?.port || 3e3),
95
131
  ...opts,
@@ -126,32 +162,6 @@ async function getTestInstance(options, config) {
126
162
  });
127
163
  }
128
164
  await createTestUser();
129
- const cleanup = async () => {
130
- if (testWith === "mongodb") {
131
- await (await mongodbClient()).dropDatabase();
132
- return;
133
- }
134
- if (testWith === "postgres") {
135
- const postgres = await getPostgres();
136
- if (postgresSchema) await sql.raw(`DROP SCHEMA IF EXISTS ${quotePostgresIdentifier(postgresSchema)} CASCADE`).execute(postgres);
137
- else await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(postgres);
138
- await postgres.destroy();
139
- return;
140
- }
141
- if (testWith === "mysql") {
142
- const mysql = await getMysql();
143
- await sql`SET FOREIGN_KEY_CHECKS = 0;`.execute(mysql);
144
- const tables = await mysql.introspection.getTables();
145
- for (const table of tables) await mysql.deleteFrom(table.name).execute();
146
- await sql`SET FOREIGN_KEY_CHECKS = 1;`.execute(mysql);
147
- return;
148
- }
149
- if (testWith === "sqlite") {
150
- (await getSqlite()).close();
151
- return;
152
- }
153
- };
154
- cleanupSet.add(cleanup);
155
165
  const customFetchImpl = async (url, init) => {
156
166
  const headers = init?.headers || {};
157
167
  const storageHeaders = currentUserContextStorage.getStore()?.headers;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.8.0-beta.5",
3
+ "version": "1.8.0-beta.6",
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.8.0-beta.5",
507
- "@better-auth/drizzle-adapter": "1.8.0-beta.5",
508
- "@better-auth/kysely-adapter": "1.8.0-beta.5",
509
- "@better-auth/memory-adapter": "1.8.0-beta.5",
510
- "@better-auth/mongo-adapter": "1.8.0-beta.5",
511
- "@better-auth/prisma-adapter": "1.8.0-beta.5",
512
- "@better-auth/telemetry": "1.8.0-beta.5"
506
+ "@better-auth/core": "1.8.0-beta.6",
507
+ "@better-auth/drizzle-adapter": "1.8.0-beta.6",
508
+ "@better-auth/kysely-adapter": "1.8.0-beta.6",
509
+ "@better-auth/memory-adapter": "1.8.0-beta.6",
510
+ "@better-auth/mongo-adapter": "1.8.0-beta.6",
511
+ "@better-auth/prisma-adapter": "1.8.0-beta.6",
512
+ "@better-auth/telemetry": "1.8.0-beta.6"
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",