better-auth 1.6.20 → 1.6.22

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.
Files changed (45) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/api/index.mjs +5 -5
  3. package/dist/api/rate-limiter/index.mjs +2 -3
  4. package/dist/api/routes/index.d.mts +1 -1
  5. package/dist/api/routes/session.d.mts +30 -3
  6. package/dist/api/routes/session.mjs +15 -4
  7. package/dist/context/create-context.mjs +6 -0
  8. package/dist/db/get-migration.mjs +1 -0
  9. package/dist/db/get-schema.d.mts +1 -0
  10. package/dist/db/get-schema.mjs +3 -1
  11. package/dist/db/index.d.mts +3 -2
  12. package/dist/db/index.mjs +5 -2
  13. package/dist/db/internal-adapter.mjs +1 -1
  14. package/dist/db/revoke-unproven-account-access.d.mts +19 -0
  15. package/dist/db/revoke-unproven-account-access.mjs +23 -0
  16. package/dist/db/schema.d.mts +2 -1
  17. package/dist/db/schema.mjs +13 -1
  18. package/dist/oauth2/link-account.mjs +20 -7
  19. package/dist/package.mjs +1 -1
  20. package/dist/plugins/admin/routes.mjs +4 -4
  21. package/dist/plugins/captcha/index.mjs +1 -1
  22. package/dist/plugins/device-authorization/index.d.mts +9 -9
  23. package/dist/plugins/device-authorization/index.mjs +1 -1
  24. package/dist/plugins/email-otp/routes.mjs +5 -1
  25. package/dist/plugins/magic-link/index.d.mts +3 -3
  26. package/dist/plugins/magic-link/index.mjs +6 -2
  27. package/dist/plugins/oauth-proxy/index.mjs +11 -6
  28. package/dist/plugins/one-tap/index.mjs +10 -9
  29. package/dist/plugins/siwe/index.mjs +5 -1
  30. package/dist/plugins/two-factor/backup-codes/index.mjs +21 -7
  31. package/dist/plugins/two-factor/client.d.mts +1 -0
  32. package/dist/plugins/two-factor/error-code.d.mts +1 -0
  33. package/dist/plugins/two-factor/error-code.mjs +1 -0
  34. package/dist/plugins/two-factor/index.d.mts +14 -0
  35. package/dist/plugins/two-factor/index.mjs +7 -1
  36. package/dist/plugins/two-factor/otp/index.mjs +17 -1
  37. package/dist/plugins/two-factor/schema.d.mts +13 -0
  38. package/dist/plugins/two-factor/schema.mjs +13 -0
  39. package/dist/plugins/two-factor/totp/index.mjs +23 -9
  40. package/dist/plugins/two-factor/types.d.mts +25 -0
  41. package/dist/plugins/two-factor/verify-two-factor.mjs +110 -3
  42. package/dist/plugins/username/index.mjs +56 -42
  43. package/package.json +9 -9
  44. package/dist/utils/get-request-ip.d.mts +0 -6
  45. package/dist/utils/get-request-ip.mjs +0 -20
@@ -1,6 +1,5 @@
1
1
  import { OverrideMerge, Prettify as Prettify$1, UnionToIntersection } from "../types/helper.mjs";
2
2
  import { AdditionalSessionFieldsInput, AdditionalUserFieldsInput } from "../types/models.mjs";
3
- import { getIp } from "../utils/get-request-ip.mjs";
4
3
  import { isAPIError } from "../utils/is-api-error.mjs";
5
4
  import { DispatchContext, dispatchAuthEndpoint } from "./dispatch.mjs";
6
5
  import { requireOrgRole, requireResourceOwnership } from "./middlewares/authorization.mjs";
@@ -11,7 +10,7 @@ import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEm
11
10
  import { error } from "./routes/error.mjs";
12
11
  import { ok } from "./routes/ok.mjs";
13
12
  import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./routes/password.mjs";
14
- import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
13
+ import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
15
14
  import { signInEmail, signInSocial } from "./routes/sign-in.mjs";
16
15
  import { signOut } from "./routes/sign-out.mjs";
17
16
  import { signUpEmail } from "./routes/sign-up.mjs";
@@ -24,6 +23,7 @@ import * as _better_auth_core_db0 from "@better-auth/core/db";
24
23
  import { InternalLogger } from "@better-auth/core/env";
25
24
  import { APIError } from "@better-auth/core/error";
26
25
  import * as _better_auth_core_oauth20 from "@better-auth/core/oauth2";
26
+ import { getIp } from "@better-auth/core/utils/ip";
27
27
  import * as better_call0 from "better-call";
