better-auth
Advanced tools
| import { isAPIError } from "../utils/is-api-error.mjs"; | ||
| import { requireOrgRole, requireResourceOwnership } from "./middlewares/authorization.mjs"; | ||
| import { formCsrfMiddleware, originCheck, originCheckMiddleware } from "./middlewares/origin-check.mjs"; | ||
| import { getIp } from "../utils/get-request-ip.mjs"; | ||
| import { onRequestRateLimit } from "./rate-limiter/index.mjs"; | ||
| import { getOAuthState } from "./state/oauth.mjs"; | ||
| import { getShouldSkipSessionRefresh, setShouldSkipSessionRefresh } from "./state/should-session-refresh.mjs"; | ||
| import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs"; | ||
| import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs"; | ||
| import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs"; | ||
@@ -24,2 +23,3 @@ import { callbackOAuth } from "./routes/callback.mjs"; | ||
| import { APIError } from "@better-auth/core/error"; | ||
| import { getIp } from "@better-auth/core/utils/ip"; | ||
| import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, withSpan } from "@better-auth/core/instrumentation"; | ||
@@ -169,2 +169,4 @@ import { normalizePathname } from "@better-auth/core/utils/url"; | ||
| let currentRequest = req; | ||
| const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx); | ||
| if (rateLimitResponse) return rateLimitResponse; | ||
| for (const plugin of ctx.options.plugins || []) if (plugin.onRequest) { | ||
@@ -178,4 +180,2 @@ const response = await withSpan(`onRequest ${plugin.id}`, { | ||
| } | ||
| const rateLimitResponse = await onRequestRateLimit(currentRequest, ctx); | ||
| if (rateLimitResponse) return rateLimitResponse; | ||
| return currentRequest; | ||
@@ -219,2 +219,2 @@ }, | ||
| //#endregion | ||
| 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 }; | ||
| 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 }; |
| import { wildcardMatch } from "../../utils/wildcard.mjs"; | ||
| import { getIp } from "../../utils/get-request-ip.mjs"; | ||
| import { safeJSONParse } from "@better-auth/core/utils/json"; | ||
| import { createRateLimitKey, getIp } from "@better-auth/core/utils/ip"; | ||
| import { normalizePathname } from "@better-auth/core/utils/url"; | ||
| import { createRateLimitKey } from "@better-auth/core/utils/ip"; | ||
| //#region src/api/rate-limiter/index.ts | ||
@@ -285,3 +284,3 @@ const memory = /* @__PURE__ */ new Map(); | ||
| if (!ip && !ipWarningLogged) { | ||
| ctx.logger.warn("Rate limiting could not determine a client IP and is falling back to a single shared per-path bucket. Ensure your runtime forwards a trusted client IP header and configure `advanced.ipAddress.ipAddressHeaders` if needed."); | ||
| 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."); | ||
| ipWarningLogged = true; | ||
@@ -288,0 +287,0 @@ } |
@@ -7,3 +7,3 @@ import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./account.mjs"; | ||
| import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./password.mjs"; | ||
| import { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs"; | ||
| import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs"; | ||
| import { signInEmail, signInSocial } from "./sign-in.mjs"; | ||
@@ -10,0 +10,0 @@ import { signOut } from "./sign-out.mjs"; |
@@ -67,2 +67,30 @@ import { Prettify as Prettify$1 } from "../../types/helper.mjs"; | ||
| /** | ||
| * Reads the session from the source that can authorize sensitive work. | ||
| * | ||
| * Stateful deployments must re-read the server-side session store because an | ||
| * earlier hook may have populated `ctx.context.session` from cookie cache. | ||
| * Stateless deployments keep the signed cookie as the session record. | ||
| */ | ||
| declare const getAuthoritativeSessionFromCtx: <U extends Record<string, any> = Record<string, any>, S extends Record<string, any> = Record<string, any>>(ctx: GenericEndpointContext) => Promise<{ | ||
| session: S & { | ||
| id: string; | ||
| createdAt: Date; | ||
| updatedAt: Date; | ||
| userId: string; | ||
| expiresAt: Date; | ||
| token: string; | ||
| ipAddress?: string | null | undefined; | ||
| userAgent?: string | null | undefined; | ||
| }; | ||
| user: U & { | ||
| id: string; | ||
| createdAt: Date; | ||
| updatedAt: Date; | ||
| email: string; | ||
| emailVerified: boolean; | ||
| name: string; | ||
| image?: string | null | undefined; | ||
| }; | ||
| } | null>; | ||
| /** | ||
| * The middleware forces the endpoint to require a valid session. | ||
@@ -94,5 +122,4 @@ */ | ||
| /** | ||
| * This middleware forces the endpoint to require a valid session and ignores cookie cache. | ||
| * This middleware forces the endpoint to require a valid authoritative session. | ||
| * This should be used for sensitive operations like password changes, account deletion, etc. | ||
| * to ensure that revoked sessions cannot be used even if they're still cached in cookies. | ||
| */ | ||
@@ -425,2 +452,2 @@ declare const sensitiveSessionMiddleware: (inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{ | ||
| //#endregion | ||
| export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware }; | ||
| export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware }; |
@@ -302,2 +302,14 @@ import { isAPIError } from "../../utils/is-api-error.mjs"; | ||
| /** | ||
| * Reads the session from the source that can authorize sensitive work. | ||
| * | ||
| * Stateful deployments must re-read the server-side session store because an | ||
| * earlier hook may have populated `ctx.context.session` from cookie cache. | ||
| * Stateless deployments keep the signed cookie as the session record. | ||
| */ | ||
| const getAuthoritativeSessionFromCtx = async (ctx) => { | ||
| if (!isStateful(ctx)) return getSessionFromCtx(ctx); | ||
| ctx.context.session = null; | ||
| return getSessionFromCtx(ctx, { disableCookieCache: true }); | ||
| }; | ||
| /** | ||
| * The middleware forces the endpoint to require a valid session. | ||
@@ -314,8 +326,7 @@ */ | ||
| /** | ||
| * This middleware forces the endpoint to require a valid session and ignores cookie cache. | ||
| * This middleware forces the endpoint to require a valid authoritative session. | ||
| * This should be used for sensitive operations like password changes, account deletion, etc. | ||
| * to ensure that revoked sessions cannot be used even if they're still cached in cookies. | ||
| */ | ||
| const sensitiveSessionMiddleware = createAuthMiddleware(async (ctx) => { | ||
| const session = await getSessionFromCtx(ctx, { disableCookieCache: true }); | ||
| const session = await getAuthoritativeSessionFromCtx(ctx); | ||
| if (!session?.session) throw APIError.from("UNAUTHORIZED", { | ||
@@ -496,2 +507,2 @@ message: "Unauthorized", | ||
| //#endregion | ||
| export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware }; | ||
| export { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware }; |
@@ -19,2 +19,3 @@ import { getBaseURL, isDynamicBaseURLConfig } from "../utils/url.mjs"; | ||
| import { socialProviders } from "@better-auth/core/social-providers"; | ||
| import { findInvalidTrustedProxies } from "@better-auth/core/utils/ip"; | ||
| import { createTelemetry } from "@better-auth/telemetry"; | ||
@@ -90,2 +91,7 @@ import defu$1 from "defu"; | ||
| checkEndpointConflicts(options, logger); | ||
| const trustedProxies = options.advanced?.ipAddress?.trustedProxies; | ||
| if (trustedProxies && trustedProxies.length > 0) { | ||
| const invalid = findInvalidTrustedProxies(trustedProxies); | ||
| if (invalid.length > 0) logger.warn(`Ignoring invalid \`advanced.ipAddress.trustedProxies\` entries: ${invalid.join(", ")}. Each entry must be an IP address or CIDR range.`); | ||
| } | ||
| const cookies = getCookies(options); | ||
@@ -92,0 +98,0 @@ const tables = getAuthTables(options); |
@@ -151,2 +151,3 @@ import { getSchema } from "./get-schema.mjs"; | ||
| for (const [key, value] of Object.entries(betterAuthSchema)) { | ||
| if (value.disableMigrations) continue; | ||
| const table = tableMetadata.find((t) => t.name === key); | ||
@@ -153,0 +154,0 @@ if (!table) { |
@@ -8,4 +8,5 @@ import { BetterAuthOptions } from "@better-auth/core"; | ||
| order: number; | ||
| disableMigrations?: boolean | undefined; | ||
| }>; | ||
| //#endregion | ||
| export { getSchema }; |
@@ -26,2 +26,3 @@ import { getAuthTables } from "@better-auth/core/db"; | ||
| }; | ||
| if (table.disableMigrations) schema[table.modelName].disableMigrations = true; | ||
| continue; | ||
@@ -31,3 +32,4 @@ } | ||
| fields: actualFields, | ||
| order: table.order || Infinity | ||
| order: table.order || Infinity, | ||
| disableMigrations: table.disableMigrations | ||
| }; | ||
@@ -34,0 +36,0 @@ } |
@@ -6,5 +6,5 @@ import { FieldAttributeToObject, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse } from "./field.mjs"; | ||
| import { createInternalAdapter } from "./internal-adapter.mjs"; | ||
| import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs"; | ||
| import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs"; | ||
| import { FieldAttributeToSchema, toZodSchema } from "./to-zod.mjs"; | ||
| export * from "@better-auth/core/db"; | ||
| 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 }; | ||
| 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 }; |
| import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs"; | ||
| import { getSchema } from "./get-schema.mjs"; | ||
| import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs"; | ||
| import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs"; | ||
| import { convertFromDB, convertToDB } from "./field-converter.mjs"; | ||
@@ -22,2 +22,3 @@ import { getWithHooks } from "./with-hooks.mjs"; | ||
| parseAdditionalUserInput: () => parseAdditionalUserInput, | ||
| parseAdditionalUserInputFromProviderProfile: () => parseAdditionalUserInputFromProviderProfile, | ||
| parseInputData: () => parseInputData, | ||
@@ -33,2 +34,2 @@ parseSessionInput: () => parseSessionInput, | ||
| //#endregion | ||
| export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema }; | ||
| export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema }; |
@@ -1,2 +0,1 @@ | ||
| import { getIp } from "../utils/get-request-ip.mjs"; | ||
| import { getSessionDefaultFields, parseSessionOutput, parseUserOutput } from "./schema.mjs"; | ||
@@ -9,2 +8,3 @@ import { getDate } from "../utils/date.mjs"; | ||
| import { safeJSONParse } from "@better-auth/core/utils/json"; | ||
| import { getIp } from "@better-auth/core/utils/ip"; | ||
| import { base64Url } from "@better-auth/utils/base64"; | ||
@@ -11,0 +11,0 @@ import { createHash } from "@better-auth/utils/hash"; |
@@ -27,2 +27,3 @@ import { Account, Session as Session$1, User as User$1 } from "../types/models.mjs"; | ||
| declare function parseUserInput(options: BetterAuthOptions, user: Record<string, any> | undefined, action: "create" | "update"): Partial<Record<string, any>>; | ||
| declare function parseAdditionalUserInputFromProviderProfile(options: BetterAuthOptions, profile: Record<string, unknown> | undefined, action: "create" | "update"): Partial<Record<string, unknown>>; | ||
| declare function parseAdditionalUserInput(options: BetterAuthOptions, user?: Record<string, any> | undefined): Partial<Record<string, any>>; | ||
@@ -62,2 +63,2 @@ declare function parseAccountInput(options: BetterAuthOptions, account: Partial<Account>): Partial<Partial<{ | ||
| //#endregion | ||
| export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput }; | ||
| export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput }; |
+13
-1
@@ -116,2 +116,14 @@ import { getAuthTables } from "@better-auth/core/db"; | ||
| } | ||
| function parseAdditionalUserInputFromProviderProfile(options, profile = {}, action) { | ||
| const schema = getFields(options, "user", "input"); | ||
| const allowedProfileFields = Object.create(null); | ||
| for (const key of Object.keys(profile)) { | ||
| if (schema[key]?.input === false) continue; | ||
| allowedProfileFields[key] = profile[key]; | ||
| } | ||
| return parseInputData(allowedProfileFields, { | ||
| fields: schema, | ||
| action | ||
| }); | ||
| } | ||
| function parseAdditionalUserInput(options, user) { | ||
@@ -150,2 +162,2 @@ const schema = getFields(options, "user", "input"); | ||
| //#endregion | ||
| export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput }; | ||
| export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput }; |
| import { isAPIError } from "../utils/is-api-error.mjs"; | ||
| import { parseAdditionalUserInputFromProviderProfile } from "../db/schema.mjs"; | ||
| import { setAccountCookie } from "../cookies/session-store.mjs"; | ||
@@ -67,5 +68,8 @@ import { redirectOnError } from "./errors.mjs"; | ||
| if (overrideUserInfo) { | ||
| const { id: _, ...restUserInfo } = userInfo; | ||
| const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo; | ||
| const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update"); | ||
| user = await c.context.internalAdapter.updateUser(dbUser.user.id, { | ||
| ...restUserInfo, | ||
| name, | ||
| image, | ||
| ...additionalUserFields, | ||
| email: userInfo.email.toLowerCase(), | ||
@@ -82,3 +86,4 @@ emailVerified: userInfo.email.toLowerCase() === dbUser.user.email ? dbUser.user.emailVerified || userInfo.emailVerified : userInfo.emailVerified | ||
| try { | ||
| const { id: _, ...restUserInfo } = userInfo; | ||
| const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo; | ||
| const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "create"); | ||
| const accountData = { | ||
@@ -95,4 +100,7 @@ accessToken: await setTokenUtil(account.accessToken, c.context), | ||
| const { user: createdUser, account: createdAccount } = await c.context.internalAdapter.createOAuthUser({ | ||
| ...restUserInfo, | ||
| email: userInfo.email.toLowerCase() | ||
| name, | ||
| image, | ||
| ...additionalUserFields, | ||
| email: userInfo.email.toLowerCase(), | ||
| emailVerified: userInfo.emailVerified | ||
| }, accountData); | ||
@@ -158,5 +166,10 @@ user = createdUser; | ||
| if (c.context.options.account?.accountLinking?.updateUserInfoOnLink !== true) return; | ||
| const { id: _id, email: _email, emailVerified: _emailVerified, ...profile } = userInfo; | ||
| try { | ||
| return await c.context.internalAdapter.updateUser(userId, profile); | ||
| const { id: _id, email: _email, emailVerified: _emailVerified, name, image, ...providerProfile } = userInfo; | ||
| const additionalUserFields = parseAdditionalUserInputFromProviderProfile(c.context.options, providerProfile, "update"); | ||
| return await c.context.internalAdapter.updateUser(userId, { | ||
| name, | ||
| image, | ||
| ...additionalUserFields | ||
| }); | ||
| } catch (e) { | ||
@@ -163,0 +176,0 @@ c.context.logger.warn("Could not update user info on account link", e); |
+1
-1
| //#region package.json | ||
| var version = "1.6.20"; | ||
| var version = "1.6.21"; | ||
| //#endregion | ||
| export { version }; |
| import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs"; | ||
| import { getDate } from "../../utils/date.mjs"; | ||
| import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs"; | ||
| import { getSessionFromCtx } from "../../api/routes/session.mjs"; | ||
| import { getAuthoritativeSessionFromCtx, getSessionFromCtx } from "../../api/routes/session.mjs"; | ||
| import { ADMIN_ERROR_CODES } from "./error-codes.mjs"; | ||
@@ -17,3 +17,3 @@ import { hasPermission } from "./has-permission.mjs"; | ||
| const adminMiddleware = createAuthMiddleware(async (ctx) => { | ||
| const session = await getSessionFromCtx(ctx); | ||
| const session = await getAuthoritativeSessionFromCtx(ctx); | ||
| if (!session) throw APIError.fromStatus("UNAUTHORIZED"); | ||
@@ -150,3 +150,3 @@ return { session }; | ||
| }, async (ctx) => { | ||
| const session = await getSessionFromCtx(ctx); | ||
| const session = await getAuthoritativeSessionFromCtx(ctx); | ||
| if (!session && (ctx.request || ctx.headers)) throw ctx.error("UNAUTHORIZED"); | ||
@@ -890,3 +890,3 @@ if (session) { | ||
| if (!ctx.body?.permissions) throw new APIError("BAD_REQUEST", { message: "invalid permission check. no permission(s) were passed." }); | ||
| const session = await getSessionFromCtx(ctx); | ||
| const session = await getAuthoritativeSessionFromCtx(ctx); | ||
| if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED"); | ||
@@ -893,0 +893,0 @@ if (!session && !ctx.body.userId && !ctx.body.role) throw new APIError("BAD_REQUEST", { message: "user id or role is required" }); |
@@ -1,2 +0,1 @@ | ||
| import { getIp } from "../../utils/get-request-ip.mjs"; | ||
| import { PACKAGE_VERSION } from "../../version.mjs"; | ||
@@ -10,2 +9,3 @@ import { middlewareResponse } from "../../utils/middleware-response.mjs"; | ||
| import { hCaptcha } from "./verify-handlers/h-captcha.mjs"; | ||
| import { getIp } from "@better-auth/core/utils/ip"; | ||
| //#region src/plugins/captcha/index.ts | ||
@@ -12,0 +12,0 @@ const captcha = (options) => ({ |
@@ -24,3 +24,3 @@ import { TimeString, ms, sec } from "../../utils/time.mjs"; | ||
| verificationUri: z.ZodOptional<z.ZodString>; | ||
| schema: z.ZodCustom<{ | ||
| schema: z.ZodOptional<z.ZodCustom<{ | ||
| deviceCode?: { | ||
@@ -55,3 +55,3 @@ modelName?: string | undefined; | ||
| } | undefined; | ||
| }>; | ||
| }>>; | ||
| }, z.core.$strip>; | ||
@@ -404,3 +404,8 @@ type DeviceAuthorizationOptions = z.infer<typeof deviceAuthorizationOptionsSchema>; | ||
| userCodeLength: number; | ||
| schema: { | ||
| generateDeviceCode?: (() => string | Promise<string>) | undefined; | ||
| generateUserCode?: (() => string | Promise<string>) | undefined; | ||
| validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined; | ||
| onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined; | ||
| verificationUri?: string | undefined; | ||
| schema?: { | ||
| deviceCode?: { | ||
@@ -420,8 +425,3 @@ modelName?: string | undefined; | ||
| } | undefined; | ||
| }; | ||
| generateDeviceCode?: (() => string | Promise<string>) | undefined; | ||
| generateUserCode?: (() => string | Promise<string>) | undefined; | ||
| validateClient?: ((clientId: string) => boolean | Promise<boolean>) | undefined; | ||
| onDeviceAuthRequest?: ((clientId: string, scope: string | undefined) => void | Promise<void>) | undefined; | ||
| verificationUri?: string | undefined; | ||
| } | undefined; | ||
| }>; | ||
@@ -428,0 +428,0 @@ }; |
@@ -28,3 +28,3 @@ import { mergeSchema } from "../../db/schema.mjs"; | ||
| 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."), | ||
| schema: z.custom(() => true) | ||
| schema: z.custom(() => true).optional() | ||
| }); | ||
@@ -31,0 +31,0 @@ const deviceAuthorization = (options = {}) => { |
@@ -25,5 +25,5 @@ import { Awaitable, GenericEndpointContext } from "@better-auth/core"; | ||
| * so a given token mints at most one session regardless of this value | ||
| * (see GHSA-hc7v-rggr-4hvx). The option is kept for source compatibility | ||
| * and may be removed in a future major; any value other than `1` is | ||
| * ignored and emits a `console.warn` at plugin construction. | ||
| * The option is kept for source compatibility and may be removed in a future | ||
| * major; any value other than `1` is ignored and emits a `console.warn` at | ||
| * plugin construction. | ||
| * | ||
@@ -30,0 +30,0 @@ * @default 1 |
@@ -30,3 +30,3 @@ import { originCheck } from "../../api/middlewares/origin-check.mjs"; | ||
| }; | ||
| 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 (GHSA-hc7v-rggr-4hvx). Any value other than `1` has no effect; remove the option to silence this warning."); | ||
| 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."); | ||
| async function storeToken(ctx, token) { | ||
@@ -33,0 +33,0 @@ if (opts.storeToken === "hashed") return await defaultKeyHasher(token); |
@@ -17,2 +17,11 @@ import { isAPIError } from "../../utils/is-api-error.mjs"; | ||
| //#region src/plugins/oauth-proxy/index.ts | ||
| const consumeOAuthProxyState = async (ctx, state) => { | ||
| try { | ||
| await parseGenericState(ctx, state, { skipStateCookieCheck: true }); | ||
| return true; | ||
| } catch (e) { | ||
| ctx.context.logger.warn("OAuth proxy state missing or invalid", e); | ||
| return false; | ||
| } | ||
| }; | ||
| const oauthProxyQuerySchema = z.object({ | ||
@@ -85,3 +94,3 @@ callbackURL: z.string().meta({ description: "The URL to redirect to after the proxy" }), | ||
| } | ||
| if (typeof payload.timestamp !== "number" || !payload.userInfo || !payload.account || !payload.callbackURL) { | ||
| if (typeof payload.timestamp !== "number" || !payload.userInfo || !payload.account || !payload.state || !payload.callbackURL) { | ||
| ctx.context.logger.error("Failed to parse OAuth proxy payload"); | ||
@@ -96,7 +105,3 @@ throw redirectOnError(ctx, defaultErrorURL, "invalid_payload"); | ||
| } | ||
| try { | ||
| await parseGenericState(ctx, payload.state, { skipStateCookieCheck: true }); | ||
| } catch (e) { | ||
| ctx.context.logger.warn("Failed to clean up OAuth state", e); | ||
| } | ||
| if (!await consumeOAuthProxyState(ctx, payload.state)) throw redirectOnError(ctx, errorURL, "state_mismatch"); | ||
| let result; | ||
@@ -103,0 +108,0 @@ try { |
@@ -7,5 +7,5 @@ import { parseUserOutput } from "../../db/schema.mjs"; | ||
| import { toBoolean } from "../../utils/boolean.mjs"; | ||
| import { isGoogleHostedDomainAllowed, verifyGoogleIdToken } from "@better-auth/core/social-providers"; | ||
| import { createAuthEndpoint } from "@better-auth/core/api"; | ||
| import * as z from "zod"; | ||
| import { createRemoteJWKSet, jwtVerify } from "jose"; | ||
| //#region src/plugins/one-tap/index.ts | ||
@@ -44,10 +44,11 @@ const oneTapCallbackBodySchema = z.object({ | ||
| 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." }); | ||
| let payload; | ||
| try { | ||
| const { payload: verifiedPayload } = await jwtVerify(idToken, createRemoteJWKSet(new URL("https://www.googleapis.com/oauth2/v3/certs")), { | ||
| issuer: ["https://accounts.google.com", "accounts.google.com"], | ||
| audience | ||
| }); | ||
| payload = verifiedPayload; | ||
| } catch { | ||
| const payload = await verifyGoogleIdToken({ | ||
| token: idToken, | ||
| audience | ||
| }); | ||
| if (!payload) throw new APIError("BAD_REQUEST", { message: "invalid id token" }); | ||
| if (!payload.sub) throw new APIError("BAD_REQUEST", { message: "invalid id token" }); | ||
| const configuredHostedDomain = googleProvider?.hd; | ||
| if (!isGoogleHostedDomainAllowed(configuredHostedDomain, payload.hd)) { | ||
| 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}".`); | ||
| throw new APIError("BAD_REQUEST", { message: "invalid id token" }); | ||
@@ -54,0 +55,0 @@ } |
@@ -169,3 +169,7 @@ import { isAPIError } from "../../utils/is-api-error.mjs"; | ||
| const domain = options.emailDomainName ?? getOrigin(ctx.context.baseURL); | ||
| const userEmail = !isAnon && email ? email : `${walletAddress}@${domain}`; | ||
| const normalizedEmail = email?.toLowerCase(); | ||
| let userEmail = `${walletAddress}@${domain}`; | ||
| if (!isAnon && normalizedEmail) { | ||
| if (!await ctx.context.internalAdapter.findUserByEmail(normalizedEmail)) userEmail = normalizedEmail; | ||
| } | ||
| const { name, avatar } = await options.ensLookup?.({ walletAddress }) ?? {}; | ||
@@ -172,0 +176,0 @@ user = await ctx.context.internalAdapter.createUser({ |
@@ -164,4 +164,5 @@ import { parseUserOutput } from "../../../db/schema.mjs"; | ||
| }, async (ctx) => { | ||
| const { session, valid } = await verifyTwoFactor(ctx); | ||
| const { session, valid, beginAttempt } = await verifyTwoFactor(ctx); | ||
| const user = session.user; | ||
| const isSignIn = !session.session; | ||
| const twoFactor = await ctx.context.adapter.findOne({ | ||
@@ -175,7 +176,17 @@ model: twoFactorTable, | ||
| if (!twoFactor) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED); | ||
| const validate = await verifyBackupCode({ | ||
| backupCodes: twoFactor.backupCodes, | ||
| code: ctx.body.code | ||
| }, ctx.context.secretConfig, opts); | ||
| if (!validate.status || !validate.updated) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE); | ||
| const attempt = isSignIn ? await beginAttempt(5) : null; | ||
| let validate; | ||
| try { | ||
| validate = await verifyBackupCode({ | ||
| backupCodes: twoFactor.backupCodes, | ||
| code: ctx.body.code | ||
| }, ctx.context.secretConfig, opts); | ||
| } catch (error) { | ||
| await attempt?.restore(); | ||
| throw error; | ||
| } | ||
| if (!validate.status || !validate.updated) { | ||
| await attempt?.recordFailure(); | ||
| throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE); | ||
| } | ||
| const updatedBackupCodes = await encodeBackupCodes(validate.updated, ctx.context.secretConfig, opts); | ||
@@ -182,0 +193,0 @@ if (!await ctx.context.adapter.incrementOne({ |
@@ -239,7 +239,13 @@ import { mergeSchema } from "../../db/schema.mjs"; | ||
| const identifier = `2fa-${generateRandomString(20)}`; | ||
| const expiresAt = new Date(Date.now() + maxAge * 1e3); | ||
| await ctx.context.internalAdapter.createVerificationValue({ | ||
| value: data.user.id, | ||
| identifier, | ||
| expiresAt: new Date(Date.now() + maxAge * 1e3) | ||
| expiresAt | ||
| }); | ||
| await ctx.context.internalAdapter.createVerificationValue({ | ||
| value: "0", | ||
| identifier: `2fa-attempts-${identifier}`, | ||
| expiresAt | ||
| }); | ||
| await ctx.setSignedCookie(twoFactorCookie.name, identifier, ctx.context.secret, twoFactorCookie.attributes); | ||
@@ -246,0 +252,0 @@ const twoFactorMethods = []; |
@@ -125,3 +125,3 @@ import { symmetricDecrypt } from "../../../crypto/index.mjs"; | ||
| } | ||
| const { session, valid, invalid } = await verifyTwoFactor(ctx); | ||
| const { session, valid, invalid, beginAttempt } = await verifyTwoFactor(ctx); | ||
| const user = session.user; | ||
@@ -138,9 +138,20 @@ const isSignIn = !session.session; | ||
| if (isSignIn && twoFactor.verified === false) throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED); | ||
| if (!await createOTP(await symmetricDecrypt({ | ||
| key: ctx.context.secretConfig, | ||
| data: twoFactor.secret | ||
| }), { | ||
| period: opts.period, | ||
| digits: opts.digits | ||
| }).verify(ctx.body.code)) return invalid("INVALID_CODE"); | ||
| const attempt = isSignIn ? await beginAttempt(5) : null; | ||
| let status; | ||
| try { | ||
| status = await createOTP(await symmetricDecrypt({ | ||
| key: ctx.context.secretConfig, | ||
| data: twoFactor.secret | ||
| }), { | ||
| period: opts.period, | ||
| digits: opts.digits | ||
| }).verify(ctx.body.code); | ||
| } catch (error) { | ||
| await attempt?.restore(); | ||
| throw error; | ||
| } | ||
| if (!status) { | ||
| await attempt?.recordFailure(); | ||
| return invalid("INVALID_CODE"); | ||
| } | ||
| if (twoFactor.verified !== true) { | ||
@@ -147,0 +158,0 @@ if (!user.twoFactorEnabled) { |
@@ -69,3 +69,24 @@ import { parseUserOutput } from "../../db/schema.mjs"; | ||
| }, | ||
| key: signedTwoFactorCookie | ||
| key: signedTwoFactorCookie, | ||
| beginAttempt: async (allowedAttempts) => { | ||
| const identifier = `2fa-attempts-${signedTwoFactorCookie}`; | ||
| const consumed = await ctx.context.internalAdapter.consumeVerificationValue(identifier).catch(() => null); | ||
| if (!consumed) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE); | ||
| const parsed = Number(consumed.value); | ||
| const attempts = Number.isInteger(parsed) && parsed >= 0 ? parsed : allowedAttempts; | ||
| if (attempts >= allowedAttempts) { | ||
| await ctx.context.internalAdapter.consumeVerificationValue(signedTwoFactorCookie).catch(() => {}); | ||
| expireCookie(ctx, twoFactorCookie); | ||
| throw APIError.from("BAD_REQUEST", TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE); | ||
| } | ||
| const rearm = (count) => ctx.context.internalAdapter.createVerificationValue({ | ||
| value: `${count}`, | ||
| identifier, | ||
| expiresAt: verificationToken.expiresAt | ||
| }).catch(() => {}); | ||
| return { | ||
| recordFailure: () => rearm(attempts + 1), | ||
| restore: () => rearm(attempts) | ||
| }; | ||
| } | ||
| }; | ||
@@ -82,3 +103,7 @@ } | ||
| session, | ||
| key: `${session.user.id}!${session.session.id}` | ||
| key: `${session.user.id}!${session.session.id}`, | ||
| beginAttempt: async (_allowedAttempts) => ({ | ||
| recordFailure: async () => {}, | ||
| restore: async () => {} | ||
| }) | ||
| }; | ||
@@ -85,0 +110,0 @@ } |
@@ -35,7 +35,13 @@ import { mergeSchema, parseUserOutput } from "../../db/schema.mjs"; | ||
| const pathsWithHttpHookValidation = ["/sign-up/email", "/update-user"]; | ||
| const getUsernameToValidate = (username) => options?.validationOrder?.username === "post-normalization" ? normalizer(username) : username; | ||
| async function validateUsernameValue(username) { | ||
| const usernameToValidate = getUsernameToValidate(username); | ||
| if (usernameToValidate.length < minUsernameLength) return USERNAME_ERROR_CODES.USERNAME_TOO_SHORT; | ||
| if (usernameToValidate.length > maxUsernameLength) return USERNAME_ERROR_CODES.USERNAME_TOO_LONG; | ||
| if (!await validator(usernameToValidate)) return USERNAME_ERROR_CODES.INVALID_USERNAME; | ||
| return null; | ||
| } | ||
| async function validateUsername(username, displayUsername, adapter, currentUserId) { | ||
| const usernameToValidate = options?.validationOrder?.username === "post-normalization" ? normalizer(username) : username; | ||
| if (usernameToValidate.length < minUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT); | ||
| if (usernameToValidate.length > maxUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_LONG); | ||
| if (!await validator(usernameToValidate)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_USERNAME); | ||
| const validationError = await validateUsernameValue(username); | ||
| if (validationError) throw APIError.from("BAD_REQUEST", validationError); | ||
| const normalizedUsername = normalizer(username); | ||
@@ -251,44 +257,52 @@ const existingUser = await adapter.findOne({ | ||
| }), options?.schema), | ||
| hooks: { before: [{ | ||
| matcher(context) { | ||
| return context.path === "/sign-up/email" || context.path === "/update-user"; | ||
| hooks: { before: [ | ||
| { | ||
| matcher(context) { | ||
| return context.path === "/sign-up/email"; | ||
| }, | ||
| handler: createAuthMiddleware(async (ctx) => { | ||
| if (typeof ctx.body.displayUsername !== "string" || ctx.body.username !== void 0) return; | ||
| if (!await validateUsernameValue(ctx.body.displayUsername)) ctx.body.username = ctx.body.displayUsername; | ||
| }) | ||
| }, | ||
| handler: createAuthMiddleware(async (ctx) => { | ||
| const username = typeof ctx.body.username === "string" && options?.validationOrder?.username === "post-normalization" ? normalizer(ctx.body.username) : ctx.body.username; | ||
| if (username !== void 0 && typeof username === "string") { | ||
| const minUsernameLength = options?.minUsernameLength || 3; | ||
| const maxUsernameLength = options?.maxUsernameLength || 30; | ||
| if (username.length < minUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT); | ||
| if (username.length > maxUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_LONG); | ||
| if (!await (options?.usernameValidator || defaultUsernameValidator)(username)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_USERNAME); | ||
| const normalizedUsername = normalizer(ctx.body.username); | ||
| const existingUser = await ctx.context.adapter.findOne({ | ||
| model: "user", | ||
| where: [{ | ||
| field: "username", | ||
| value: normalizedUsername | ||
| }] | ||
| }); | ||
| if (ctx.path === "/sign-up/email" && existingUser) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN); | ||
| if (ctx.path === "/update-user" && existingUser) { | ||
| const session = await getSessionFromCtx(ctx); | ||
| if (!session || existingUser.id !== session.user.id) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN); | ||
| { | ||
| matcher(context) { | ||
| return context.path === "/sign-up/email" || context.path === "/update-user"; | ||
| }, | ||
| handler: createAuthMiddleware(async (ctx) => { | ||
| const username = ctx.body.username; | ||
| if (username !== void 0 && typeof username === "string") { | ||
| const validationError = await validateUsernameValue(username); | ||
| if (validationError) throw APIError.from("BAD_REQUEST", validationError); | ||
| const normalizedUsername = normalizer(username); | ||
| const existingUser = await ctx.context.adapter.findOne({ | ||
| model: "user", | ||
| where: [{ | ||
| field: "username", | ||
| value: normalizedUsername | ||
| }] | ||
| }); | ||
| if (ctx.path === "/sign-up/email" && existingUser) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN); | ||
| if (ctx.path === "/update-user" && existingUser) { | ||
| const session = await getSessionFromCtx(ctx); | ||
| if (!session || existingUser.id !== session.user.id) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN); | ||
| } | ||
| } | ||
| } | ||
| const displayUsername = typeof ctx.body.displayUsername === "string" && options?.validationOrder?.displayUsername === "post-normalization" ? displayUsernameNormalizer(ctx.body.displayUsername) : ctx.body.displayUsername; | ||
| if (displayUsername !== void 0 && typeof displayUsername === "string") { | ||
| if (options?.displayUsernameValidator) { | ||
| if (!await options.displayUsernameValidator(displayUsername)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_DISPLAY_USERNAME); | ||
| const displayUsername = typeof ctx.body.displayUsername === "string" && options?.validationOrder?.displayUsername === "post-normalization" ? displayUsernameNormalizer(ctx.body.displayUsername) : ctx.body.displayUsername; | ||
| if (displayUsername !== void 0 && typeof displayUsername === "string") { | ||
| if (options?.displayUsernameValidator) { | ||
| if (!await options.displayUsernameValidator(displayUsername)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_DISPLAY_USERNAME); | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| }, { | ||
| matcher(context) { | ||
| return context.path === "/sign-up/email"; | ||
| }) | ||
| }, | ||
| handler: createAuthMiddleware(async (ctx) => { | ||
| if (ctx.body.username && !ctx.body.displayUsername) ctx.body.displayUsername = ctx.body.username; | ||
| if (ctx.body.displayUsername && !ctx.body.username) ctx.body.username = ctx.body.displayUsername; | ||
| }) | ||
| }] }, | ||
| { | ||
| matcher(context) { | ||
| return context.path === "/sign-up/email"; | ||
| }, | ||
| handler: createAuthMiddleware(async (ctx) => { | ||
| if (ctx.body.username && !ctx.body.displayUsername) ctx.body.displayUsername = ctx.body.username; | ||
| }) | ||
| } | ||
| ] }, | ||
| options, | ||
@@ -295,0 +309,0 @@ $ERROR_CODES: USERNAME_ERROR_CODES |
+9
-9
| { | ||
| "name": "better-auth", | ||
| "version": "1.6.20", | ||
| "version": "1.6.21", | ||
| "description": "The most comprehensive authentication framework for TypeScript.", | ||
@@ -486,3 +486,3 @@ "type": "module", | ||
| "@noble/hashes": "^2.0.1", | ||
| "better-call": "1.3.6", | ||
| "better-call": "1.3.7", | ||
| "defu": "^6.1.4", | ||
@@ -493,9 +493,9 @@ "jose": "^6.1.3", | ||
| "zod": "^4.3.6", | ||
| "@better-auth/core": "1.6.20", | ||
| "@better-auth/drizzle-adapter": "1.6.20", | ||
| "@better-auth/kysely-adapter": "1.6.20", | ||
| "@better-auth/memory-adapter": "1.6.20", | ||
| "@better-auth/mongo-adapter": "1.6.20", | ||
| "@better-auth/prisma-adapter": "1.6.20", | ||
| "@better-auth/telemetry": "1.6.20" | ||
| "@better-auth/core": "1.6.21", | ||
| "@better-auth/drizzle-adapter": "1.6.21", | ||
| "@better-auth/kysely-adapter": "1.6.21", | ||
| "@better-auth/memory-adapter": "1.6.21", | ||
| "@better-auth/mongo-adapter": "1.6.21", | ||
| "@better-auth/prisma-adapter": "1.6.21", | ||
| "@better-auth/telemetry": "1.6.21" | ||
| }, | ||
@@ -502,0 +502,0 @@ "devDependencies": { |
| import { BetterAuthOptions } from "@better-auth/core"; | ||
| //#region src/utils/get-request-ip.d.ts | ||
| declare function getIp(req: Request | Headers, options: BetterAuthOptions): string | null; | ||
| //#endregion | ||
| export { getIp }; |
| import { isDevelopment, isTest } from "@better-auth/core/env"; | ||
| import { isValidIP, normalizeIP } from "@better-auth/core/utils/ip"; | ||
| //#region src/utils/get-request-ip.ts | ||
| const LOCALHOST_IP = "127.0.0.1"; | ||
| function getIp(req, options) { | ||
| if (options.advanced?.ipAddress?.disableIpTracking) return null; | ||
| const headers = "headers" in req ? req.headers : req; | ||
| const ipHeaders = options.advanced?.ipAddress?.ipAddressHeaders || ["x-forwarded-for"]; | ||
| for (const key of ipHeaders) { | ||
| const value = "get" in headers ? headers.get(key) : headers[key]; | ||
| if (typeof value === "string") { | ||
| const ip = value.split(",")[0].trim(); | ||
| if (isValidIP(ip)) return normalizeIP(ip, { ipv6Subnet: options.advanced?.ipAddress?.ipv6Subnet }); | ||
| } | ||
| } | ||
| if (isTest() || isDevelopment()) return LOCALHOST_IP; | ||
| return null; | ||
| } | ||
| //#endregion | ||
| export { getIp }; |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
2004956
0.27%27987
0.38%470
-0.42%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated