better-auth 1.6.18 → 1.6.19

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,19 +1,30 @@
1
1
  import { symmetricDecodeJWT, symmetricEncodeJWT } from "../crypto/jwt.mjs";
2
2
  import { parseCookies } from "./cookie-utils.mjs";
3
3
  import { safeJSONParse } from "@better-auth/core/utils/json";
4
+ import { serializeCookie } from "better-call";
4
5
  import * as z from "zod";
5
6
  //#region src/cookies/session-store.ts
6
- const ALLOWED_COOKIE_SIZE = 4096;
7
- const ESTIMATED_EMPTY_COOKIE_SIZE = 200;
8
- const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
9
7
  /**
10
- * Extract the chunk index from a cookie name
8
+ * Per-cookie byte ceiling.
9
+ * Safari's ~4093 floor is the lowest among browsers.
10
+ * Kept a little under it for attributes added after sizing.
11
+ *
12
+ * @see https://datatracker.ietf.org/doc/html/rfc6265#section-6.1
13
+ * @see https://github.com/dotnet/aspnetcore/blob/aa5493528640932601bb82ef3295e4d8ca7e11c5/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs#L40
14
+ */
15
+ const MAX_COOKIE_SIZE = 4050;
16
+ /**
17
+ * Max chunks per cookie.
18
+ * A larger value does not belong in a cookie.
11
19
  */