28
28
  import { AuthEndpoint, AuthMiddleware, createAuthEndpoint, createAuthMiddleware, optionsMiddleware } from "@better-auth/core/api";
29
29
  import * as zod from "zod";
@@ -3961,4 +3961,4 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3961
3961
  } 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;
3962
3962
  };
3963
3963
  //#endregion
3964
- export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, 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 };
3964
+ export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, accountInfo, 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, 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 };
@@ -1,11 +1,10 @@
1
1
  import { isAPIError } from "../utils/is-api-error.mjs";
2
2
  import { requireOrgRole, requireResourceOwnership } from "./middlewares/authorization.mjs";
3
3
  import { formCsrfMiddleware, originCheck, originCheckMiddleware } from "./middlewares/origin-check.mjs";
4
- import { getIp } from "../utils/get-request-ip.mjs";
5
4
  import { onRequestRateLimit } from "./rate-limiter/index.mjs";
6
5
  import { getOAuthState } from "./state/oauth.mjs";
7
6
  import { getShouldSkipSessionRefresh, setShouldSkipSessionRefresh } from "./state/should-session-refresh.mjs";
8
- import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
7
+ import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
9
8
  import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs";
10
9
  import { callbackOAuth } from "./routes/callback.mjs";
11
10
  import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./routes/email-verification.mjs";
@@ -21,6 +20,7 @@ import { dispatchAuthEndpoint } from "./dispatch.mjs";
21
20
  import { toAuthEndpoints } from "./to-auth-endpoints.mjs";
22
21
  import { logger } from "@better-auth/core/env";
23
22
  import { APIError } from "@better-auth/core/error";
23
+ import { getIp } from "@better-auth/core/utils/ip";
24
24
  import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, withSpan } from "@better-auth/core/instrumentation";
25
25
  import { normalizePathname } from "@better-auth/core/utils/url";
26
26
  import { createRouter } from "better-call";
@@ -165,6 +165,8 @@ const router = (ctx, options) => {
165
165
  const normalizedPath = normalizePathname(req.url, basePath);
166
166
  if (disabledPaths.includes(normalizedPath)) return new Response("Not Found", { status: 404 });
167
167
  let currentRequest = req;
168
+ const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);
169
+ if (rateLimitResponse) return rateLimitResponse;
168
170
  for (const plugin of ctx.options.plugins || []) if (plugin.onRequest) {
169
171
  const response = await withSpan(`onRequest ${plugin.id}`, {
170
172
  [ATTR_HOOK_TYPE]: "onRequest",
@@ -173,8 +175,6 @@ const router = (ctx, options) => {
173
175
  if (response && "response" in response) return response.response;
174
176
  if (response && "request" in response) currentRequest = response.request;
175
177
  }
176
- const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);
177
- if (rateLimitResponse) return rateLimitResponse;
178
178
  return currentRequest;
179
179
  },
180
180
  async onResponse(res, req) {
@@ -213,4 +213,4 @@ const router = (ctx, options) => {
213
213
  });
214
214
  };
215
215
  //#endregion
216
- export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, 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 };
216
+ export { APIError, accountInfo, 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, 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 };
@@ -1,8 +1,7 @@
1
1
  import { wildcardMatch } from "../../utils/wildcard.mjs";
2
- import { getIp } from "../../utils/get-request-ip.mjs";
3
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
+ import { createRateLimitKey, getIp } from "@better-auth/core/utils/ip";
4
4
  import { normalizePathname } from "@better-auth/core/utils/url";
5
- import { createRateLimitKey } from "@better-auth/core/utils/ip";
6
5
  //#region src/api/rate-limiter/index.ts
7
6
  const memory = /* @__PURE__ */ new Map();
8
7
  const MEMORY_STORE_MAX_ENTRIES = 1e5;
@@ -282,7 +281,7 @@ async function resolveRateLimitConfig(req, ctx) {
282
281
  const ip = getIp(req, ctx.options);
283
282
  if (!ip && ctx.options.advanced?.ipAddress?.disableIpTracking) return null;
284
283
  if (!ip && !ipWarningLogged) {
285
- ctx.logger.warn("Rate limiting could not determine a client IP and is falling back to a single shared per-path bucket. Ensure your runtime forwards a trusted client IP header and configure `advanced.ipAddress.ipAddressHeaders` if needed.");
284
+ ctx.logger.warn("Rate limiting could not determine a client IP and is falling back to a single shared per-path bucket. Ensure your runtime forwards a trusted client IP header, then set `advanced.ipAddress.ipAddressHeaders` or `advanced.ipAddress.trustedProxies` so the address can be resolved.");
286
285
  ipWarningLogged = true;
287
286
  }
288
287
  const key = createRateLimitKey(ip ?? NO_TRUSTED_IP_KEY, path);
@@ -4,7 +4,7 @@ import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEm
4
4
  import { error } from "./error.mjs";
5
5
  import { ok } from "./ok.mjs";
6
6
  import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./password.mjs";
7
- import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
7
+ import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
8
8
  import { signInEmail, signInSocial } from "./sign-in.mjs";
9
9
  import { signOut } from "./sign-out.mjs";
10
10
  import { signUpEmail } from "./sign-up.mjs";
@@ -63,6 +63,34 @@ declare const getSessionFromCtx: <U extends Record<string, any> = Record<string,
63
63
  session: S & Session$1;
64
64
  user: U & User$1;
65
65
  } | null>;
