better-auth 1.6.20 → 1.6.21
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 +2 -3
- package/dist/api/routes/index.d.mts +1 -1
- package/dist/api/routes/session.d.mts +30 -3
- package/dist/api/routes/session.mjs +15 -4
- 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 +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +1 -1
- package/dist/db/schema.d.mts +2 -1
- package/dist/db/schema.mjs +13 -1
- package/dist/oauth2/link-account.mjs +20 -7
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/routes.mjs +4 -4
- package/dist/plugins/captcha/index.mjs +1 -1
- package/dist/plugins/device-authorization/index.d.mts +9 -9
- package/dist/plugins/device-authorization/index.mjs +1 -1
- package/dist/plugins/magic-link/index.d.mts +3 -3
- package/dist/plugins/magic-link/index.mjs +1 -1
- package/dist/plugins/oauth-proxy/index.mjs +11 -6
- package/dist/plugins/one-tap/index.mjs +10 -9
- package/dist/plugins/siwe/index.mjs +5 -1
- package/dist/plugins/two-factor/backup-codes/index.mjs +17 -6
- package/dist/plugins/two-factor/index.mjs +7 -1
- package/dist/plugins/two-factor/totp/index.mjs +19 -8
- package/dist/plugins/two-factor/verify-two-factor.mjs +27 -2
- package/dist/plugins/username/index.mjs +56 -42
- package/package.json +9 -9
- 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, createAuthEndpoint, createAuthMiddleware, optionsMiddleware } from "@better-auth/core/api";
|
|
29
29
|
import * as zod from "zod";
|
|
@@ -3961,4 +3961,4 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3961
3961
|
} extends infer T_2 ? { [K in keyof T_2 as K extends keyof T_1 ? never : K]: T_2[K] } : never) & T_1> : never : never : never;
|
|
3962
3962
|
};
|
|
3963
3963
|
//#endregion
|
|
3964
|
-
export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
3964
|
+
export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
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 { getOAuthState } from "./state/oauth.mjs";
|
|
7
6
|
import { getShouldSkipSessionRefresh, setShouldSkipSessionRefresh } from "./state/should-session-refresh.mjs";
|
|
8
|
-
import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
|
|
7
|
+
import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
|
|
9
8
|
import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs";
|
|
10
9
|
import { callbackOAuth } from "./routes/callback.mjs";
|
|
11
10
|
import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./routes/email-verification.mjs";
|
|
@@ -21,6 +20,7 @@ import { dispatchAuthEndpoint } from "./dispatch.mjs";
|
|
|
21
20
|
import { toAuthEndpoints } from "./to-auth-endpoints.mjs";
|
|
22
21
|
import { logger } from "@better-auth/core/env";
|
|
23
22
|
import { APIError } from "@better-auth/core/error";
|
|
23
|
+
import { getIp } from "@better-auth/core/utils/ip";
|
|
24
24
|
import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, withSpan } from "@better-auth/core/instrumentation";
|
|
25
25
|
import { normalizePathname } from "@better-auth/core/utils/url";
|
|
26
26
|
import { createRouter } from "better-call";
|
|
@@ -165,6 +165,8 @@ const router = (ctx, options) => {
|
|
|
165
165
|
const normalizedPath = normalizePathname(req.url, basePath);
|
|
166
166
|
if (disabledPaths.includes(normalizedPath)) return new Response("Not Found", { status: 404 });
|
|
167
167
|
let currentRequest = req;
|
|
168
|
+
const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);
|
|
169
|
+
if (rateLimitResponse) return rateLimitResponse;
|
|
168
170
|
for (const plugin of ctx.options.plugins || []) if (plugin.onRequest) {
|
|
169
171
|
const response = await withSpan(`onRequest ${plugin.id}`, {
|
|
170
172
|
[ATTR_HOOK_TYPE]: "onRequest",
|
|
@@ -173,8 +175,6 @@ const router = (ctx, options) => {
|
|
|
173
175
|
if (response && "response" in response) return response.response;
|
|
174
176
|
if (response && "request" in response) currentRequest = response.request;
|
|
175
177
|
}
|
|
176
|
-
const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);
|
|
177
|
-
if (rateLimitResponse) return rateLimitResponse;
|
|
178
178
|
return currentRequest;
|
|
179
179
|
},
|
|
180
180
|
async onResponse(res, req) {
|
|
@@ -213,4 +213,4 @@ const router = (ctx, options) => {
|
|
|
213
213
|
});
|
|
214
214
|
};
|
|
215
215
|
//#endregion
|
|
216
|
-
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
216
|
+
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { wildcardMatch } from "../../utils/wildcard.mjs";
|
|
2
|
-
import { getIp } from "../../utils/get-request-ip.mjs";
|
|
3
2
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
3
|
+
import { createRateLimitKey, getIp } from "@better-auth/core/utils/ip";
|
|
4
4
|
import { normalizePathname } from "@better-auth/core/utils/url";
|
|
5
|
-
import { createRateLimitKey } from "@better-auth/core/utils/ip";
|
|
6
5
|
//#region src/api/rate-limiter/index.ts
|
|
7
6
|
const memory = /* @__PURE__ */ new Map();
|
|
8
7
|
const MEMORY_STORE_MAX_ENTRIES = 1e5;
|
|
@@ -282,7 +281,7 @@ async function resolveRateLimitConfig(req, ctx) {
|
|
|
282
281
|
const ip = getIp(req, ctx.options);
|
|
283
282
|
if (!ip && ctx.options.advanced?.ipAddress?.disableIpTracking) return null;
|
|
284
283
|
if (!ip && !ipWarningLogged) {
|
|
285
|
-
ctx.logger.warn("Rate limiting could not determine a client IP and is falling back to a single shared per-path bucket. Ensure your runtime forwards a trusted client IP header
|
|
284
|
+
ctx.logger.warn("Rate limiting could not determine a client IP and is falling back to a single shared per-path bucket. Ensure your runtime forwards a trusted client IP header, then set `advanced.ipAddress.ipAddressHeaders` or `advanced.ipAddress.trustedProxies` so the address can be resolved.");
|
|
286
285
|
ipWarningLogged = true;
|
|
287
286
|
}
|
|
288
287
|
const key = createRateLimitKey(ip ?? NO_TRUSTED_IP_KEY, path);
|
|
@@ -4,7 +4,7 @@ import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEm
|
|
|
4
4
|
import { error } from "./error.mjs";
|
|
5
5
|
import { ok } from "./ok.mjs";
|
|
6
6
|
import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./password.mjs";
|
|
7
|
-
import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
|
|
7
|
+
import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
|
|
8
8
|
import { signInEmail, signInSocial } from "./sign-in.mjs";
|
|
9
9
|
import { signOut } from "./sign-out.mjs";
|
|
10
10
|
import { signUpEmail } from "./sign-up.mjs";
|
|
@@ -63,6 +63,34 @@ declare const getSessionFromCtx: <U extends Record<string, any> = Record<string,
|
|
|
63
63
|
session: S & Session$1;
|
|
64
64
|
user: U & User$1;
|
|
65
65
|
} | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Reads the session from the source that can authorize sensitive work.
|
|
68
|
+
*
|
|
69
|
+
* Stateful deployments must re-read the server-side session store because an
|
|
70
|
+
* earlier hook may have populated `ctx.context.session` from cookie cache.
|
|
71
|
+
* Stateless deployments keep the signed cookie as the session record.
|
|
72
|
+
*/
|
|
73
|
+
declare const getAuthoritativeSessionFromCtx: <U extends Record<string, any> = Record<string, any>, S extends Record<string, any> = Record<string, any>>(ctx: GenericEndpointContext) => Promise<{
|
|
74
|
+
session: S & {
|
|
75
|
+
id: string;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
updatedAt: Date;
|
|
78
|
+
userId: string;
|
|
79
|
+
expiresAt: Date;
|
|
80
|
+
token: string;
|
|
81
|
+
ipAddress?: string | null | undefined;
|
|
82
|
+
userAgent?: string | null | undefined;
|
|
83
|
+
};
|
|
84
|
+
user: U & {
|
|
85
|
+
id: string;
|
|
86
|
+
createdAt: Date;
|
|
87
|
+
updatedAt: Date;
|
|
88
|
+
email: string;
|
|
89
|
+
emailVerified: boolean;
|
|
90
|
+
name: string;
|
|
91
|
+
image?: string | null | undefined;
|
|
92
|
+
};
|
|
93
|
+
} | null>;
|
|
66
94
|
/**
|
|
67
95
|
* The middleware forces the endpoint to require a valid session.
|
|
68
96
|
*/
|
|
@@ -90,9 +118,8 @@ declare const sessionMiddleware: (inputContext: better_call0.MiddlewareInputCont
|
|
|
90
118
|
};
|
|
91
119
|
}>;
|
|
92
120
|
/**
|
|
93
|
-
* This middleware forces the endpoint to require a valid session
|
|
121
|
+
* This middleware forces the endpoint to require a valid authoritative session.
|
|
94
122
|
* This should be used for sensitive operations like password changes, account deletion, etc.
|
|
95
|
-
* to ensure that revoked sessions cannot be used even if they're still cached in cookies.
|
|
96
123
|
*/
|
|
97
124
|
declare const sensitiveSessionMiddleware: (inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
98
125
|
session: {
|
|
@@ -420,4 +447,4 @@ declare const revokeOtherSessions: better_call0.StrictEndpoint<"/revoke-other-se
|
|
|
420
447
|
status: boolean;
|
|
421
448
|
}>;
|
|
422
449
|
//#endregion
|
|
423
|
-
export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
|
|
450
|
+
export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
|
|
@@ -299,6 +299,18 @@ const getSessionFromCtx = async (ctx, config) => {
|
|
|
299
299
|
return session.response;
|
|
300
300
|
};
|
|
301
301
|
/**
|
|
302
|
+
* Reads the session from the source that can authorize sensitive work.
|
|
303
|
+
*
|
|
304
|
+
* Stateful deployments must re-read the server-side session store because an
|
|
305
|
+
* earlier hook may have populated `ctx.context.session` from cookie cache.
|
|
306
|
+
* Stateless deployments keep the signed cookie as the session record.
|
|
307
|
+
*/
|
|
308
|
+
const getAuthoritativeSessionFromCtx = async (ctx) => {
|
|
309
|
+
if (!isStateful(ctx)) return getSessionFromCtx(ctx);
|
|
310
|
+
ctx.context.session = null;
|
|
311
|
+
return getSessionFromCtx(ctx, { disableCookieCache: true });
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
302
314
|
* The middleware forces the endpoint to require a valid session.
|
|
303
315
|
*/
|
|
304
316
|
const sessionMiddleware = createAuthMiddleware(async (ctx) => {
|
|
@@ -310,12 +322,11 @@ const sessionMiddleware = createAuthMiddleware(async (ctx) => {
|
|
|
310
322
|
return { session };
|
|
311
323
|
});
|
|
312
324
|
/**
|
|
313
|
-
* This middleware forces the endpoint to require a valid session
|
|
325
|
+
* This middleware forces the endpoint to require a valid authoritative session.
|
|
314
326
|
* This should be used for sensitive operations like password changes, account deletion, etc.
|
|
315
|
-
* to ensure that revoked sessions cannot be used even if they're still cached in cookies.
|
|
316
327
|
*/
|
|
317
328
|
const sensitiveSessionMiddleware = createAuthMiddleware(async (ctx) => {
|
|
318
|
-
const session = await
|
|
329
|
+
const session = await getAuthoritativeSessionFromCtx(ctx);
|
|
319
330
|
if (!session?.session) throw APIError.from("UNAUTHORIZED", {
|
|
320
331
|
message: "Unauthorized",
|
|
321
332
|
code: "UNAUTHORIZED"
|
|
@@ -491,4 +502,4 @@ const revokeOtherSessions = createAuthEndpoint("/revoke-other-sessions", {
|
|
|
491
502
|
return ctx.json({ status: true });
|
|
492
503
|
});
|
|
493
504
|
//#endregion
|
|
494
|
-
export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
|
|
505
|
+
export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
|
|
@@ -16,6 +16,7 @@ import { createLogger, env, isProduction, isTest } from "@better-auth/core/env";
|
|
|
16
16
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
17
17
|
import { generateId } from "@better-auth/core/utils/id";
|
|
18
18
|
import { socialProviders } from "@better-auth/core/social-providers";
|
|
19
|
+
import { findInvalidTrustedProxies } from "@better-auth/core/utils/ip";
|
|
19
20
|
import { createTelemetry } from "@better-auth/telemetry";
|
|
20
21
|
import defu$1 from "defu";
|
|
21
22
|
//#region src/context/create-context.ts
|
|
@@ -86,6 +87,11 @@ Most of the features of Better Auth will not work correctly.`);
|
|
|
86
87
|
plugins: plugins.concat(internalPlugins)
|
|
87
88
|
};
|
|
88
89
|
checkEndpointConflicts(options, logger);
|
|
90
|
+
const trustedProxies = options.advanced?.ipAddress?.trustedProxies;
|
|
91
|
+
if (trustedProxies && trustedProxies.length > 0) {
|
|
92
|
+
const invalid = findInvalidTrustedProxies(trustedProxies);
|
|
93
|
+
if (invalid.length > 0) logger.warn(`Ignoring invalid \`advanced.ipAddress.trustedProxies\` entries: ${invalid.join(", ")}. Each entry must be an IP address or CIDR range.`);
|
|
94
|
+
}
|
|
89
95
|
const cookies = getCookies(options);
|
|
90
96
|
const tables = getAuthTables(options);
|
|
91
97
|
const providers = (await Promise.all(Object.entries(options.socialProviders || {}).map(async ([key, originalConfig]) => {
|
|
@@ -148,6 +148,7 @@ async function getMigrations(config) {
|
|
|
148
148
|
const toBeCreated = [];
|
|
149
149
|
const toBeAdded = [];
|
|
150
150
|
for (const [key, value] of Object.entries(betterAuthSchema)) {
|
|
151
|
+
if (value.disableMigrations) continue;
|
|
151
152
|
const table = tableMetadata.find((t) => t.name === key);
|
|
152
153
|
if (!table) {
|
|
153
154
|
const tIndex = toBeCreated.findIndex((t) => t.table === key);
|
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,7 @@ 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 { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
|
|
7
7
|
import { FieldAttributeToSchema, toZodSchema } from "./to-zod.mjs";
|
|
8
8
|
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 };
|
|
9
|
+
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, toZodSchema };
|
package/dist/db/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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";
|
|
@@ -19,6 +19,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
19
19
|
parseAccountInput: () => parseAccountInput,
|
|
20
20
|
parseAccountOutput: () => parseAccountOutput,
|
|
21
21
|
parseAdditionalUserInput: () => parseAdditionalUserInput,
|
|
22
|
+
parseAdditionalUserInputFromProviderProfile: () => parseAdditionalUserInputFromProviderProfile,
|
|
22
23
|
parseInputData: () => parseInputData,
|
|
23
24
|
parseSessionInput: () => parseSessionInput,
|
|
24
25
|
parseSessionOutput: () => parseSessionOutput,
|
|
@@ -29,4 +30,4 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
29
30
|
import * as import__better_auth_core_db from "@better-auth/core/db";
|
|
30
31
|
__reExport(db_exports, import__better_auth_core_db);
|
|
31
32
|
//#endregion
|
|
32
|
-
export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
|
33
|
+
export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getIp } from "../utils/get-request-ip.mjs";
|
|
2
1
|
import { getSessionDefaultFields, parseSessionOutput, parseUserOutput } from "./schema.mjs";
|
|
3
2
|
import { getDate } from "../utils/date.mjs";
|
|
4
3
|
import { getStorageOption, processIdentifier } from "./verification-token-storage.mjs";
|
|
@@ -6,6 +5,7 @@ import { getWithHooks } from "./with-hooks.mjs";
|
|
|
6
5
|
import { getCurrentAdapter, getCurrentAuthContext, runWithTransaction } from "@better-auth/core/context";
|
|
7
6
|
import { generateId } from "@better-auth/core/utils/id";
|
|
8
7
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
8
|
+
import { getIp } from "@better-auth/core/utils/ip";
|
|
9
9
|
import { base64Url } from "@better-auth/utils/base64";
|
|
10
10
|
import { createHash } from "@better-auth/utils/hash";
|
|
11
11
|
//#region src/db/internal-adapter.ts
|
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,4 +1,5 @@
|
|
|
1
1
|
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
|
+
import { parseAdditionalUserInputFromProviderProfile } from "../db/schema.mjs";
|
|
2
3
|
import { setAccountCookie } from "../cookies/session-store.mjs";
|
|
3
4
|
import { redirectOnError } from "./errors.mjs";
|
|
4
5
|
import { setTokenUtil } from "./utils.mjs";
|
|
@@ -64,9 +65,12 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
64
65
|
if (userInfo.emailVerified && !dbUser.user.emailVerified && userInfo.email.toLowerCase() === dbUser.user.email) await c.context.internalAdapter.updateUser(dbUser.user.id, { emailVerified: true });
|
|
65
66
|
}
|
|
66
67
|
if (overrideUserInfo) {
|
|
67
|
-
const { id:
|
|
68
|
+
const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
|
|
69
|
+
const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
|
|
68
70
|
user = await c.context.internalAdapter.updateUser(dbUser.user.id, {
|
|
69
|
-
|
|
71
|
+
name,
|
|
72
|
+
image,
|
|
73
|
+
...additionalUserFields,
|
|
70
74
|
email: userInfo.email.toLowerCase(),
|
|
71
75
|
emailVerified: userInfo.email.toLowerCase() === dbUser.user.email ? dbUser.user.emailVerified || userInfo.emailVerified : userInfo.emailVerified
|
|
72
76
|
});
|
|
@@ -78,7 +82,8 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
78
82
|
isRegister: false
|
|
79
83
|
};
|
|
80
84
|
try {
|
|
81
|
-
const { id:
|
|
85
|
+
const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
|
|
86
|
+
const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "create");
|
|
82
87
|
const accountData = {
|
|
83
88
|
accessToken: await setTokenUtil(account.accessToken, c.context),
|
|
84
89
|
refreshToken: await setTokenUtil(account.refreshToken, c.context),
|
|
@@ -90,8 +95,11 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
90
95
|
accountId: userInfo.id.toString()
|
|
91
96
|
};
|
|
92
97
|
const { user: createdUser, account: createdAccount } = await c.context.internalAdapter.createOAuthUser({
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
name,
|
|
99
|
+
image,
|
|
100
|
+
...additionalUserFields,
|
|
101
|
+
email: userInfo.email.toLowerCase(),
|
|
102
|
+
emailVerified: userInfo.emailVerified
|
|
95
103
|
}, accountData);
|
|
96
104
|
user = createdUser;
|
|
97
105
|
if (c.context.options.account?.storeAccountCookie) await setAccountCookie(c, createdAccount);
|
|
@@ -152,9 +160,14 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
152
160
|
*/
|
|
153
161
|
async function applyUpdateUserInfoOnLink(c, userId, userInfo) {
|
|
154
162
|
if (c.context.options.account?.accountLinking?.updateUserInfoOnLink !== true) return;
|
|
155
|
-
const { id: _id, email: _email, emailVerified: _emailVerified, ...profile } = userInfo;
|
|
156
163
|
try {
|
|
157
|
-
|
|
164
|
+
const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo;
|
|
165
|
+
const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update");
|
|
166
|
+
return await c.context.internalAdapter.updateUser(userId, {
|
|
167
|
+
name,
|
|
168
|
+
image,
|
|
169
|
+
...additionalUserFields
|
|
170
|
+
});
|
|
158
171
|
} catch (e) {
|
|
159
172
|
c.context.logger.warn("Could not update user info on account link", e);
|
|
160
173
|
return;
|
package/dist/package.mjs
CHANGED
|
@@ -1,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 ? {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getIp } from "../../utils/get-request-ip.mjs";
|
|
2
1
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
3
2
|
import { middlewareResponse } from "../../utils/middleware-response.mjs";
|
|
4
3
|
import { Providers, defaultEndpoints, siteVerifyMap } from "./constants.mjs";
|
|
@@ -7,6 +6,7 @@ import { captchaFox } from "./verify-handlers/captchafox.mjs";
|
|
|
7
6
|
import { cloudflareTurnstile } from "./verify-handlers/cloudflare-turnstile.mjs";
|
|
8
7
|
import { googleRecaptcha } from "./verify-handlers/google-recaptcha.mjs";
|
|
9
8
|
import { hCaptcha } from "./verify-handlers/h-captcha.mjs";
|
|
9
|
+
import { getIp } from "@better-auth/core/utils/ip";
|
|
10
10
|
//#region src/plugins/captcha/index.ts
|
|
11
11
|
const captcha = (options) => ({
|
|
12
12
|
id: "captcha",
|
|
@@ -21,7 +21,7 @@ declare const deviceAuthorizationOptionsSchema: z.ZodObject<{
|
|
|
21
21
|
validateClient: z.ZodOptional<z.ZodCustom<(clientId: string) => boolean | Promise<boolean>, (clientId: string) => boolean | Promise<boolean>>>;
|
|
22
22
|
onDeviceAuthRequest: z.ZodOptional<z.ZodCustom<(clientId: string, scope: string | undefined) => void | Promise<void>, (clientId: string, scope: string | undefined) => void | Promise<void>>>;
|
|
23
23
|
verificationUri: z.ZodOptional<z.ZodString>;
|
|
24
|
-
schema: z.ZodCustom<{
|
|
24
|
+
schema: z.ZodOptional<z.ZodCustom<{
|
|
25
25
|
deviceCode?: {
|
|
26
26
|
modelName?: string | undefined;
|
|
27
27
|
fields?: {
|
|
@@ -51,7 +51,7 @@ declare const deviceAuthorizationOptionsSchema: z.ZodObject<{
|
|
|
51
51
|
scope?: string | undefined;
|
|
52
52
|
} | undefined;
|
|
53
53
|
} | undefined;
|
|
54
|
-
}
|
|
54
|
+
}>>;
|
|
55
55
|
}, z.core.$strip>;
|
|
56
56
|
type DeviceAuthorizationOptions = z.infer<typeof deviceAuthorizationOptionsSchema>;
|
|
57
57
|
declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions>) => {
|
|
@@ -399,7 +399,12 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
|
|
|
399
399
|
interval: TimeString;
|
|
400
400
|
deviceCodeLength: number;
|
|
401
401
|
userCodeLength: number;
|
|
402
|
-
|
|
402
|
+
generateDeviceCode?: (() => string | Promise<string>) | undefined;
|
|
403
|
+
generateUserCode?: (() => string | Promise<string>) | undefined;
|
|
404
|
+
validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined;
|
|
405
|
+
onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined;
|
|
406
|
+
verificationUri?: string | undefined;
|
|
407
|
+
schema?: {
|
|
403
408
|
deviceCode?: {
|
|
404
409
|
modelName?: string | undefined;
|
|
405
410
|
fields?: {
|
|
@@ -414,12 +419,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
|
|
|
414
419
|
scope?: string | undefined;
|
|
415
420
|
} | undefined;
|
|
416
421
|
} | undefined;
|
|
417
|
-
};
|
|
418
|
-
generateDeviceCode?: (() => string | Promise<string>) | undefined;
|
|
419
|
-
generateUserCode?: (() => string | Promise<string>) | undefined;
|
|
420
|
-
validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined;
|
|
421
|
-
onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined;
|
|
422
|
-
verificationUri?: string | undefined;
|
|
422
|
+
} | undefined;
|
|
423
423
|
}>;
|
|
424
424
|
};
|
|
425
425
|
//#endregion
|
|
@@ -25,7 +25,7 @@ const deviceAuthorizationOptionsSchema = z.object({
|
|
|
25
25
|
validateClient: z.custom((val) => typeof val === "function", { message: "validateClient must be a function that returns a boolean or a promise that resolves to a boolean." }).optional().describe("Function to validate the client ID. If not provided, no validation will be performed."),
|
|
26
26
|
onDeviceAuthRequest: z.custom((val) => typeof val === "function", { message: "onDeviceAuthRequest must be a function that returns void or a promise that resolves to void." }).optional().describe("Function to handle device authorization requests. If not provided, no additional actions will be taken."),
|
|
27
27
|
verificationUri: z.string().optional().describe("The URI where users verify their device code. Can be an absolute URL (https://example.com/device) or relative path (/custom-path). This will be returned as verification_uri in the device code response. If not provided, defaults to /device."),
|
|
28
|
-
schema: z.custom(() => true)
|
|
28
|
+
schema: z.custom(() => true).optional()
|
|
29
29
|
});
|
|
30
30
|
const deviceAuthorization = (options = {}) => {
|
|
31
31
|
const opts = deviceAuthorizationOptionsSchema.parse(options);
|
|
@@ -22,9 +22,9 @@ interface MagicLinkOptions {
|
|
|
22
22
|
* @deprecated Multi-attempt verification is no longer supported. Each
|
|
23
23
|
* magic link token is consumed atomically on the first verification call,
|
|
24
24
|
* so a given token mints at most one session regardless of this value
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* The option is kept for source compatibility and may be removed in a future
|
|
26
|
+
* major; any value other than `1` is ignored and emits a `console.warn` at
|
|
27
|
+
* plugin construction.
|
|
28
28
|
*
|
|
29
29
|
* @default 1
|
|
30
30
|
*/
|
|
@@ -27,7 +27,7 @@ const magicLink = (options) => {
|
|
|
27
27
|
allowedAttempts: 1,
|
|
28
28
|
...options
|
|
29
29
|
};
|
|
30
|
-
if (options.allowedAttempts !== void 0 && options.allowedAttempts !== 1) console.warn("[better-auth/magic-link] `allowedAttempts` is ignored: tokens are consumed atomically on the first verification call
|
|
30
|
+
if (options.allowedAttempts !== void 0 && options.allowedAttempts !== 1) console.warn("[better-auth/magic-link] `allowedAttempts` is ignored: tokens are consumed atomically on the first verification call. Any value other than `1` has no effect; remove the option to silence this warning.");
|
|
31
31
|
async function storeToken(ctx, token) {
|
|
32
32
|
if (opts.storeToken === "hashed") return await defaultKeyHasher(token);
|
|
33
33
|
if (typeof opts.storeToken === "object" && "type" in opts.storeToken && opts.storeToken.type === "custom-hasher") return await opts.storeToken.hash(token);
|
|
@@ -14,6 +14,15 @@ import { defu } from "defu";
|
|
|
14
14
|
import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
|
|
15
15
|
import * as z from "zod";
|
|
16
16
|
//#region src/plugins/oauth-proxy/index.ts
|
|
17
|
+
const consumeOAuthProxyState = async (ctx, state) => {
|
|
18
|
+
try {
|
|
19
|
+
await parseGenericState(ctx, state, { skipStateCookieCheck: true });
|
|
20
|
+
return true;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
ctx.context.logger.warn("OAuth proxy state missing or invalid", e);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
17
26
|
const oauthProxyQuerySchema = z.object({
|
|
18
27
|
callbackURL: z.string().meta({ description: "The URL to redirect to after the proxy" }),
|
|
19
28
|
profile: z.string().optional().meta({ description: "Encrypted OAuth profile data" })
|
|
@@ -81,7 +90,7 @@ const oAuthProxy = (opts) => {
|
|
|
81
90
|
ctx.context.logger.error("Failed to parse OAuth proxy payload", e);
|
|
82
91
|
throw redirectOnError(ctx, defaultErrorURL, "invalid_payload");
|
|
83
92
|
}
|
|
84
|
-
if (typeof payload.timestamp !== "number" || !payload.userInfo || !payload.account || !payload.callbackURL) {
|
|
93
|
+
if (typeof payload.timestamp !== "number" || !payload.userInfo || !payload.account || !payload.state || !payload.callbackURL) {
|
|
85
94
|
ctx.context.logger.error("Failed to parse OAuth proxy payload");
|
|
86
95
|
throw redirectOnError(ctx, defaultErrorURL, "invalid_payload");
|
|
87
96
|
}
|
|
@@ -91,11 +100,7 @@ const oAuthProxy = (opts) => {
|
|
|
91
100
|
ctx.context.logger.error(`OAuth proxy payload expired or invalid (age: ${age}s, maxAge: ${maxAge}s)`);
|
|
92
101
|
throw redirectOnError(ctx, errorURL, "payload_expired");
|
|
93
102
|
}
|
|
94
|
-
|
|
95
|
-
await parseGenericState(ctx, payload.state, { skipStateCookieCheck: true });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
ctx.context.logger.warn("Failed to clean up OAuth state", e);
|
|
98
|
-
}
|
|
103
|
+
if (!await consumeOAuthProxyState(ctx, payload.state)) throw redirectOnError(ctx, errorURL, "state_mismatch");
|
|
99
104
|
let result;
|
|
100
105
|
try {
|
|
101
106
|
result = await handleOAuthUserInfo(ctx, {
|
|
@@ -4,9 +4,9 @@ import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
|
4
4
|
import { APIError } from "../../api/index.mjs";
|
|
5
5
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
6
6
|
import { toBoolean } from "../../utils/boolean.mjs";
|
|
7
|
+
import { isGoogleHostedDomainAllowed, verifyGoogleIdToken } from "@better-auth/core/social-providers";
|
|
7
8
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
8
9
|
import * as z from "zod";
|
|
9
|
-
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
10
10
|
//#region src/plugins/one-tap/index.ts
|
|
11
11
|
const oneTapCallbackBodySchema = z.object({
|
|
12
12
|
idToken: z.string().meta({ description: "Google ID token, which the client obtains from the One Tap API" }),
|
|
@@ -40,14 +40,15 @@ const oneTap = (options) => ({
|
|
|
40
40
|
const googleProvider = typeof ctx.context.options.socialProviders?.google === "function" ? await ctx.context.options.socialProviders?.google() : ctx.context.options.socialProviders?.google;
|
|
41
41
|
const audience = options?.clientId || googleProvider?.clientId;
|
|
42
42
|
if (!audience || Array.isArray(audience) && audience.length === 0) throw new APIError("BAD_REQUEST", { message: "Google client ID is required for One Tap. Set it on the oneTap plugin (clientId) or on socialProviders.google." });
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
const payload = await verifyGoogleIdToken({
|
|
44
|
+
token: idToken,
|
|
45
|
+
audience
|
|
46
|
+
});
|
|
47
|
+
if (!payload) throw new APIError("BAD_REQUEST", { message: "invalid id token" });
|
|
48
|
+
if (!payload.sub) throw new APIError("BAD_REQUEST", { message: "invalid id token" });
|
|
49
|
+
const configuredHostedDomain = googleProvider?.hd;
|
|
50
|
+
if (!isGoogleHostedDomainAllowed(configuredHostedDomain, payload.hd)) {
|
|
51
|
+
ctx.context.logger.error(`Google One Tap sign-in rejected: id token hosted domain (hd) "${payload.hd ?? "<missing>"}" does not satisfy the configured "hd" option "${configuredHostedDomain}".`);
|
|
51
52
|
throw new APIError("BAD_REQUEST", { message: "invalid id token" });
|
|
52
53
|
}
|
|
53
54
|
const { email: rawEmail, email_verified, name, picture, sub } = payload;
|
|
@@ -166,7 +166,11 @@ const siwe = (options) => {
|
|
|
166
166
|
}
|
|
167
167
|
if (!user) {
|
|
168
168
|
const domain = options.emailDomainName ?? getOrigin(ctx.context.baseURL);
|
|
169
|
-
const
|
|
169
|
+
const normalizedEmail = email?.toLowerCase();
|
|
170
|
+
let userEmail = `${walletAddress}@${domain}`;
|
|
171
|
+
if (!isAnon && normalizedEmail) {
|
|
172
|
+
if (!await ctx.context.internalAdapter.findUserByEmail(normalizedEmail)) userEmail = normalizedEmail;
|
|
173
|
+
}
|
|
170
174
|
const { name, avatar } = await options.ensLookup?.({ walletAddress }) ?? {};
|
|
171
175
|
user = await ctx.context.internalAdapter.createUser({
|
|
172
176
|
name: name ?? walletAddress,
|
|
@@ -161,8 +161,9 @@ const backupCode2fa = (opts) => {
|
|
|
161
161
|
} }
|
|
162
162
|
} }
|
|
163
163
|
}, async (ctx) => {
|
|
164
|
-
const { session, valid } = await verifyTwoFactor(ctx);
|
|
164
|
+
const { session, valid, beginAttempt } = await verifyTwoFactor(ctx);
|
|
165
165
|
const user = session.user;
|
|
166
|
+
const isSignIn = !session.session;
|
|
166
167
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
167
168
|
model: twoFactorTable,
|
|
168
169
|
where: [{
|
|
@@ -171,11 +172,21 @@ const backupCode2fa = (opts) => {
|
|
|
171
172
|
}]
|
|
172
173
|
});
|
|
173
174
|
if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED);
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
const attempt = isSignIn ? await beginAttempt(5) : null;
|
|
176
|
+
let validate;
|
|
177
|
+
try {
|
|
178
|
+
validate = await verifyBackupCode({
|
|
179
|
+
backupCodes: twoFactor.backupCodes,
|
|
180
|
+
code: ctx.body.code
|
|
181
|
+
}, ctx.context.secretConfig, opts);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await attempt?.restore();
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
if (!validate.status || !validate.updated) {
|
|
187
|
+
await attempt?.recordFailure();
|
|
188
|
+
throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE);
|
|
189
|
+
}
|
|
179
190
|
const updatedBackupCodes = await encodeBackupCodes(validate.updated, ctx.context.secretConfig, opts);
|
|
180
191
|
if (!await ctx.context.adapter.incrementOne({
|
|
181
192
|
model: twoFactorTable,
|
|
@@ -236,10 +236,16 @@ const twoFactor = (options) => {
|
|
|
236
236
|
const maxAge = options?.twoFactorCookieMaxAge ?? 600;
|
|
237
237
|
const twoFactorCookie = ctx.context.createAuthCookie(TWO_FACTOR_COOKIE_NAME, { maxAge });
|
|
238
238
|
const identifier = `2fa-${generateRandomString(20)}`;
|
|
239
|
+
const expiresAt = new Date(Date.now() + maxAge * 1e3);
|
|
239
240
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
240
241
|
value: data.user.id,
|
|
241
242
|
identifier,
|
|
242
|
-
expiresAt
|
|
243
|
+
expiresAt
|
|
244
|
+
});
|
|
245
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
246
|
+
value: "0",
|
|
247
|
+
identifier: `2fa-attempts-${identifier}`,
|
|
248
|
+
expiresAt
|
|
243
249
|
});
|
|
244
250
|
await ctx.setSignedCookie(twoFactorCookie.name, identifier, ctx.context.secret, twoFactorCookie.attributes);
|
|
245
251
|
const twoFactorMethods = [];
|
|
@@ -122,7 +122,7 @@ const totp2fa = (options) => {
|
|
|
122
122
|
code: "TOTP_NOT_CONFIGURED"
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
|
-
const { session, valid, invalid } = await verifyTwoFactor(ctx);
|
|
125
|
+
const { session, valid, invalid, beginAttempt } = await verifyTwoFactor(ctx);
|
|
126
126
|
const user = session.user;
|
|
127
127
|
const isSignIn = !session.session;
|
|
128
128
|
const twoFactor = await ctx.context.adapter.findOne({
|
|
@@ -134,13 +134,24 @@ const totp2fa = (options) => {
|
|
|
134
134
|
});
|
|
135
135
|
if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED);
|
|
136
136
|
if (isSignIn && twoFactor.verified === false) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
const attempt = isSignIn ? await beginAttempt(5) : null;
|
|
138
|
+
let status;
|
|
139
|
+
try {
|
|
140
|
+
status = await createOTP(await symmetricDecrypt({
|
|
141
|
+
key: ctx.context.secretConfig,
|
|
142
|
+
data: twoFactor.secret
|
|
143
|
+
}), {
|
|
144
|
+
period: opts.period,
|
|
145
|
+
digits: opts.digits
|
|
146
|
+
}).verify(ctx.body.code);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await attempt?.restore();
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
if (!status) {
|
|
152
|
+
await attempt?.recordFailure();
|
|
153
|
+
return invalid("INVALID_CODE");
|
|
154
|
+
}
|
|
144
155
|
if (twoFactor.verified !== true) {
|
|
145
156
|
if (!user.twoFactorEnabled) {
|
|
146
157
|
const activeSession = session.session;
|
|
@@ -66,7 +66,28 @@ async function verifyTwoFactor(ctx) {
|
|
|
66
66
|
session: null,
|
|
67
67
|
user
|
|
68
68
|
},
|
|
69
|
-
key: signedTwoFactorCookie
|
|
69
|
+
key: signedTwoFactorCookie,
|
|
70
|
+
beginAttempt: async (allowedAttempts) => {
|
|
71
|
+
const identifier = `2fa-attempts-${signedTwoFactorCookie}`;
|
|
72
|
+
const consumed = await ctx.context.internalAdapter.consumeVerificationValue(identifier).catch(() => null);
|
|
73
|
+
if (!consumed) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE);
|
|
74
|
+
const parsed = Number(consumed.value);
|
|
75
|
+
const attempts = Number.isInteger(parsed) && parsed >= 0 ? parsed : allowedAttempts;
|
|
76
|
+
if (attempts >= allowedAttempts) {
|
|
77
|
+
await ctx.context.internalAdapter.consumeVerificationValue(signedTwoFactorCookie).catch(() => {});
|
|
78
|
+
expireCookie(ctx, twoFactorCookie);
|
|
79
|
+
throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE);
|
|
80
|
+
}
|
|
81
|
+
const rearm = (count) => ctx.context.internalAdapter.createVerificationValue({
|
|
82
|
+
value: `${count}`,
|
|
83
|
+
identifier,
|
|
84
|
+
expiresAt: verificationToken.expiresAt
|
|
85
|
+
}).catch(() => {});
|
|
86
|
+
return {
|
|
87
|
+
recordFailure: () => rearm(attempts + 1),
|
|
88
|
+
restore: () => rearm(attempts)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
70
91
|
};
|
|
71
92
|
}
|
|
72
93
|
return {
|
|
@@ -78,7 +99,11 @@ async function verifyTwoFactor(ctx) {
|
|
|
78
99
|
},
|
|
79
100
|
invalid,
|
|
80
101
|
session,
|
|
81
|
-
key: `${session.user.id}!${session.session.id}
|
|
102
|
+
key: `${session.user.id}!${session.session.id}`,
|
|
103
|
+
beginAttempt: async (_allowedAttempts) => ({
|
|
104
|
+
recordFailure: async () => {},
|
|
105
|
+
restore: async () => {}
|
|
106
|
+
})
|
|
82
107
|
};
|
|
83
108
|
}
|
|
84
109
|
//#endregion
|
|
@@ -32,11 +32,17 @@ const username = (options) => {
|
|
|
32
32
|
const maxUsernameLength = options?.maxUsernameLength || 30;
|
|
33
33
|
const validator = options?.usernameValidator || defaultUsernameValidator;
|
|
34
34
|
const pathsWithHttpHookValidation = ["/sign-up/email", "/update-user"];
|
|
35
|
+
const getUsernameToValidate = (username) => options?.validationOrder?.username === "post-normalization" ? normalizer(username) : username;
|
|
36
|
+
async function validateUsernameValue(username) {
|
|
37
|
+
const usernameToValidate = getUsernameToValidate(username);
|
|
38
|
+
if (usernameToValidate.length < minUsernameLength) return USERNAME_ERROR_CODES.USERNAME_TOO_SHORT;
|
|
39
|
+
if (usernameToValidate.length > maxUsernameLength) return USERNAME_ERROR_CODES.USERNAME_TOO_LONG;
|
|
40
|
+
if (!await validator(usernameToValidate)) return USERNAME_ERROR_CODES.INVALID_USERNAME;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
35
43
|
async function validateUsername(username, displayUsername, adapter, currentUserId) {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
if (usernameToValidate.length > maxUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_LONG);
|
|
39
|
-
if (!await validator(usernameToValidate)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_USERNAME);
|
|
44
|
+
const validationError = await validateUsernameValue(username);
|
|
45
|
+
if (validationError) throw APIError.from("BAD_REQUEST", validationError);
|
|
40
46
|
const normalizedUsername = normalizer(username);
|
|
41
47
|
const existingUser = await adapter.findOne({
|
|
42
48
|
model: "user",
|
|
@@ -247,48 +253,56 @@ const username = (options) => {
|
|
|
247
253
|
username: normalizer,
|
|
248
254
|
displayUsername: displayUsernameNormalizer
|
|
249
255
|
}), options?.schema),
|
|
250
|
-
hooks: { before: [
|
|
251
|
-
|
|
252
|
-
|
|
256
|
+
hooks: { before: [
|
|
257
|
+
{
|
|
258
|
+
matcher(context) {
|
|
259
|
+
return context.path === "/sign-up/email";
|
|
260
|
+
},
|
|
261
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
262
|
+
if (typeof ctx.body.displayUsername !== "string" || ctx.body.username !== void 0) return;
|
|
263
|
+
if (!await validateUsernameValue(ctx.body.displayUsername)) ctx.body.username = ctx.body.displayUsername;
|
|
264
|
+
})
|
|
253
265
|
},
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (username
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
266
|
+
{
|
|
267
|
+
matcher(context) {
|
|
268
|
+
return context.path === "/sign-up/email" || context.path === "/update-user";
|
|
269
|
+
},
|
|
270
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
271
|
+
const username = ctx.body.username;
|
|
272
|
+
if (username !== void 0 && typeof username === "string") {
|
|
273
|
+
const validationError = await validateUsernameValue(username);
|
|
274
|
+
if (validationError) throw APIError.from("BAD_REQUEST", validationError);
|
|
275
|
+
const normalizedUsername = normalizer(username);
|
|
276
|
+
const existingUser = await ctx.context.adapter.findOne({
|
|
277
|
+
model: "user",
|
|
278
|
+
where: [{
|
|
279
|
+
field: "username",
|
|
280
|
+
value: normalizedUsername
|
|
281
|
+
}]
|
|
282
|
+
});
|
|
283
|
+
if (ctx.path === "/sign-up/email" && existingUser) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN);
|
|
284
|
+
if (ctx.path === "/update-user" && existingUser) {
|
|
285
|
+
const session = await getSessionFromCtx(ctx);
|
|
286
|
+
if (!session || existingUser.id !== session.user.id) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN);
|
|
287
|
+
}
|
|
274
288
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
289
|
+
const displayUsername = typeof ctx.body.displayUsername === "string" && options?.validationOrder?.displayUsername === "post-normalization" ? displayUsernameNormalizer(ctx.body.displayUsername) : ctx.body.displayUsername;
|
|
290
|
+
if (displayUsername !== void 0 && typeof displayUsername === "string") {
|
|
291
|
+
if (options?.displayUsernameValidator) {
|
|
292
|
+
if (!await options.displayUsernameValidator(displayUsername)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_DISPLAY_USERNAME);
|
|
293
|
+
}
|
|
280
294
|
}
|
|
281
|
-
}
|
|
282
|
-
})
|
|
283
|
-
}, {
|
|
284
|
-
matcher(context) {
|
|
285
|
-
return context.path === "/sign-up/email";
|
|
295
|
+
})
|
|
286
296
|
},
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
{
|
|
298
|
+
matcher(context) {
|
|
299
|
+
return context.path === "/sign-up/email";
|
|
300
|
+
},
|
|
301
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
302
|
+
if (ctx.body.username && !ctx.body.displayUsername) ctx.body.displayUsername = ctx.body.username;
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
] },
|
|
292
306
|
options,
|
|
293
307
|
$ERROR_CODES: USERNAME_ERROR_CODES
|
|
294
308
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.21",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -483,19 +483,19 @@
|
|
|
483
483
|
"@better-fetch/fetch": "1.3.1",
|
|
484
484
|
"@noble/ciphers": "^2.1.1",
|
|
485
485
|
"@noble/hashes": "^2.0.1",
|
|
486
|
-
"better-call": "1.3.
|
|
486
|
+
"better-call": "1.3.7",
|
|
487
487
|
"defu": "^6.1.4",
|
|
488
488
|
"jose": "^6.1.3",
|
|
489
489
|
"kysely": "^0.28.17 || ^0.29.0",
|
|
490
490
|
"nanostores": "^1.1.1",
|
|
491
491
|
"zod": "^4.3.6",
|
|
492
|
-
"@better-auth/core": "1.6.
|
|
493
|
-
"@better-auth/drizzle-adapter": "1.6.
|
|
494
|
-
"@better-auth/kysely-adapter": "1.6.
|
|
495
|
-
"@better-auth/memory-adapter": "1.6.
|
|
496
|
-
"@better-auth/mongo-adapter": "1.6.
|
|
497
|
-
"@better-auth/prisma-adapter": "1.6.
|
|
498
|
-
"@better-auth/telemetry": "1.6.
|
|
492
|
+
"@better-auth/core": "1.6.21",
|
|
493
|
+
"@better-auth/drizzle-adapter": "1.6.21",
|
|
494
|
+
"@better-auth/kysely-adapter": "1.6.21",
|
|
495
|
+
"@better-auth/memory-adapter": "1.6.21",
|
|
496
|
+
"@better-auth/mongo-adapter": "1.6.21",
|
|
497
|
+
"@better-auth/prisma-adapter": "1.6.21",
|
|
498
|
+
"@better-auth/telemetry": "1.6.21"
|
|
499
499
|
},
|
|
500
500
|
"devDependencies": {
|
|
501
501
|
"@lynx-js/react": "^0.116.3",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { isDevelopment, isTest } from "@better-auth/core/env";
|
|
2
|
-
import { isValidIP, normalizeIP } from "@better-auth/core/utils/ip";
|
|
3
|
-
//#region src/utils/get-request-ip.ts
|
|
4
|
-
const LOCALHOST_IP = "127.0.0.1";
|
|
5
|
-
function getIp(req, options) {
|
|
6
|
-
if (options.advanced?.ipAddress?.disableIpTracking) return null;
|
|
7
|
-
const headers = "headers" in req ? req.headers : req;
|
|
8
|
-
const ipHeaders = options.advanced?.ipAddress?.ipAddressHeaders || ["x-forwarded-for"];
|
|
9
|
-
for (const key of ipHeaders) {
|
|
10
|
-
const value = "get" in headers ? headers.get(key) : headers[key];
|
|
11
|
-
if (typeof value === "string") {
|
|
12
|
-
const ip = value.split(",")[0].trim();
|
|
13
|
-
if (isValidIP(ip)) return normalizeIP(ip, { ipv6Subnet: options.advanced?.ipAddress?.ipv6Subnet });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
if (isTest() || isDevelopment()) return LOCALHOST_IP;
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
export { getIp };
|