12
- function getChunkIndex(cookieName) {
13
- const parts = cookieName.split(".");
14
- const lastPart = parts[parts.length - 1];
15
- const index = parseInt(lastPart || "0", 10);
16
- return isNaN(index) ? 0 : index;
20
+ const MAX_COOKIE_CHUNKS = 100;
21
+ /**
22
+ * Largest value that keeps the serialized cookie within {@link MAX_COOKIE_SIZE},
23
+ * measured with the real `serializeCookie` writer so it stays in sync with the
24
+ * wire. Non-positive when the name and attributes alone overflow.
25
+ */
26
+ function getMaxCookieValueSize(name, options) {
27
+ return MAX_COOKIE_SIZE - serializeCookie(name, "", { ...options }).length;
17
28
  }
18
29
  /**
19
30
  * Read all existing chunks from cookies
@@ -25,27 +36,24 @@ function readExistingChunks(cookieName, ctx) {
25
36
  return chunks;
26
37
  }
27
38
  /**
28
- * Get the full session data by joining all chunks
29
- */
30
- function joinChunks(chunks) {
31
- return Object.keys(chunks).sort((a, b) => {
32
- return getChunkIndex(a) - getChunkIndex(b);
33
- }).map((key) => chunks[key]).join("");
34
- }
35
- /**
36
39
  * Split a cookie value into chunks if needed
37
40
  */
38
41
  function chunkCookie(storeName, cookie, chunks, logger) {
39
- const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE);
40
- if (chunkCount === 1) {
42
+ const chunkSize = getMaxCookieValueSize(`${cookie.name}.${MAX_COOKIE_CHUNKS - 1}`, cookie.attributes);
43
+ const chunkCount = chunkSize > 0 ? Math.ceil(cookie.value.length / chunkSize) : Infinity;
44
+ if (chunkCount <= 1) {
41
45
  chunks[cookie.name] = cookie.value;
42
46
  return [cookie];
43
47
  }
48
+ if (chunkCount > MAX_COOKIE_CHUNKS) {
49
+ logger.warn(`${storeName} cookie is too large to store even after chunking, so the cache was skipped. Reduce the cached data or use a database session.`);
50
+ return [];
51
+ }
44
52
  const cookies = [];
45
53
  for (let i = 0; i < chunkCount; i++) {
46
54
  const name = `${cookie.name}.${i}`;
47
- const start = i * CHUNK_SIZE;
48
- const value = cookie.value.substring(start, start + CHUNK_SIZE);
55
+ const start = i * chunkSize;
56
+ const value = cookie.value.substring(start, start + chunkSize);
49
57
  cookies.push({
50
58
  ...cookie,
51
59
  name,
@@ -54,11 +62,10 @@ function chunkCookie(storeName, cookie, chunks, logger) {
54
62
  chunks[name] = value;
55
63
  }
56
64
  logger.debug(`CHUNKING_${storeName.toUpperCase()}_COOKIE`, {
57
- message: `${storeName} cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`,
58
- emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE,
65
+ message: `${storeName} cookie exceeds the ${MAX_COOKIE_SIZE} byte limit and was split into ${chunkCount} chunks.`,
59
66
  valueSize: cookie.value.length,
60
67
  chunkCount,
61
- chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE)
68
+ chunkSizes: cookies.map((c) => c.value.length)
62
69
  });
63
70
  return cookies;
64
71
  }
@@ -78,26 +85,22 @@ function getCleanCookies(chunks, cookieOptions) {
78
85
  return cleanedChunks;
79
86
  }
80
87
  /**
81
- * Create a session store for handling cookie chunking.
82
- * When session data exceeds 4KB, it automatically splits it into multiple cookies.
88
+ * Store that splits a cookie into numbered chunks when its serialized form
89
+ * would exceed the per-cookie byte limit, expiring stale chunks as needed.
83
90
  *
84
- * Based on next-auth's SessionStore implementation.
85
91
  * @see https://github.com/nextauthjs/next-auth/blob/27b2519b84b8eb9cf053775dea29d577d2aa0098/packages/next-auth/src/core/lib/cookie.ts
86
92
  */
87
93
  const storeFactory = (storeName) => (cookieName, cookieOptions, ctx) => {
88
94
  const chunks = readExistingChunks(cookieName, ctx);
89
95
  const logger = ctx.context.logger;
96
+ const expireExistingChunks = () => {
97
+ const expired = getCleanCookies(chunks, cookieOptions);
98
+ for (const name in chunks) delete chunks[name];
99
+ return expired;
100
+ };
90
101
  return {
91
- getValue() {
92
- return joinChunks(chunks);
93
- },
94
- hasChunks() {
95
- return Object.keys(chunks).length > 0;
96
- },
97
102
  chunk(value, options) {
98
- const cleanedChunks = getCleanCookies(chunks, cookieOptions);
99
- for (const name in chunks) delete chunks[name];
100
- const cookies = cleanedChunks;
103
+ const cookies = expireExistingChunks();
101
104
  const chunked = chunkCookie(storeName, {
102
105
  name: cookieName,
103
106
  value,
@@ -110,9 +113,7 @@ const storeFactory = (storeName) => (cookieName, cookieOptions, ctx) => {
110
113
  return Object.values(cookies);
111
114
  },
112
115
  clean() {
113
- const cleanedChunks = getCleanCookies(chunks, cookieOptions);
114
- for (const name in chunks) delete chunks[name];
115
- return Object.values(cleanedChunks);
116
+ return Object.values(expireExistingChunks());
116
117
  },
117
118
  setCookies(cookies) {
118
119
  for (const cookie of cookies) ctx.setCookie(cookie.name, cookie.value, cookie.attributes);
@@ -148,18 +149,8 @@ async function setAccountCookie(c, accountData) {
148
149
  ...accountDataCookie.attributes
149
150
  };
150
151
  const data = await symmetricEncodeJWT(accountData, c.context.secretConfig, "better-auth-account", options.maxAge);
151
- if (data.length > ALLOWED_COOKIE_SIZE) {
152
- const accountStore = createAccountStore(accountDataCookie.name, options, c);
153
- const cookies = accountStore.chunk(data, options);
154
- accountStore.setCookies(cookies);
155
- } else {
156
- const accountStore = createAccountStore(accountDataCookie.name, options, c);
157
- if (accountStore.hasChunks()) {
158
- const cleanCookies = accountStore.clean();
159
- accountStore.setCookies(cleanCookies);
160
- }
161
- c.setCookie(accountDataCookie.name, data, options);
162
- }
152
+ const accountStore = createAccountStore(accountDataCookie.name, options, c);
153
+ accountStore.setCookies(accountStore.chunk(data, options));
163
154
  }
164
155
  async function getAccountCookie(c) {
165
156
  const accountCookie = getChunkedCookie(c, c.context.authCookies.accountData.name);
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.6.18";
2
+ var version = "1.6.19";
3
3
  //#endregion
4
4
  export { version };
@@ -104,6 +104,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
104
104
  method: "POST";
105
105
  body: z.ZodObject<{
106
106
  client_id: z.ZodString;
107
+ user_id: z.ZodOptional<z.ZodString>;
107
108
  scope: z.ZodOptional<z.ZodString>;
108
109
  }, z.core.$strip>;
109
110
  error: z.ZodObject<{
@@ -9,6 +9,7 @@ import * as z from "zod";
9
9
  const defaultCharset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
10
10
  const deviceCodeBodySchema = z.object({
11
11
  client_id: z.string().meta({ description: "The client ID of the application" }),
12
+ user_id: z.string().meta({ description: "The user ID to which the device code should be pre-bound." }).optional(),
12
13
  scope: z.string().meta({ description: "Space-separated list of scopes" }).optional()
13
14
  });
14
15
  const deviceCodeErrorSchema = z.object({
@@ -99,7 +100,7 @@ Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#secti
99
100
  data: {
100
101
  deviceCode,
101
102
  userCode,
102
- userId: null,
103
+ userId: ctx.body.user_id || null,
103
104
  expiresAt,
104
105
  status: "pending",
105
106
  pollingInterval: ms(opts.interval),
@@ -341,7 +342,7 @@ const deviceVerify = createAuthEndpoint("/device", {
341
342
  });
342
343
  const session = await getSessionFromCtx(ctx);
343
344
  if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
344
- if (await ctx.context.adapter.update({
345
+ if (await ctx.context.adapter.incrementOne({
345
346
  model: "deviceCode",
346
347
  where: [
347
348
  {
@@ -358,7 +359,8 @@ const deviceVerify = createAuthEndpoint("/device", {
358
359
  value: null
359
360
  }
360
361
  ],
361
- update: { userId: session.user.id }
362
+ increment: {},
363
+ set: { userId: session.user.id }
362
364
  })) deviceCodeRecord.userId = session.user.id;
363
365
  }
364
366
  return ctx.json({
@@ -8,6 +8,16 @@ interface LastLoginMethodClientConfig {
8
8
  * @default "better-auth.last_used_login_method"
9
9
  */
10
10
  cookieName?: string | undefined;
11
+ /**
12
+ * Domain for the cookie. Required when using cross-subdomain cookies
13
+ * so the client can properly clear the cookie set by the server.
14
+ *
15
+ * Should match the `domain` value in your server's
16
+ * `crossSubDomainCookies` configuration.
17
+ *
18
+ * @example ".example.com"
19
+ */
20
+ domain?: string | undefined;
11
21
  }
12
22
  /**
13
23
  * Client-side plugin to retrieve the last used login method
@@ -19,7 +19,10 @@ const lastLoginMethodClient = (config = {}) => {
19
19
  return getCookieValue(cookieName);
20
20
  },
21
21
  clearLastUsedLoginMethod: () => {
22
- if (typeof document !== "undefined") document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
22
+ if (typeof document !== "undefined") {
23
+ const domainPart = config.domain ? ` domain=${config.domain};` : "";
24
+ document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;${domainPart}`;
25
+ }
23
26
  },
24
27
  isLastUsedLoginMethod: (method) => {
25
28
  return getCookieValue(cookieName) === method;
@@ -3,7 +3,7 @@ import { generateRandomString } from "../../crypto/random.mjs";
3
3
  import { expireCookie } from "../../cookies/index.mjs";
4
4
  import { getAwaitableValue } from "../../context/helpers.mjs";
5
5
  import { setOAuthState } from "../../api/state/oauth.mjs";
6
- import { generateGenericState } from "../../state.mjs";
6
+ import { INTERNAL_STATE_KEYS, generateGenericState } from "../../state.mjs";
7
7
  import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
8
8
  import { PACKAGE_VERSION } from "../../version.mjs";
9
9
  import { OAUTH_POPUP_DATA_ELEMENT_ID, OAUTH_POPUP_MESSAGE_TYPE, POPUP_MARKER_COOKIE } from "./constants.mjs";
@@ -132,8 +132,9 @@ const oauthPopupStart = createAuthEndpoint("/oauth-popup/start", {
132
132
  let url;
133
133
  try {
134
134
  const codeVerifier = generateRandomString(128);
135
+ const parsedAdditionalData = c.query.additionalData ? safeJSONParse(c.query.additionalData) ?? {} : {};
135
136
  const stateData = {
136
- ...c.query.additionalData ? safeJSONParse(c.query.additionalData) ?? {} : {},
137
+ ...Object.fromEntries(Object.entries(parsedAdditionalData).filter(([key]) => !INTERNAL_STATE_KEYS.has(key))),
137
138
  callbackURL,
138
139
  codeVerifier,
139
140
  errorURL: c.query.errorCallbackURL,
@@ -72,7 +72,7 @@ type OpenAPIOperation = {
72
72
  };
73
73
  type FieldSchema = {
74
74
  type: DBFieldType;
75
- default?: (DBFieldAttributeConfig["defaultValue"] | "Generated at runtime") | undefined;
75
+ default?: DBFieldAttributeConfig["defaultValue"] | undefined;
76
76
  readOnly?: boolean | undefined;
77
77
  format?: string;
78
78
  };
@@ -1,7 +1,6 @@
1
1
  import { db_exports } from "../../db/index.mjs";
2
2
  import { getEndpoints } from "../../api/index.mjs";
3
3
  import * as z from "zod";
4
- import { toPascalCase } from "@better-auth/core/utils/string";
5
4
  //#region src/plugins/open-api/generator.ts
6
5
  const OPEN_API_SCHEMA_TYPES = new Set([
7
6
  "string",
@@ -20,7 +19,9 @@ function getFieldSchema(field) {
20
19
  type: field.type === "date" ? "string" : field.type,
21
20
  ...field.type === "date" && { format: "date-time" }
22
21
  };
23
- if (field.defaultValue !== void 0) schema.default = typeof field.defaultValue === "function" ? "Generated at runtime" : field.defaultValue;
22
+ if (field.defaultValue !== void 0) {
23
+ if (typeof field.defaultValue !== "function") schema.default = field.defaultValue;
24
+ }
24
25
  if (field.input === false) schema.readOnly = true;
25
26
  return schema;
26
27
  }
@@ -68,11 +69,8 @@ function getZodPipeSchema(zodType) {
68
69
  }
69
70
  function getParameters(options) {
70
71
  const parameters = [];
71
- if (options.metadata?.openapi?.parameters) {
72
- parameters.push(...options.metadata.openapi.parameters);
73
- return parameters;
74
- }
75
- if (options.query instanceof z.ZodObject) Object.entries(options.query.shape).forEach(([key, value]) => {
72
+ if (options.metadata?.openapi?.parameters) parameters.push(...options.metadata.openapi.parameters);
73
+ if (!options.metadata?.openapi?.parameters && options.query instanceof z.ZodObject) Object.entries(options.query.shape).forEach(([key, value]) => {
76
74
  if (value instanceof z.ZodType) {
77
75
  const parameterSchema = toOpenApiSchema(value);
78
76
  parameters.push({
@@ -84,6 +82,15 @@ function getParameters(options) {
84
82
  });
85
83
  return parameters;
86
84
  }
85
+ function getPathParameters(path, parameters) {
86
+ const existingParameters = new Set(parameters.map((parameter) => `${parameter.in}:${parameter.name}`));
87
+ return path.split("/").filter((part) => part.startsWith(":")).map((part) => part.slice(1)).filter((name) => !existingParameters.has(`path:${name}`)).map((name) => ({
88
+ name,
89
+ in: "path",
90
+ required: true,
91
+ schema: { type: "string" }
92
+ }));
93
+ }
87
94
  function getRequestBodySchemaInfo(zodType) {
88
95
  return {
89
96
  required: !schemaAcceptsUndefined(zodType),
@@ -278,6 +285,29 @@ function getResponse(responses) {
278
285
  function toOpenApiPath(path) {
279
286
  return path.split("/").map((part) => part.startsWith(":") ? `{${part.slice(1)}}` : part).join("/");
280
287
  }
288
+ function getOperationId(operationId, method, usedOperationIds) {
289
+ if (!operationId) return;
290
+ if (!usedOperationIds.has(operationId)) {
291
+ usedOperationIds.add(operationId);
292
+ return operationId;
293
+ }
294
+ const normalizedMethod = method.toUpperCase();
295
+ const methodSuffix = normalizedMethod.charAt(0) + normalizedMethod.slice(1).toLowerCase();
296
+ let candidate = `${operationId}${methodSuffix}`;
297
+ let duplicateIndex = 2;
298
+ while (usedOperationIds.has(candidate)) {
299
+ candidate = `${operationId}${methodSuffix}${duplicateIndex}`;
300
+ duplicateIndex += 1;
301
+ }
302
+ usedOperationIds.add(candidate);
303
+ return candidate;
304
+ }
305
+ function cloneOpenAPIValue(value) {
306
+ if (Array.isArray(value)) return value.map((item) => cloneOpenAPIValue(item));
307
+ if (value instanceof Date) return new Date(value);
308
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneOpenAPIValue(entry)]));
309
+ return value;
310
+ }
281
311
  async function generator(ctx, options) {
282
312
  const baseEndpoints = getEndpoints(ctx, {
283
313
  ...options,
@@ -315,31 +345,24 @@ async function generator(ctx, options) {
315
345
  return acc;
316
346
  }, {}) } };
317
347
  const paths = {};
318
- const seenOperationIds = /* @__PURE__ */ new Set();
319
- const uniqueOperationId = (operationId, method) => {
320
- if (!operationId) return void 0;
321
- const base = seenOperationIds.has(operationId) ? `${operationId}${toPascalCase(method)}` : operationId;
322
- let result = base;
323
- let n = 2;
324
- while (seenOperationIds.has(result)) result = `${base}${n++}`;
325
- seenOperationIds.add(result);
326
- return result;
327
- };
348
+ const usedOperationIds = /* @__PURE__ */ new Set();
328
349
  Object.entries(baseEndpoints.api).forEach(([_, value]) => {
329
350
  if (!value.path || ctx.options.disabledPaths?.includes(value.path)) return;
330
351
  const options = value.options;
331
352
  if (options.metadata?.SERVER_ONLY) return;
332
353
  const path = toOpenApiPath(value.path);
354
+ const operationParameters = getParameters(options);
355
+ const parameters = [...operationParameters, ...getPathParameters(value.path, operationParameters)];
333
356
  const methods = Array.isArray(options.method) ? options.method : [options.method];
334
357
  for (const method of methods.filter((m) => m === "GET" || m === "DELETE")) paths[path] = {
335
358
  ...paths[path],
336
359
  [method.toLowerCase()]: {
337
360
  tags: ["Default", ...options.metadata?.openapi?.tags || []],
338
361
  description: options.metadata?.openapi?.description,
339
- operationId: uniqueOperationId(options.metadata?.openapi?.operationId, method),
362
+ operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
340
363
  security: [{ bearerAuth: [] }],
341
- parameters: getParameters(options),
342
- responses: getResponse(options.metadata?.openapi?.responses)
364
+ parameters: cloneOpenAPIValue(parameters),
365
+ responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
343
366
  }
344
367
  };
345
368
  for (const method of methods.filter((m) => m === "POST" || m === "PATCH" || m === "PUT")) {
@@ -349,14 +372,14 @@ async function generator(ctx, options) {
349
372
  [method.toLowerCase()]: {
350
373
  tags: ["Default", ...options.metadata?.openapi?.tags || []],
351
374
  description: options.metadata?.openapi?.description,
352
- operationId: uniqueOperationId(options.metadata?.openapi?.operationId, method),
375
+ operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
353
376
  security: [{ bearerAuth: [] }],
354
- parameters: getParameters(options),
355
- ...body ? { requestBody: body } : { requestBody: { content: { "application/json": { schema: {
377
+ parameters: cloneOpenAPIValue(parameters),
378
+ ...body ? { requestBody: cloneOpenAPIValue(body) } : { requestBody: { content: { "application/json": { schema: {
356
379
  type: "object",
357
380
  properties: {}
358
381
  } } } } },
359
- responses: getResponse(options.metadata?.openapi?.responses)
382
+ responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
360
383
  }
361
384
  };
362
385
  }
@@ -376,16 +399,18 @@ async function generator(ctx, options) {
376
399
  const options = value.options;
377
400
  if (options.metadata?.SERVER_ONLY) return;
378
401
  const path = toOpenApiPath(value.path);
402
+ const operationParameters = getParameters(options);
403
+ const parameters = [...operationParameters, ...getPathParameters(value.path, operationParameters)];
379
404
  const methods = Array.isArray(options.method) ? options.method : [options.method];
380
405
  for (const method of methods.filter((m) => m === "GET" || m === "DELETE")) paths[path] = {
381
406
  ...paths[path],
382
407
  [method.toLowerCase()]: {
383
408
  tags: options.metadata?.openapi?.tags || [plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)],
384
409
  description: options.metadata?.openapi?.description,
385
- operationId: uniqueOperationId(options.metadata?.openapi?.operationId, method),
410
+ operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
386
411
  security: [{ bearerAuth: [] }],
387
- parameters: getParameters(options),
388
- responses: getResponse(options.metadata?.openapi?.responses)
412
+ parameters: cloneOpenAPIValue(parameters),
413
+ responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
389
414
  }
390
415
  };
391
416
  for (const method of methods.filter((m) => m === "POST" || m === "PATCH" || m === "PUT")) paths[path] = {
@@ -393,11 +418,11 @@ async function generator(ctx, options) {
393
418
  [method.toLowerCase()]: {
394
419
  tags: options.metadata?.openapi?.tags || [plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)],
395
420
  description: options.metadata?.openapi?.description,
396
- operationId: uniqueOperationId(options.metadata?.openapi?.operationId, method),
421
+ operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
397
422
  security: [{ bearerAuth: [] }],
398
- parameters: getParameters(options),
399
- requestBody: getRequestBody(options),
400
- responses: getResponse(options.metadata?.openapi?.responses)
423
+ parameters: cloneOpenAPIValue(parameters),
424
+ requestBody: cloneOpenAPIValue(getRequestBody(options)),
425
+ responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
401
426
  }
402
427
  };
403
428
  });
@@ -678,10 +678,11 @@ const getOrgAdapter = (context, options) => {
678
678
  field: "status",
679
679
  value: data.fromStatus
680
680
  });
681
- return await adapter.update({
681
+ return await adapter.incrementOne({
682
682
  model: "invitation",
683
683
  where,
684
- update: { status: data.status }
684
+ increment: {},
685
+ set: { status: data.status }
685
686
  });
686
687
  }
687
688
  };
@@ -564,7 +564,7 @@ const updateOrgRole = (options) => {
564
564
  ...updateData,
565
565
  ...updateData.permission ? { permission: JSON.stringify(updateData.permission) } : {}
566
566
  };
567
- await ctx.context.adapter.update({
567
+ await ctx.context.adapter.updateMany({
568
568
  model: "organizationRole",
569
569
  where: [{
570
570
  field: "organizationId",
@@ -177,16 +177,17 @@ const backupCode2fa = (opts) => {
177
177
  }, ctx.context.secretConfig, opts);
178
178
  if (!validate.status || !validate.updated) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE);
179
179
  const updatedBackupCodes = await encodeBackupCodes(validate.updated, ctx.context.secretConfig, opts);
180
- if (!await ctx.context.adapter.update({
180
+ if (!await ctx.context.adapter.incrementOne({
181
181
  model: twoFactorTable,
182
- update: { backupCodes: updatedBackupCodes },
183
182
  where: [{
184
183
  field: "id",
185
184
  value: twoFactor.id
186
185
  }, {
187
186
  field: "backupCodes",
188
187
  value: twoFactor.backupCodes
189
- }]
188
+ }],
189
+ increment: {},
190
+ set: { backupCodes: updatedBackupCodes }
190
191
  })) throw APIError.fromStatus("CONFLICT", { message: "Failed to verify backup code. Please try again." });
191
192
  if (!ctx.body.disableSession) return valid(ctx);
192
193
  return ctx.json({
package/dist/state.mjs CHANGED
@@ -17,6 +17,7 @@ const stateDataSchema = z.looseObject({
17
17
  }).optional(),
18
18
  requestSignUp: z.boolean().optional()
19
19
  });
20
+ const INTERNAL_STATE_KEYS = new Set(Object.keys(stateDataSchema.shape));
20
21
  var StateError = class extends BetterAuthError {
21
22
  code;
22
23
  details;
@@ -130,4 +131,4 @@ async function parseGenericState(c, state, settings) {
130
131
  return parsedData;
131
132
  }
132
133
  //#endregion
133
- export { StateError, generateGenericState, parseGenericState };
134
+ export { INTERNAL_STATE_KEYS, StateError, generateGenericState, parseGenericState };