better-auth
Version:
The most comprehensive authentication framework for TypeScript.
195 lines (193 loc) • 8.45 kB
JavaScript
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