better-auth 1.7.0-beta.9 → 1.7.0-rc.1

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 (52) 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 +3 -4
  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 +16 -5
  7. package/dist/context/create-context.mjs +6 -0
  8. package/dist/db/get-migration.mjs +10 -4
  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 +2 -2
  14. package/dist/db/revoke-unproven-account-access.d.mts +21 -0
  15. package/dist/db/revoke-unproven-account-access.mjs +58 -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 +26 -13
  19. package/dist/package.mjs +1 -1
  20. package/dist/plugins/admin/routes.mjs +4 -4
  21. package/dist/plugins/captcha/index.mjs +2 -2
  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 +10 -4
  25. package/dist/plugins/generic-oauth/index.d.mts +2 -1
  26. package/dist/plugins/generic-oauth/index.mjs +2 -1
  27. package/dist/plugins/generic-oauth/providers/index.d.mts +2 -1
  28. package/dist/plugins/generic-oauth/providers/yandex.d.mts +29 -0
  29. package/dist/plugins/generic-oauth/providers/yandex.mjs +63 -0
  30. package/dist/plugins/index.d.mts +2 -1
  31. package/dist/plugins/index.mjs +2 -1
  32. package/dist/plugins/magic-link/index.d.mts +3 -3
  33. package/dist/plugins/magic-link/index.mjs +7 -2
  34. package/dist/plugins/oauth-proxy/index.mjs +11 -6
  35. package/dist/plugins/one-tap/index.mjs +10 -9
  36. package/dist/plugins/siwe/index.mjs +33 -3
  37. package/dist/plugins/two-factor/backup-codes/index.mjs +21 -7
  38. package/dist/plugins/two-factor/client.d.mts +1 -0
  39. package/dist/plugins/two-factor/error-code.d.mts +1 -0
  40. package/dist/plugins/two-factor/error-code.mjs +1 -0
  41. package/dist/plugins/two-factor/index.d.mts +14 -0
  42. package/dist/plugins/two-factor/index.mjs +7 -1
  43. package/dist/plugins/two-factor/otp/index.mjs +16 -1
  44. package/dist/plugins/two-factor/schema.d.mts +13 -0
  45. package/dist/plugins/two-factor/schema.mjs +13 -0
  46. package/dist/plugins/two-factor/totp/index.mjs +23 -9
  47. package/dist/plugins/two-factor/types.d.mts +25 -0
  48. package/dist/plugins/two-factor/verify-two-factor.mjs +123 -3
  49. package/dist/plugins/username/index.mjs +61 -47
  50. package/package.json +25 -25
  51. package/dist/utils/get-request-ip.d.mts +0 -6
  52. 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, NO_STORE_HEADERS, createAuthEndpoint, createAuthMiddleware, optionsMiddleware } from "@better-auth/core/api";
29
29
  import * as zod from "zod";
@@ -3971,4 +3971,4 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3971
3971
  } 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;
3972
3972
  };
3973
3973
  //#endregion