66
+ /**
67
+ * Reads the session from the source that can authorize sensitive work.
68
+ *
69
+ * Stateful deployments must re-read the server-side session store because an
70
+ * earlier hook may have populated `ctx.context.session` from cookie cache.
71
+ * Stateless deployments keep the signed cookie as the session record.
72
+ */
73
+ declare const getAuthoritativeSessionFromCtx: <U extends Record<string, any> = Record<string, any>, S extends Record<string, any> = Record<string, any>>(ctx: GenericEndpointContext) => Promise<{
74
+ session: S & {
75
+ id: string;
76
+ createdAt: Date;
77
+ updatedAt: Date;
78
+ userId: string;
79
+ expiresAt: Date;
80
+ token: string;
81
+ ipAddress?: string | null | undefined;
82
+ userAgent?: string | null | undefined;
83
+ };
84
+ user: U & {
85
+ id: string;
86
+ createdAt: Date;
87
+ updatedAt: Date;
88
+ email: string;
89
+ emailVerified: boolean;
90
+ name: string;
91
+ image?: string | null | undefined;
92
+ };
93
+ } | null>;
66
94
  /**
67
95
  * The middleware forces the endpoint to require a valid session.
68
96
  */
@@ -90,9 +118,8 @@ declare const sessionMiddleware: (inputContext: better_call0.MiddlewareInputCont
90
118
  };
91
119
  }>;
92
120
  /**
93
- * This middleware forces the endpoint to require a valid session and ignores cookie cache.
121
+ * This middleware forces the endpoint to require a valid authoritative session.
94
122
  * This should be used for sensitive operations like password changes, account deletion, etc.
95
- * to ensure that revoked sessions cannot be used even if they're still cached in cookies.
96
123
  */
