UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

195 lines (193 loc) 8.45 kB
import { createInternalAdapter } from "../db/internal-adapter.mjs"; import { hashPassword, verifyPassword } from "../crypto/password.mjs"; import { createCookieGetter, getCookies } from "../cookies/index.mjs"; import { generateId } from "../utils/index.mjs"; import { checkEndpointConflicts } from "../api/index.mjs"; import { getBaseURL } from "../utils/url.mjs"; import { matchesOriginPattern } from "../auth/trusted-origins.mjs"; import { DEFAULT_SECRET } from "../utils/constants.mjs"; import { isPromise } from "../utils/is-promise.mjs"; import { checkPassword } from "../utils/password.mjs"; import { getInternalPlugins, getTrustedOrigins, runPluginInit } from "./helpers.mjs"; import { getAuthTables } from "@better-auth/core/db"; import { createLogger, env, isProduction, isTest } from "@better-auth/core/env"; import { BetterAuthError } from "@better-auth/core/error"; import { socialProviders } from "@better-auth/core/social-providers"; import { createTelemetry } from "@better-auth/telemetry"; import defu$1 from "defu"; //#region src/context/create-context.ts /** * Estimates the entropy of a string in bits. * This is a simple approximation that helps detect low-entropy secrets. */ function estimateEntropy(str) { const unique = new Set(str).size; if (unique === 0) return 0; return Math.log2(Math.pow(unique, str.length)); } /** * Validates that the secret meets minimum security requirements. * Throws BetterAuthError if the secret is invalid. * Skips validation for DEFAULT_SECRET in test environments only. * Only throws for DEFAULT_SECRET in production environment. */ function validateSecret(secret, logger$1) { const isDefaultSecret = secret === DEFAULT_SECRET; if (isTest()) return; if (isDefaultSecret && isProduction) throw new BetterAuthError("You are using the default secret. Please set `BETTER_AUTH_SECRET` in your environment variables or pass `secret` in your auth config."); if (!secret) throw new BetterAuthError("BETTER_AUTH_SECRET is missing. Set it in your environment or pass `secret` to betterAuth({ secret })."); if (secret.length < 32) throw new BetterAuthError(`Invalid BETTER_AUTH_SECRET: must be at least 32 characters long for adequate security. Generate one with \`npx @better-auth/cli secret\` or \`openssl rand -base64 32\`.`); if (estimateEntropy(secret) < 120) logger$1.warn("[better-auth] Warning: your BETTER_AUTH_SECRET appears low-entropy. Use a randomly generated secret for production."); } async function createAuthContext(adapter, options, getDatabaseType) { if (!options.database) options = defu$1(options, { session: { cookieCache: { enabled: true, strategy: "jwe", refreshCache: true } }, account: { storeStateStrategy: "cookie", storeAccountCookie: true } }); const plugins = options.plugins || []; const internalPlugins = getInternalPlugins(options); const logger$1 = createLogger(options.logger); const baseURL = getBaseURL(options.baseURL, options.basePath); if (!baseURL) logger$1.warn(`[better-auth] Base URL could not be determined. Please set a valid base URL using the baseURL config option or the BETTER_AUTH_BASE_URL environment variable. Without this, callbacks and redirects may not work correctly.`); const secret = options.secret || env.BETTER_AUTH_SECRET || env.AUTH_SECRET || DEFAULT_SECRET; validateSecret(secret, logger$1); options = { ...options, secret, baseURL: baseURL ? new URL(baseURL).origin : "", basePath: options.basePath || "/api/auth", plugins: plugins.concat(internalPlugins) }; checkEndpointConflicts(options, logger$1); const cookies = getCookies(options); const tables = getAuthTables(options); const providers = Object.entries(options.socialProviders || {}).map(([key, config]) => { if (config == null) return null; if (config.enabled === false) return null; if (!config.clientId) logger$1.warn(`Social provider ${key} is missing clientId or clientSecret`); const provider = socialProviders[key](config); provider.disableImplicitSignUp = config.disableImplicitSignUp; return provider; }).filter((x) => x !== null); const generateIdFunc = ({ model, size }) => { if (typeof options.advanced?.generateId === "function") return options.advanced.generateId({ model, size }); if (typeof options?.advanced?.database?.generateId === "function") return options.advanced.database.generateId({ model, size }); return generateId(size); }; const { publish } = await createTelemetry(options, { adapter: adapter.id, database: typeof options.database === "function" ? "adapter" : getDatabaseType(options.database) }); let ctx = { appName: options.appName || "Better Auth", socialProviders: providers, options, oauthConfig: { storeStateStrategy: options.account?.storeStateStrategy || (options.database ? "database" : "cookie"), skipStateCookieCheck: !!options.account?.skipStateCookieCheck }, tables, trustedOrigins: await getTrustedOrigins(options), isTrustedOrigin(url, settings) { return ctx.trustedOrigins.some((origin) => matchesOriginPattern(url, origin, settings)); }, baseURL: baseURL || "", sessionConfig: { updateAge: options.session?.updateAge !== void 0 ? options.session.updateAge : 1440 * 60, expiresIn: options.session?.expiresIn || 3600 * 24 * 7, freshAge: options.session?.freshAge === void 0 ? 3600 * 24 : options.session.freshAge, cookieRefreshCache: (() => { const refreshCache = options.session?.cookieCache?.refreshCache; const maxAge = options.session?.cookieCache?.maxAge || 300; if ((!!options.database || !!options.secondaryStorage) && refreshCache) { logger$1.warn("[better-auth] `session.cookieCache.refreshCache` is enabled while `database` or `secondaryStorage` is configured. `refreshCache` is meant for stateless (DB-less) setups. Disabling `refreshCache` — remove it from your config to silence this warning."); return false; } if (refreshCache === false || refreshCache === void 0) return false; if (refreshCache === true) return { enabled: true, updateAge: Math.floor(maxAge * .2) }; return { enabled: true, updateAge: refreshCache.updateAge !== void 0 ? refreshCache.updateAge : Math.floor(maxAge * .2) }; })() }, secret, rateLimit: { ...options.rateLimit, enabled: options.rateLimit?.enabled ?? isProduction, window: options.rateLimit?.window || 10, max: options.rateLimit?.max || 100, storage: options.rateLimit?.storage || (options.secondaryStorage ? "secondary-storage" : "memory") }, authCookies: cookies, logger: logger$1, generateId: generateIdFunc, session: null, secondaryStorage: options.secondaryStorage, password: { hash: options.emailAndPassword?.password?.hash || hashPassword, verify: options.emailAndPassword?.password?.verify || verifyPassword, config: { minPasswordLength: options.emailAndPassword?.minPasswordLength || 8, maxPasswordLength: options.emailAndPassword?.maxPasswordLength || 128 }, checkPassword }, setNewSession(session) { this.newSession = session; }, newSession: null, adapter, internalAdapter: createInternalAdapter(adapter, { options, logger: logger$1, hooks: options.databaseHooks ? [options.databaseHooks] : [], generateId: generateIdFunc }), createAuthCookie: createCookieGetter(options), async runMigrations() { throw new BetterAuthError("runMigrations will be set by the specific init implementation"); }, publishTelemetry: publish, skipCSRFCheck: !!options.advanced?.disableCSRFCheck, skipOriginCheck: options.advanced?.disableOriginCheck !== void 0 ? options.advanced.disableOriginCheck : isTest() ? true : false, runInBackground: options.advanced?.backgroundTasks?.handler ?? ((p) => { p.catch(() => {}); }), async runInBackgroundOrAwait(promise) { try { if (options.advanced?.backgroundTasks?.handler) { if (promise instanceof Promise) options.advanced.backgroundTasks.handler(promise.catch((e) => { logger$1.error("Failed to run background task:", e); })); } else await promise; } catch (e) { logger$1.error("Failed to run background task:", e); } } }; const initOrPromise = runPluginInit(ctx); let context; if (isPromise(initOrPromise)) ({context} = await initOrPromise); else ({context} = initOrPromise); return context; } //#endregion export { createAuthContext }; //# sourceMappingURL=create-context.mjs.map