3974
- export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, 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 };
3974
+ export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, 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 { addOAuthServerContext, 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, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, 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, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, 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 { BetterAuthError } from "@better-auth/core/error";
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;
@@ -237,10 +236,10 @@ async function resolveRateLimitConfig(req, ctx) {
237
236
  const path = normalizePathname(req.url, basePath);
238
237
  let currentWindow = ctx.rateLimit.window;
239
238
  let currentMax = ctx.rateLimit.max;
240
- const ip = getIp(req, ctx.options);
239
+ const ip = getIP(req, ctx.options);
241
240
  if (!ip && ctx.options.advanced?.ipAddress?.disableIpTracking) return null;
242
241
  if (!ip && !ipWarningLogged) {
243
- 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.");
242
+ 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.");
244
243
  ipWarningLogged = true;
245
244
  }
246
245
  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 };
@@ -240,7 +240,7 @@ const getSession = () => createAuthEndpoint("/get-session", {
240
240
  deleteSessionCookie(ctx);
241
241
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
242
242
  }
243
- const maxAge = (updatedSession.expiresAt.valueOf() - Date.now()) / 1e3;
243
+ const maxAge = ctx.context.sessionConfig.expiresIn;
244
244
  await setSessionCookie(ctx, {
245
245
  session: updatedSession,
246
246
  user: session.user
@@ -307,6 +307,18 @@ const getSessionFromCtx = async (ctx, config) => {
307
307
  return session.response;
308
308
  };
309
309
  /**
310
+ * Reads the session from the source that can authorize sensitive work.
311
+ *
312
+ * Stateful deployments must re-read the server-side session store because an
313
+ * earlier hook may have populated `ctx.context.session` from cookie cache.
314
+ * Stateless deployments keep the signed cookie as the session record.
315
+ */
316
+ const getAuthoritativeSessionFromCtx = async (ctx) => {
317
+ if (!isStateful(ctx)) return getSessionFromCtx(ctx);
318
+ ctx.context.session = null;
319
+ return getSessionFromCtx(ctx, { disableCookieCache: true });
320
+ };
321
+ /**
310
322
  * The middleware forces the endpoint to require a valid session.
311
323
  */
312
324
  const sessionMiddleware = createAuthMiddleware(async (ctx) => {
@@ -318,12 +330,11 @@ const sessionMiddleware = createAuthMiddleware(async (ctx) => {
318
330
  return { session };
319
331
  });
320
332
  /**
321
- * This middleware forces the endpoint to require a valid session and ignores cookie cache.
333
+ * This middleware forces the endpoint to require a valid authoritative session.
322
334
  * This should be used for sensitive operations like password changes, account deletion, etc.
323
- * to ensure that revoked sessions cannot be used even if they're still cached in cookies.
324
335
  */
325
336
  const sensitiveSessionMiddleware = createAuthMiddleware(async (ctx) => {
326
- const session = await getSessionFromCtx(ctx, { disableCookieCache: true });
337
+ const session = await getAuthoritativeSessionFromCtx(ctx);
327
338
  if (!session?.session) throw APIError.from("UNAUTHORIZED", {
328
339
  message: "Unauthorized",
329
340
  code: "UNAUTHORIZED"
@@ -499,4 +510,4 @@ const revokeOtherSessions = createAuthEndpoint("/revoke-other-sessions", {
499
510
  return ctx.json({ status: true });
500
511
  });
501
512
  //#endregion
502
- export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
513
+ export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
@@ -17,6 +17,7 @@ import { createLogger, env, isProduction, isTest } from "@better-auth/core/env";
17
17
  import { BetterAuthError } from "@better-auth/core/error";
18
18
  import { generateId } from "@better-auth/core/utils/id";
19
19
  import { socialProviders } from "@better-auth/core/social-providers";
20
+ import { findInvalidTrustedProxies } from "@better-auth/core/utils/ip";
20
21
  import { createTelemetry } from "@better-auth/telemetry";
21
22
  import defu$1 from "defu";
22
23
  //#region src/context/create-context.ts
@@ -87,6 +88,11 @@ Most of the features of Better Auth will not work correctly.`);
87
88
  plugins: plugins.concat(internalPlugins)
88
89
  };
89
90
  checkEndpointConflicts(options, logger);
91
+ const trustedProxies = options.advanced?.ipAddress?.trustedProxies;
92
+ if (trustedProxies && trustedProxies.length > 0) {
93
+ const invalid = findInvalidTrustedProxies(trustedProxies);
94
+ if (invalid.length > 0) logger.warn(`Ignoring invalid \`advanced.ipAddress.trustedProxies\` entries: ${invalid.join(", ")}. Each entry must be an IP address or CIDR range.`);
95
+ }
90
96
  const cookies = getCookies(options);
91
97
  const tables = getAuthTables(options);
92
98
  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);
@@ -273,17 +274,22 @@ async function getMigrations(config) {
273
274
  if (toBeAdded.length) for (const table of toBeAdded) for (const [fieldName, field] of Object.entries(table.fields)) {
274
275
  const type = getType(field, fieldName);
275
276
  const builder = db.schema.alterTable(table.table);
276
- if (field.index) {
277
+ if (field.index || field.unique) {
277
278
  const indexName = `${table.table}_${fieldName}_${field.unique ? "uidx" : "idx"}`;
278
- const indexBuilder = db.schema.createIndex(indexName).on(table.table).columns([fieldName]);
279
- deferredIndexes.push(field.unique ? indexBuilder.unique() : indexBuilder);
279
+ let indexBuilder = db.schema.createIndex(indexName).on(table.table).columns([fieldName]);
280
+ if (field.unique) {
281
+ indexBuilder = indexBuilder.unique();
282
+ if (field.required === false && dbType === "mssql") indexBuilder = indexBuilder.where(fieldName, "is not", null);
283
+ if (field.required !== false && field.defaultValue !== void 0 && field.defaultValue !== null && typeof field.defaultValue !== "function") logger.warn(`Adding unique column "${fieldName}" to existing table "${table.table}" backfills every existing row with its default value. If the table has more than one row, creating the unique index "${indexName}" will fail; backfill distinct values manually, then re-run the migration or create the index yourself.`);
284
+ }
285
+ deferredIndexes.push(indexBuilder);
280
286
  }
281
287
  const built = builder.addColumn(fieldName, type, (col) => {
282
288
  col = field.required !== false ? col.notNull() : col;
283
289
  if (field.references) col = col.references(getReferencePath(field.references.model, field.references.field)).onDelete(field.references.onDelete || "cascade");
284
- if (field.unique) col = col.unique();
285
290
  if (field.type === "date" && typeof field.defaultValue === "function" && (dbType === "postgres" || dbType === "mysql" || dbType === "mssql")) if (dbType === "mysql") col = col.defaultTo(sql`CURRENT_TIMESTAMP(3)`);
286
291
  else col = col.defaultTo(sql`CURRENT_TIMESTAMP`);
292
+ else if (!(field.unique && field.required === false) && (field.type === "string" || field.type === "number" || field.type === "boolean") && field.defaultValue !== void 0 && field.defaultValue !== null && typeof field.defaultValue !== "function") col = col.defaultTo(typeof field.defaultValue === "boolean" && (dbType === "sqlite" || dbType === "mssql") ? field.defaultValue ? 1 : 0 : field.defaultValue);
287
293
  return col;
288
294
  });
289
295
  migrations.push(built);
@@ -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 { assertValidUserInfo, assertValidUserInfoSource } from "../utils/validate-user-info.mjs";
@@ -8,6 +7,7 @@ import { getCurrentAdapter, getCurrentAuthContext, runWithTransaction } from "@b
8
7
  import { APIError, BetterAuthError } from "@better-auth/core/error";
9
8
  import { generateId } from "@better-auth/core/utils/id";
10
9
  import { safeJSONParse } from "@better-auth/core/utils/json";
10
+ import { getIP } from "@better-auth/core/utils/ip";
11
11
  import { base64Url } from "@better-auth/utils/base64";
12
12
  import { createHash } from "@better-auth/utils/hash";
13
13
  //#region src/db/internal-adapter.ts
@@ -230,7 +230,7 @@ const createInternalAdapter = (adapter, ctx) => {
230
230
  const defaultAdditionalFields = getSessionDefaultFields(options);
231
231
  const data = {
232
232
  ...sessionId ? { id: sessionId } : {},
233
- ipAddress: headers ? getIp(headers, options) || "" : "",
233
+ ipAddress: headers ? getIP(headers, options) || "" : "",
234
234
  userAgent: headers?.get("user-agent") || "",
235
235
  ...rest,
236
236
  expiresAt: dontRememberMe ? getDate(3600 * 24, "sec") : getDate(sessionExpiration, "sec"),
@@ -0,0 +1,21 @@
1
+ import { User } from "../types/models.mjs";
2
+ import { GenericEndpointContext } from "@better-auth/core";
3
+
4
+ //#region src/db/revoke-unproven-account-access.d.ts
5
+ /**
6
+ * Strip every account link and session a pre-existing account accrued before
7
+ * control of its email was proven.
8
+ *
9
+ * An `emailVerified: false` row carries no proof that linked access belongs
10
+ * to the mailbox owner. When an email-primary proof (magic link, email OTP)
11
+ * resolves to such a row, deleting accounts and revoking standing sessions
12
+ * makes the verified owner inherit no password, OAuth link, or session that
13
+ * predates the proof. This helper also flips `emailVerified` after cleanup and
14
+ * returns the current user for the caller to use when minting the owner's
15
+ * session.
16
+ *
17
+ * @param userId - The pre-existing, not-yet-verified user being promoted.
18
+ */
19
+ declare function revokeUnprovenAccountAccess(ctx: GenericEndpointContext, userId: string): Promise<User | null>;
20
+ //#endregion
21
+ export { revokeUnprovenAccountAccess };
@@ -0,0 +1,58 @@
1
+ import { BetterAuthError } from "@better-auth/core/error";
2
+ //#region src/db/revoke-unproven-account-access.ts
3
+ const cleanupLockExpiresInMs = 5e3;
4
+ const cleanupLockWaitMs = 2e3;
5
+ const cleanupLockPollMs = 250;
6
+ const cleanupLockIdentifier = (userId) => `revoke-unproven-account-access:${userId}`;
7
+ async function waitForCleanupLock(ctx, identifier) {
8
+ const deadline = Date.now() + cleanupLockWaitMs;
9
+ while (Date.now() < deadline) {
10
+ const lock = await ctx.context.internalAdapter.findVerificationValue(identifier);
11
+ if (!lock) return;
12
+ if (lock.expiresAt <= /* @__PURE__ */ new Date()) {
13
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
14
+ return;
15
+ }
16
+ await new Promise((resolve) => setTimeout(resolve, cleanupLockPollMs));
17
+ }
18
+ }
19
+ /**
20
+ * Strip every account link and session a pre-existing account accrued before
21
+ * control of its email was proven.
22
+ *
23
+ * An `emailVerified: false` row carries no proof that linked access belongs
24
+ * to the mailbox owner. When an email-primary proof (magic link, email OTP)
25
+ * resolves to such a row, deleting accounts and revoking standing sessions
26
+ * makes the verified owner inherit no password, OAuth link, or session that
27
+ * predates the proof. This helper also flips `emailVerified` after cleanup and
28
+ * returns the current user for the caller to use when minting the owner's
29
+ * session.
30
+ *
31
+ * @param userId - The pre-existing, not-yet-verified user being promoted.
32
+ */
33
+ async function revokeUnprovenAccountAccess(ctx, userId) {
34
+ const lockIdentifier = cleanupLockIdentifier(userId);
35
+ if (!await ctx.context.internalAdapter.reserveVerificationValue({
36
+ identifier: lockIdentifier,
37
+ value: userId,
38
+ expiresAt: new Date(Date.now() + cleanupLockExpiresInMs)
39
+ }).catch((error) => {
40
+ if (error instanceof BetterAuthError && error.message.includes("requires database-backed verification storage")) return true;
41
+ throw error;
42
+ })) {
43
+ await waitForCleanupLock(ctx, lockIdentifier);
44
+ return ctx.context.internalAdapter.findUserById(userId);
45
+ }
46
+ try {
47
+ const user = await ctx.context.internalAdapter.findUserById(userId);
48
+ if (!user || user.emailVerified) return user;
49
+ const accounts = await ctx.context.internalAdapter.findAccounts(userId);
50
+ for (const account of accounts) await ctx.context.internalAdapter.deleteAccount(account.id);
51
+ await ctx.context.internalAdapter.deleteUserSessions(userId);
52
+ return await ctx.context.internalAdapter.updateUser(userId, { emailVerified: true });
53
+ } finally {
54
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(lockIdentifier).catch(() => {});
55
+ }
56
+ }
57
+ //#endregion
58
+ 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,11 +1,12 @@
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 { assertValidUserInfo } from "../utils/validate-user-info.mjs";
4
5
  import { OAUTH_CALLBACK_ERROR_CODES, redirectOnError } from "./errors.mjs";
5
6
  import { setTokenUtil } from "./utils.mjs";
6
7
  import { createEmailVerificationToken } from "../api/routes/email-verification.mjs";
7
8
  import { runWithTransaction } from "@better-auth/core/context";
8
- import { isDevelopment, logger } from "@better-auth/core/env";
9
+ import { isDevelopment } from "@better-auth/core/env";
9
10
  //#region src/oauth2/link-account.ts
10
11
  async function handleOAuthUserInfo(c, opts) {
11
12
  const { userInfo, account, callbackURL, disableSignUp, overrideUserInfo } = opts;
@@ -14,7 +15,7 @@ async function handleOAuthUserInfo(c, opts) {
14
15
  oauth: { providerId: account.providerId }
15
16
  };
16
17
  const dbUser = await c.context.internalAdapter.findOAuthUser(userInfo.email.toLowerCase(), account.accountId, account.providerId).catch((e) => {
17
- logger.error("Better auth was unable to query your database.\nError: ", e);
18
+ c.context.logger.error("Better auth was unable to query your database.\nError: ", e);
18
19
  redirectOnError(c, c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`, "internal_server_error");
19
20
  });
20
21
  let user = dbUser?.user;
@@ -26,7 +27,7 @@ async function handleOAuthUserInfo(c, opts) {
26
27
  const isTrustedProvider = opts.isTrustedProvider || opts.trustProviderByName !== false && c.context.trustedProviders.includes(account.providerId);
27
28
  const requireLocalEmailVerified = accountLinking?.requireLocalEmailVerified ?? true;
28
29
  if (!isTrustedProvider && !userInfo.emailVerified || requireLocalEmailVerified && !dbUser.user.emailVerified || accountLinking?.enabled === false || accountLinking?.disableImplicitLinking === true) {
29
- if (isDevelopment()) logger.warn(`User already exist but account isn't linked to ${account.providerId}. To read more about how account linking works in Better Auth see https://www.better-auth.com/docs/concepts/users-accounts#account-linking.`);
30
+ if (isDevelopment()) c.context.logger.warn(`User already exist but account isn't linked to ${account.providerId}. To read more about how account linking works in Better Auth see https://www.better-auth.com/docs/concepts/users-accounts#account-linking.`);
30
31
  return {
31
32
  error: "account not linked",
32
33
  data: null
@@ -58,7 +59,7 @@ async function handleOAuthUserInfo(c, opts) {
58
59
  });
59
60
  } catch (e) {
60
61
  if (isAPIError(e)) throw e;
61
- logger.error("Unable to link account", e);
62
+ c.context.logger.error("Unable to link account", e);
62
63
  return {
63
64
  error: "unable to link account",
64
65
  data: null
@@ -99,13 +100,16 @@ async function handleOAuthUserInfo(c, opts) {
99
100
  if (userInfo.emailVerified && !dbUser.user.emailVerified && userInfo.email.toLowerCase() === dbUser.user.email) await c.context.internalAdapter.updateUser(dbUser.user.id, { emailVerified: true });
100
101
  }
101
102
  if (overrideUserInfo) {
102
- const { id: _, ...restUserInfo } = userInfo;
103
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
104
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
103
105
  const updatedUser = await c.context.internalAdapter.updateUser(dbUser.user.id, {
104
- ...restUserInfo,
106
+ name,
107
+ image,
108
+ ...additionalUserFields,
105
109
  email: userInfo.email.toLowerCase(),
106
110
  emailVerified: userInfo.email.toLowerCase() === dbUser.user.email ? dbUser.user.emailVerified || userInfo.emailVerified : userInfo.emailVerified
107
111
  });
108
- if (updatedUser == null) logger.warn("Could not update user info during OAuth sign in; preserving existing user for session.");
112
+ if (updatedUser == null) c.context.logger.warn("Could not update user info during OAuth sign in; preserving existing user for session.");
109
113
  user = updatedUser ?? user;
110
114
  }
111
115
  } else {
@@ -115,7 +119,8 @@ async function handleOAuthUserInfo(c, opts) {
115
119
  isRegister: false
116
120
  };
117
121
  try {
118
- const { id: _, ...restUserInfo } = userInfo;
122
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
123
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "create");
119
124
  const accountData = {
120
125
  accessToken: await setTokenUtil(account.accessToken, c.context),
121
126
  refreshToken: await setTokenUtil(account.refreshToken, c.context),
@@ -128,8 +133,11 @@ async function handleOAuthUserInfo(c, opts) {
128
133
  };
129
134
  const { createdUser, createdAccount } = await runWithTransaction(c.context.adapter, async () => {
130
135
  const createdUser = await c.context.internalAdapter.createUser({
131
- ...restUserInfo,
132
- email: userInfo.email.toLowerCase()
136
+ name,
137
+ image,
138
+ ...additionalUserFields,
139
+ email: userInfo.email.toLowerCase(),
140
+ emailVerified: userInfo.emailVerified
133
141
  }, source);
134
142
  return {
135
143
  createdUser,
@@ -143,7 +151,7 @@ async function handleOAuthUserInfo(c, opts) {
143
151
  if (c.context.options.account?.storeAccountCookie) await setAccountCookie(c, createdAccount);
144
152
  } catch (e) {
145
153
  if (isAPIError(e)) throw e;
146
- logger.error("Unable to create OAuth user", e);
154
+ c.context.logger.error("Unable to create OAuth user", e);
147
155
  return {
148
156
  error: "unable to create user",
149
157
  data: null,
@@ -210,9 +218,14 @@ async function dispatchVerificationEmail(c, user, callbackURL) {
210
218
  */
211
219
  async function applyUpdateUserInfoOnLink(c, userId, userInfo) {
212
220
  if (c.context.options.account?.accountLinking?.updateUserInfoOnLink !== true) return;
213
- const { id: _id, email: _email, emailVerified: _emailVerified, ...profile } = userInfo;
214
221
  try {
215
- return await c.context.internalAdapter.updateUser(userId, profile);
222
+ const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
223
+ const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
224
+ return await c.context.internalAdapter.updateUser(userId, {
225
+ name,
226
+ image,
227
+ ...additionalUserFields
228
+ });
216
229
  } catch (e) {
217
230
  c.context.logger.warn("Could not update user info on account link", e);
218
231
  return;
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.7.0-beta.9";
2
+ var version = "1.7.0-rc.1";
3
3
  //#endregion
4
4
  export { version };