97
124
  declare const sensitiveSessionMiddleware: (inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
98
125
  session: {
@@ -420,4 +447,4 @@ declare const revokeOtherSessions: better_call0.StrictEndpoint<"/revoke-other-se
420
447
  status: boolean;
421
448
  }>;
422
449
  //#endregion
423
- export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
450
+ export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
@@ -299,6 +299,18 @@ const getSessionFromCtx = async (ctx, config) => {
299
299
  return session.response;
300
300
  };
301
301
  /**
302
+ * Reads the session from the source that can authorize sensitive work.
303
+ *
304
+ * Stateful deployments must re-read the server-side session store because an
305
+ * earlier hook may have populated `ctx.context.session` from cookie cache.
306
+ * Stateless deployments keep the signed cookie as the session record.
307
+ */
308
+ const getAuthoritativeSessionFromCtx = async (ctx) => {
309
+ if (!isStateful(ctx)) return getSessionFromCtx(ctx);
310
+ ctx.context.session = null;
311
+ return getSessionFromCtx(ctx, { disableCookieCache: true });
312
+ };
313
+ /**
302
314
  * The middleware forces the endpoint to require a valid session.
303
315
  */
304
316
  const sessionMiddleware = createAuthMiddleware(async (ctx) => {
@@ -310,12 +322,11 @@ const sessionMiddleware = createAuthMiddleware(async (ctx) => {
310
322
  return { session };
311
323
  });
312
324
  /**
313
- * This middleware forces the endpoint to require a valid session and ignores cookie cache.
325
+ * This middleware forces the endpoint to require a valid authoritative session.
314
326
  * This should be used for sensitive operations like password changes, account deletion, etc.
315
- * to ensure that revoked sessions cannot be used even if they're still cached in cookies.
316
327
  */
317
328
  const sensitiveSessionMiddleware = createAuthMiddleware(async (ctx) => {
318
- const session = await getSessionFromCtx(ctx, { disableCookieCache: true });
329
+ const session = await getAuthoritativeSessionFromCtx(ctx);
319
330
  if (!session?.session) throw APIError.from("UNAUTHORIZED", {
320
331
  message: "Unauthorized",
321
332
  code: "UNAUTHORIZED"
@@ -491,4 +502,4 @@ const revokeOtherSessions = createAuthEndpoint("/revoke-other-sessions", {
491
502
  return ctx.json({ status: true });
492
503
  });
493
504
  //#endregion
494
- export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
505
+ export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
@@ -16,6 +16,7 @@ import { createLogger, env, isProduction, isTest } from "@better-auth/core/env";
16
16
  import { BetterAuthError } from "@better-auth/core/error";
17
17
  import { generateId } from "@better-auth/core/utils/id";
18
18
  import { socialProviders } from "@better-auth/core/social-providers";
19
+ import { findInvalidTrustedProxies } from "@better-auth/core/utils/ip";
19
20
  import { createTelemetry } from "@better-auth/telemetry";
20
21
  import defu$1 from "defu";
21
22
  //#region src/context/create-context.ts
@@ -86,6 +87,11 @@ Most of the features of Better Auth will not work correctly.`);
86
87
  plugins: plugins.concat(internalPlugins)
87
88
  };
88
89
  checkEndpointConflicts(options, logger);
90
+ const trustedProxies = options.advanced?.ipAddress?.trustedProxies;
91
+ if (trustedProxies && trustedProxies.length > 0) {
92
+ const invalid = findInvalidTrustedProxies(trustedProxies);
93
+ if (invalid.length > 0) logger.warn(`Ignoring invalid \`advanced.ipAddress.trustedProxies\` entries: ${invalid.join(", ")}. Each entry must be an IP address or CIDR range.`);
94
+ }
89
95
  const cookies = getCookies(options);
90
96
  const tables = getAuthTables(options);
91
97
  const providers = (await Promise.all(Object.entries(options.socialProviders || {}).map(async ([key, originalConfig]) => {
@@ -148,6 +148,7 @@ async function getMigrations(config) {
148
148
  const toBeCreated = [];
149
149
  const toBeAdded = [];
150
150
  for (const [key, value] of Object.entries(betterAuthSchema)) {
151
+ if (value.disableMigrations) continue;
151
152
  const table = tableMetadata.find((t) => t.name === key);
152
153
  if (!table) {
153
154
  const tIndex = toBeCreated.findIndex((t) => t.table === key);
@@ -5,6 +5,7 @@ import { DBFieldAttribute } from "@better-auth/core/db";
5
5
  declare function getSchema(config: BetterAuthOptions): Record<string, {
6
6
  fields: Record<string, DBFieldAttribute>;
7
7
  order: number;
8
+ disableMigrations?: boolean | undefined;
8
9
  }>;
9
10
  //#endregion
10
11
  export { getSchema };
@@ -23,11 +23,13 @@ function getSchema(config) {
23
23
  ...schema[table.modelName].fields,
24
24
  ...actualFields
25
25
  };
26
+ if (table.disableMigrations) schema[table.modelName].disableMigrations = true;
26
27
  continue;
27
28
  }
28
29
  schema[table.modelName] = {
29
30
  fields: actualFields,
30
- order: table.order || Infinity
31
+ order: table.order || Infinity,
32
+ disableMigrations: table.disableMigrations
31
33
  };
32
34
  }
33
35
  return schema;
@@ -3,7 +3,8 @@ import { convertFromDB, convertToDB } from "./field-converter.mjs";
3
3
  import { getSchema } from "./get-schema.mjs";
4
4
  import { DatabaseHooksEntry, getWithHooks } from "./with-hooks.mjs";
5
5
  import { createInternalAdapter } from "./internal-adapter.mjs";
6
- import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
6
+ import { revokeUnprovenAccountAccess } from "./revoke-unproven-account-access.mjs";
7
+ import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
7
8
  import { FieldAttributeToSchema, toZodSchema } from "./to-zod.mjs";
8
9
  export * from "@better-auth/core/db";
9
- export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
10
+ export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, revokeUnprovenAccountAccess, toZodSchema };
package/dist/db/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs";
2
2
  import { getSchema } from "./get-schema.mjs";
3
- import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
3
+ import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
4
4
  import { convertFromDB, convertToDB } from "./field-converter.mjs";
5
5
  import { getWithHooks } from "./with-hooks.mjs";
6
6
  import { createInternalAdapter } from "./internal-adapter.mjs";
7
+ import { revokeUnprovenAccountAccess } from "./revoke-unproven-account-access.mjs";
7
8
  import { toZodSchema } from "./to-zod.mjs";
8
9
  export * from "@better-auth/core/db";
9
10
  //#region src/db/index.ts
@@ -19,14 +20,16 @@ var db_exports = /* @__PURE__ */ __exportAll({
19
20
  parseAccountInput: () => parseAccountInput,
20
21
  parseAccountOutput: () => parseAccountOutput,
21
22
  parseAdditionalUserInput: () => parseAdditionalUserInput,
23
+ parseAdditionalUserInputFromProviderProfile: () => parseAdditionalUserInputFromProviderProfile,
22
24
  parseInputData: () => parseInputData,
23
25
  parseSessionInput: () => parseSessionInput,
24
26
  parseSessionOutput: () => parseSessionOutput,
25
27
  parseUserInput: () => parseUserInput,
26
28
  parseUserOutput: () => parseUserOutput,
29
+ revokeUnprovenAccountAccess: () => revokeUnprovenAccountAccess,
27
30
  toZodSchema: () => toZodSchema
28
31
  });
29
32
  import * as import__better_auth_core_db from "@better-auth/core/db";
30
33
  __reExport(db_exports, import__better_auth_core_db);
31
34
  //#endregion
32
- export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
35
+ export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, revokeUnprovenAccountAccess, toZodSchema };
@@ -1,4 +1,3 @@
1
- import { getIp } from "../utils/get-request-ip.mjs";
2
1
  import { getSessionDefaultFields, parseSessionOutput, parseUserOutput } from "./schema.mjs";
3
2
  import { getDate } from "../utils/date.mjs";
4
3
  import { getStorageOption, processIdentifier } from "./verification-token-storage.mjs";
@@ -6,6 +5,7 @@ import { getWithHooks } from "./with-hooks.mjs";
6
5
  import { getCurrentAdapter, getCurrentAuthContext, runWithTransaction } from "@better-auth/core/context";
7
6
  import { generateId } from "@better-auth/core/utils/id";
8
7
  import { safeJSONParse } from "@better-auth/core/utils/json";
8
+ import { getIp } from "@better-auth/core/utils/ip";
9
9
  import { base64Url } from "@better-auth/utils/base64";
10
10
  import { createHash } from "@better-auth/utils/hash";
11
11
  //#region src/db/internal-adapter.ts
@@ -0,0 +1,19 @@
1
+ import { GenericEndpointContext } from "@better-auth/core";
2
+
3
+ //#region src/db/revoke-unproven-account-access.d.ts
4
+ /**
5
+ * Strip every credential and session a pre-existing account accrued before
6
+ * control of its email was proven.
7
+ *
8
+ * An `emailVerified: false` row carries no proof that the password on it belongs
9
+ * to the mailbox owner. When an email-primary proof (magic link, email OTP)
10
+ * resolves to such a row, deleting the `credential` account and revoking standing
11
+ * sessions makes the verified owner inherit no password or session that predates
12
+ * the proof. Call this before flipping `emailVerified` and minting the owner's
13
+ * session; it no-ops if a concurrent flow has already verified the account.
14
+ *
15
+ * @param userId - The pre-existing, not-yet-verified user being promoted.
16
+ */
17
+ declare function revokeUnprovenAccountAccess(ctx: GenericEndpointContext, userId: string): Promise<void>;
18
+ //#endregion
19
+ export { revokeUnprovenAccountAccess };
@@ -0,0 +1,23 @@
1
+ //#region src/db/revoke-unproven-account-access.ts
2
+ /**
3
+ * Strip every credential and session a pre-existing account accrued before
4
+ * control of its email was proven.
5
+ *
6
+ * An `emailVerified: false` row carries no proof that the password on it belongs
7
+ * to the mailbox owner. When an email-primary proof (magic link, email OTP)
8
+ * resolves to such a row, deleting the `credential` account and revoking standing
9
+ * sessions makes the verified owner inherit no password or session that predates
10
+ * the proof. Call this before flipping `emailVerified` and minting the owner's
11
+ * session; it no-ops if a concurrent flow has already verified the account.
12
+ *
13
+ * @param userId - The pre-existing, not-yet-verified user being promoted.
14
+ */
15
+ async function revokeUnprovenAccountAccess(ctx, userId) {
16
+ const user = await ctx.context.internalAdapter.findUserById(userId);
17
+ if (!user || user.emailVerified) return;
18
+ const accounts = await ctx.context.internalAdapter.findAccounts(userId);
19
+ for (const account of accounts) if (account.providerId === "credential") await ctx.context.internalAdapter.deleteAccount(account.id);
20
+ await ctx.context.internalAdapter.deleteUserSessions(userId);
21
+ }
22
+ //#endregion
23
+ export { revokeUnprovenAccountAccess };
@@ -24,6 +24,7 @@ declare function parseInputData<T extends Record<string, any>>(data: T, schema:
24
24
  action?: ("create" | "update") | undefined;
25
25
  }): Partial<T>;
26
26
  declare function parseUserInput(options: BetterAuthOptions, user: Record<string, any> | undefined, action: "create" | "update"): Partial<Record<string, any>>;
27
+ declare function parseAdditionalUserInputFromProviderProfile(options: BetterAuthOptions, profile: Record<string, unknown> | undefined, action: "create" | "update"): Partial<Record<string, unknown>>;
27
28
  declare function parseAdditionalUserInput(options: BetterAuthOptions, user?: Record<string, any> | undefined): Partial<Record<string, any>>;
28
29
  declare function parseAccountInput(options: BetterAuthOptions, account: Partial<Account>): Partial<Partial<{
29
30
  id: string;
@@ -58,4 +59,4 @@ declare function mergeSchema<S extends BetterAuthPluginDBSchema>(schema: S, newS
58
59
  } | undefined;
59
60
  } | undefined } | undefined): S;
60
61
  //#endregion
61
- export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
62
+ export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
@@ -113,6 +113,18 @@ function parseUserInput(options, user = {}, action) {
113
113
  action
114
114
  });
115
115
  }
116
+ function parseAdditionalUserInputFromProviderProfile(options, profile = {}, action) {
117
+ const schema = getFields(options, "user", "input");
118
+ const allowedProfileFields = Object.create(null);
119
+ for (const key of Object.keys(profile)) {
120
+ if (schema[key]?.input === false) continue;
121
+ allowedProfileFields[key] = profile[key];
122
+ }
123
+ return parseInputData(allowedProfileFields, {
124
+ fields: schema,
125
+ action
126
+ });
127
+ }
116
128
  function parseAdditionalUserInput(options, user) {
117
129
  const schema = getFields(options, "user", "input");
118
130
  return parseInputData(user || {}, { fields: schema });
@@ -146,4 +158,4 @@ function mergeSchema(schema, newSchema) {
146
158
  return schema;
147
159
  }
148
160
  //#endregion
149
- export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
161
+ export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
@@ -1,4 +1,5 @@
1
1
  import { isAPIError } from "../utils/is-api-error.mjs";
2
+ import { parseAdditionalUserInputFromProviderProfile } from "../db/schema.mjs";
2
3
  import { setAccountCookie } from "../cookies/session-store.mjs";
3
4
  import { redirectOnError } from "./errors.mjs";
4
5
  import { setTokenUtil } from "./utils.mjs";
@@ -64,9 +65,12 @@ async function handleOAuthUserInfo(c, opts) {
64
65
  if (userInfo.emailVerified && !dbUser.user.emailVerified && userInfo.email.toLowerCase() === dbUser.user.email) await c.context.internalAdapter.updateUser(dbUser.user.id, { emailVerified: true });
65
66
  }
66
67
  if (overrideUserInfo) {
67
- const { id: _, ...restUserInfo } = userInfo;
68
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
69
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
68
70
  user = await c.context.internalAdapter.updateUser(dbUser.user.id, {
69
- ...restUserInfo,
71
+ name,
72
+ image,
73
+ ...additionalUserFields,
70
74
  email: userInfo.email.toLowerCase(),
71
75
  emailVerified: userInfo.email.toLowerCase() === dbUser.user.email ? dbUser.user.emailVerified || userInfo.emailVerified : userInfo.emailVerified
72
76
  });
@@ -78,7 +82,8 @@ async function handleOAuthUserInfo(c, opts) {
78
82
  isRegister: false
79
83
  };
80
84
  try {
81
- const { id: _, ...restUserInfo } = userInfo;
85
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
86
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "create");
82
87
  const accountData = {
83
88
  accessToken: await setTokenUtil(account.accessToken, c.context),
84
89
  refreshToken: await setTokenUtil(account.refreshToken, c.context),
@@ -90,8 +95,11 @@ async function handleOAuthUserInfo(c, opts) {
90
95
  accountId: userInfo.id.toString()
91
96
  };
92
97
  const { user: createdUser, account: createdAccount } = await c.context.internalAdapter.createOAuthUser({
93
- ...restUserInfo,
94
- email: userInfo.email.toLowerCase()
98
+ name,
99
+ image,
100
+ ...additionalUserFields,
101
+ email: userInfo.email.toLowerCase(),
102
+ emailVerified: userInfo.emailVerified
95
103
  }, accountData);
96
104
  user = createdUser;
97
105
  if (c.context.options.account?.storeAccountCookie) await setAccountCookie(c, createdAccount);
@@ -152,9 +160,14 @@ async function handleOAuthUserInfo(c, opts) {
152
160
  */
153
161
  async function applyUpdateUserInfoOnLink(c, userId, userInfo) {
154
162
  if (c.context.options.account?.accountLinking?.updateUserInfoOnLink !== true) return;
155
- const { id: _id, email: _email, emailVerified: _emailVerified, ...profile } = userInfo;
156
163
  try {
157
- return await c.context.internalAdapter.updateUser(userId, profile);
164
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
165
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
166
+ return await c.context.internalAdapter.updateUser(userId, {
167
+ name,
168
+ image,
169
+ ...additionalUserFields
170
+ });
158
171
  } catch (e) {
159
172
  c.context.logger.warn("Could not update user info on account link", e);
160
173
  return;
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.6.20";
2
+ var version = "1.6.22";
3
3
  //#endregion
4
4
  export { version };
@@ -1,7 +1,7 @@
1
1
  import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
2
2
  import { getDate } from "../../utils/date.mjs";
3
3
  import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs";
4
- import { getSessionFromCtx } from "../../api/routes/session.mjs";
4
+ import { getAuthoritativeSessionFromCtx, getSessionFromCtx } from "../../api/routes/session.mjs";
5
5
  import { ADMIN_ERROR_CODES } from "./error-codes.mjs";
6
6
  import { hasPermission } from "./has-permission.mjs";
7
7
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
@@ -14,7 +14,7 @@ import * as z from "zod";
14
14
  * Will also provide additional types on the user to include role types.
15
15
  */
16
16
  const adminMiddleware = createAuthMiddleware(async (ctx) => {
17
- const session = await getSessionFromCtx(ctx);
17
+ const session = await getAuthoritativeSessionFromCtx(ctx);
18
18
  if (!session) throw APIError.fromStatus("UNAUTHORIZED");
19
19
  return { session };
20
20
  });
@@ -146,7 +146,7 @@ const createUser = (opts) => createAuthEndpoint("/admin/create-user", {
146
146
  $Infer: { body: {} }
147
147
  }
148
148
  }, async (ctx) => {
149
- const session = await getSessionFromCtx(ctx);
149
+ const session = await getAuthoritativeSessionFromCtx(ctx);
150
150
  if (!session && (ctx.request || ctx.headers)) throw ctx.error("UNAUTHORIZED");
151
151
  if (session) {
152
152
  if (!hasPermission({
@@ -885,7 +885,7 @@ const userHasPermission = (opts) => {
885
885
  }
886
886
  }, async (ctx) => {
887
887
  if (!ctx.body?.permissions) throw new APIError("BAD_REQUEST", { message: "invalid permission check. no permission(s) were passed." });
888
- const session = await getSessionFromCtx(ctx);
888
+ const session = await getAuthoritativeSessionFromCtx(ctx);
889
889
  if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED");
890
890
  if (!session && !ctx.body.userId && !ctx.body.role) throw new APIError("BAD_REQUEST", { message: "user id or role is required" });
891
891
  const user = session?.user || (ctx.body.role ? {
@@ -1,4 +1,3 @@
1
- import { getIp } from "../../utils/get-request-ip.mjs";
2
1
  import { PACKAGE_VERSION } from "../../version.mjs";
3
2
  import { middlewareResponse } from "../../utils/middleware-response.mjs";
4
3
  import { Providers, defaultEndpoints, siteVerifyMap } from "./constants.mjs";
@@ -7,6 +6,7 @@ import { captchaFox } from "./verify-handlers/captchafox.mjs";
7
6
  import { cloudflareTurnstile } from "./verify-handlers/cloudflare-turnstile.mjs";
8
7
  import { googleRecaptcha } from "./verify-handlers/google-recaptcha.mjs";
9
8
  import { hCaptcha } from "./verify-handlers/h-captcha.mjs";
9
+ import { getIp } from "@better-auth/core/utils/ip";
10
10
  //#region src/plugins/captcha/index.ts
11
11
  const captcha = (options) => ({
12
12
  id: "captcha",
@@ -21,7 +21,7 @@ declare const deviceAuthorizationOptionsSchema: z.ZodObject<{
21
21
  validateClient: z.ZodOptional<z.ZodCustom<(clientId: string) => boolean | Promise<boolean>, (clientId: string) => boolean | Promise<boolean>>>;
22
22
  onDeviceAuthRequest: z.ZodOptional<z.ZodCustom<(clientId: string, scope: string | undefined) => void | Promise<void>, (clientId: string, scope: string | undefined) => void | Promise<void>>>;
23
23
  verificationUri: z.ZodOptional<z.ZodString>;
24
- schema: z.ZodCustom<{
24
+ schema: z.ZodOptional<z.ZodCustom<{
25
25
  deviceCode?: {
26
26
  modelName?: string | undefined;
27
27
  fields?: {
@@ -51,7 +51,7 @@ declare const deviceAuthorizationOptionsSchema: z.ZodObject<{
51
51
  scope?: string | undefined;
52
52
  } | undefined;
53
53
  } | undefined;
54
- }>;
54
+ }>>;
55
55
  }, z.core.$strip>;
56
56
  type DeviceAuthorizationOptions = z.infer<typeof deviceAuthorizationOptionsSchema>;
57
57
  declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions>) => {
@@ -399,7 +399,12 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
399
399
  interval: TimeString;
400
400
  deviceCodeLength: number;
401
401
  userCodeLength: number;
402
- schema: {
402
+ generateDeviceCode?: (() => string | Promise<string>) | undefined;
403
+ generateUserCode?: (() => string | Promise<string>) | undefined;
404
+ validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined;
405
+ onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined;
406
+ verificationUri?: string | undefined;
407
+ schema?: {
403
408
  deviceCode?: {
404
409
  modelName?: string | undefined;
405
410
  fields?: {
@@ -414,12 +419,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
414
419
  scope?: string | undefined;
415
420
  } | undefined;
416
421
  } | undefined;
417
- };
418
- generateDeviceCode?: (() => string | Promise<string>) | undefined;
419
- generateUserCode?: (() => string | Promise<string>) | undefined;
420
- validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined;
421
- onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined;
422
- verificationUri?: string | undefined;
422
+ } | undefined;
423
423
  }>;
424
424
  };
425
425
  //#endregion
@@ -25,7 +25,7 @@ const deviceAuthorizationOptionsSchema = z.object({
25
25
  validateClient: z.custom((val) => typeof val === "function", { message: "validateClient must be a function that returns a boolean or a promise that resolves to a boolean." }).optional().describe("Function to validate the client ID. If not provided, no validation will be performed."),
26
26
  onDeviceAuthRequest: z.custom((val) => typeof val === "function", { message: "onDeviceAuthRequest must be a function that returns void or a promise that resolves to void." }).optional().describe("Function to handle device authorization requests. If not provided, no additional actions will be taken."),
27
27
  verificationUri: z.string().optional().describe("The URI where users verify their device code. Can be an absolute URL (https://example.com/device) or relative path (/custom-path). This will be returned as verification_uri in the device code response. If not provided, defaults to /device."),
28
- schema: z.custom(() => true)
28
+ schema: z.custom(() => true).optional()
29
29
  });
30
30
  const deviceAuthorization = (options = {}) => {
31
31
  const opts = deviceAuthorizationOptionsSchema.parse(options);
@@ -3,6 +3,7 @@ import { getDate } from "../../utils/date.mjs";
3
3
  import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { symmetricDecrypt } from "../../crypto/index.mjs";
5
5
  import { setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
6
+ import { revokeUnprovenAccountAccess } from "../../db/revoke-unproven-account-access.mjs";
6
7
  import { getSessionFromCtx, sensitiveSessionMiddleware } from "../../api/routes/session.mjs";
7
8
  import { APIError as APIError$1 } from "../../api/index.mjs";
8
9
  import { EMAIL_OTP_ERROR_CODES } from "./error-codes.mjs";
@@ -420,7 +421,10 @@ const signInEmailOTP = (opts) => createAuthEndpoint("/sign-in/email-otp", {
420
421
  user: parseUserOutput(ctx.context.options, newUser)
421
422
  });
422
423
  }
423
- if (!user.user.emailVerified) await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
424
+ if (!user.user.emailVerified) {
425
+ await revokeUnprovenAccountAccess(ctx, user.user.id);
426
+ await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
427
+ }
424
428
  const session = await ctx.context.internalAdapter.createSession(user.user.id);
425
429
  await setSessionCookie(ctx, {
426
430
  session,
@@ -22,9 +22,9 @@ interface MagicLinkOptions {
22
22
  * @deprecated Multi-attempt verification is no longer supported. Each
23
23
  * magic link token is consumed atomically on the first verification call,
24
24
  * so a given token mints at most one session regardless of this value
25
- * (see GHSA-hc7v-rggr-4hvx). The option is kept for source compatibility
26
- * and may be removed in a future major; any value other than `1` is
27
- * ignored and emits a `console.warn` at plugin construction.
25
+ * The option is kept for source compatibility and may be removed in a future
26
+ * major; any value other than `1` is ignored and emits a `console.warn` at
27
+ * plugin construction.
28
28
  *
29
29
  * @default 1
30
30
  */