better-auth 1.7.0-beta.8 → 1.7.0-rc.0
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.
- package/dist/api/index.d.mts +3 -3
- package/dist/api/index.mjs +5 -5
- package/dist/api/rate-limiter/index.mjs +3 -4
- package/dist/api/routes/index.d.mts +1 -1
- package/dist/api/routes/session.d.mts +30 -3
- package/dist/api/routes/session.mjs +16 -5
- package/dist/context/create-context.mjs +6 -0
- package/dist/db/get-migration.mjs +1 -0
- package/dist/db/get-schema.d.mts +1 -0
- package/dist/db/get-schema.mjs +3 -1
- package/dist/db/index.d.mts +3 -2
- package/dist/db/index.mjs +5 -2
- package/dist/db/internal-adapter.mjs +2 -2
- package/dist/db/revoke-unproven-account-access.d.mts +21 -0
- package/dist/db/revoke-unproven-account-access.mjs +58 -0
- package/dist/db/schema.d.mts +2 -1
- package/dist/db/schema.mjs +13 -1
- package/dist/oauth2/link-account.mjs +26 -13
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/routes.mjs +4 -4
- package/dist/plugins/captcha/index.mjs +2 -2
- package/dist/plugins/device-authorization/index.d.mts +9 -9
- package/dist/plugins/device-authorization/index.mjs +1 -1
- package/dist/plugins/email-otp/routes.mjs +10 -4
- package/dist/plugins/magic-link/index.d.mts +3 -3
- package/dist/plugins/magic-link/index.mjs +7 -2
- package/dist/plugins/oauth-proxy/index.mjs +11 -6
- package/dist/plugins/one-tap/index.mjs +10 -9
- package/dist/plugins/siwe/index.mjs +33 -3
- package/dist/plugins/two-factor/backup-codes/index.mjs +21 -7
- package/dist/plugins/two-factor/client.d.mts +1 -0
- package/dist/plugins/two-factor/error-code.d.mts +1 -0
- package/dist/plugins/two-factor/error-code.mjs +1 -0
- package/dist/plugins/two-factor/index.d.mts +14 -0
- package/dist/plugins/two-factor/index.mjs +7 -1
- package/dist/plugins/two-factor/otp/index.mjs +16 -1
- package/dist/plugins/two-factor/schema.d.mts +13 -0
- package/dist/plugins/two-factor/schema.mjs +13 -0
- package/dist/plugins/two-factor/totp/index.mjs +23 -9
- package/dist/plugins/two-factor/types.d.mts +25 -0
- package/dist/plugins/two-factor/verify-two-factor.mjs +123 -3
- package/dist/plugins/username/index.mjs +61 -47
- package/package.json +25 -25
- package/dist/utils/get-request-ip.d.mts +0 -6
- package/dist/utils/get-request-ip.mjs +0 -20
package/dist/api/index.d.mts
CHANGED
|
@@ -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,
|
|
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 };
|
package/dist/api/index.mjs
CHANGED
|
@@ -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,
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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);
|
package/dist/db/get-schema.d.mts
CHANGED
|
@@ -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 };
|
package/dist/db/get-schema.mjs
CHANGED
|
@@ -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;
|
package/dist/db/index.d.mts
CHANGED
|
@@ -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 {
|
|
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 ?
|
|
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 };
|
package/dist/db/schema.d.mts
CHANGED
|
@@ -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 };
|
package/dist/db/schema.mjs
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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,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
|
|
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
|
|
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
|
|
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 ? {
|