better-auth
Version:
The most comprehensive authentication framework for TypeScript.
158 lines (157 loc) • 7.11 kB
JavaScript
import { getBaseURL, getOrigin, isDynamicBaseURLConfig, isRequestLike, resolveBaseURL } from "../utils/url.mjs";
import { isPromise } from "../utils/is-promise.mjs";
import { createCookieGetter, getCookies } from "../cookies/index.mjs";
import { createInternalAdapter } from "../db/internal-adapter.mjs";
import { env } from "@better-auth/core/env";
import { BetterAuthError } from "@better-auth/core/error";
import { defu as defu$1 } from "defu";
import { isLoopbackHost } from "@better-auth/core/utils/host";
//#region src/context/helpers.ts
async function runPluginInit(context) {
let options = context.options;
const plugins = options.plugins || [];
const pluginTrustedOrigins = [];
const dbHooks = [];
for (const plugin of plugins) if (plugin.init) {
const initPromise = plugin.init(context);
let result;
if (isPromise(initPromise)) result = await initPromise;
else result = initPromise;
if (typeof result === "object") {
if (result.options) {
const { databaseHooks, trustedOrigins, ...restOpts } = result.options;
if (databaseHooks) dbHooks.push({
source: `plugin:${plugin.id}`,
hooks: databaseHooks
});
if (trustedOrigins) pluginTrustedOrigins.push(trustedOrigins);
options = defu$1(options, restOpts);
}
if (result.context) Object.assign(context, result.context);
}
}
if (pluginTrustedOrigins.length > 0) {
const allSources = [...options.trustedOrigins ? [options.trustedOrigins] : [], ...pluginTrustedOrigins];
const staticOrigins = allSources.filter(Array.isArray).flat();
const dynamicOrigins = allSources.filter((s) => typeof s === "function");
if (dynamicOrigins.length > 0) options.trustedOrigins = async (request) => {
const resolved = await Promise.all(dynamicOrigins.map((fn) => fn(request)));
return [...staticOrigins, ...resolved.flat()].filter((v) => typeof v === "string" && v !== "");
};
else options.trustedOrigins = staticOrigins;
}
if (options.databaseHooks) dbHooks.push({
source: "user",
hooks: options.databaseHooks
});
context.internalAdapter = createInternalAdapter(context.adapter, {
options,
logger: context.logger,
hooks: dbHooks,
generateId: context.generateId
});
context.options = options;
}
function getInternalPlugins(options) {
const plugins = [];
if (options.advanced?.crossSubDomainCookies?.enabled) {}
return plugins;
}
async function getTrustedOrigins(options, request) {
const trustedOrigins = [];
if (isDynamicBaseURLConfig(options.baseURL)) {
const allowedHosts = options.baseURL.allowedHosts;
const proto = options.baseURL.protocol;
for (const host of allowedHosts) if (!host.includes("://")) {
if (!proto || proto === "https" || proto === "auto") trustedOrigins.push(`https://${host}`);
if (proto === "http" || proto === "auto" || isLoopbackHost(host)) trustedOrigins.push(`http://${host}`);
} else trustedOrigins.push(host);
if (options.baseURL.fallback) try {
trustedOrigins.push(new URL(options.baseURL.fallback).origin);
} catch {}
} else {
const baseURL = getBaseURL(typeof options.baseURL === "string" ? options.baseURL : void 0, options.basePath, request);
if (baseURL) trustedOrigins.push(new URL(baseURL).origin);
}
if (options.trustedOrigins) {
if (Array.isArray(options.trustedOrigins)) trustedOrigins.push(...options.trustedOrigins);
if (typeof options.trustedOrigins === "function") {
const validOrigins = await options.trustedOrigins(request);
trustedOrigins.push(...validOrigins);
}
}
const envTrustedOrigins = env.BETTER_AUTH_TRUSTED_ORIGINS;
if (envTrustedOrigins) trustedOrigins.push(...envTrustedOrigins.split(","));
return trustedOrigins.filter((v) => Boolean(v));
}
/**
* Picks a `Request`-like or `Headers` value from a direct `auth.api` call.
* Headers are only accepted when they carry a host: without one, host
* resolution would fall back to `null` and the caller should use `fallback`
* or pass a `Request` instead.
*/
function pickSource(input) {
if (isRequestLike(input?.request)) return input.request;
if (!input?.headers) return void 0;
const headers = input.headers instanceof Headers ? input.headers : new Headers(input.headers);
if (!headers.has("host") && !headers.has("x-forwarded-host")) return;
return headers;
}
/**
* Returns the effective `trustedProxyHeaders` value for dynamic `baseURL`
* resolution. When the user hasn't set `advanced.trustedProxyHeaders`,
* proxy headers (`x-forwarded-host` / `x-forwarded-proto`) are trusted by
* default so deployments behind a reverse proxy work without extra config.
*/
function resolveDynamicTrustedProxyHeaders(options) {
return options.advanced?.trustedProxyHeaders ?? true;
}
/**
* Per-request clone with `baseURL`, `trustedOrigins`, `trustedProviders`
* and cookies rehydrated for the resolved host. Throws `BetterAuthError`
* when the URL cannot be resolved; callers on the direct-API path convert
* this to `APIError`.
*/
async function resolveRequestContext(ctx, source, trustedProxyHeaders) {
const dynamicBaseURLConfig = ctx.options.baseURL;
const baseURL = resolveBaseURL(dynamicBaseURLConfig, ctx.options.basePath || "/api/auth", source, void 0, trustedProxyHeaders);
if (!baseURL) throw new BetterAuthError("Could not resolve base URL from request. Check your allowedHosts config.");
const resolved = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
resolved.baseURL = baseURL;
resolved.options = {
...ctx.options,
baseURL: getOrigin(baseURL) || void 0
};
const trustedOriginOptions = {
...resolved.options,
baseURL: dynamicBaseURLConfig
};
const needsRequest = typeof ctx.options.trustedOrigins === "function" || typeof ctx.options.account?.accountLinking?.trustedProviders === "function";
let callbackRequest;
if (needsRequest) if (isRequestLike(source)) callbackRequest = source;
else if (source) callbackRequest = new Request(baseURL, { headers: source });
else callbackRequest = void 0;
else callbackRequest = void 0;
resolved.trustedOrigins = await getTrustedOrigins(trustedOriginOptions, callbackRequest);
resolved.trustedProviders = await getTrustedProviders(resolved.options, callbackRequest);
if (ctx.options.advanced?.crossSubDomainCookies?.enabled) {
resolved.authCookies = getCookies(resolved.options);
resolved.createAuthCookie = createCookieGetter(resolved.options);
}
return resolved;
}
async function getAwaitableValue(arr, item) {
if (!arr) return void 0;
for (const val of arr) {
const value = typeof val === "function" ? await val() : val;
if (value[item.field ?? "id"] === item.value) return value;
}
}
async function getTrustedProviders(options, request) {
const trustedProviders = options.account?.accountLinking?.trustedProviders;
if (!trustedProviders) return [];
if (Array.isArray(trustedProviders)) return trustedProviders.filter((v) => Boolean(v));
return (await trustedProviders(request) ?? []).filter((v) => Boolean(v));
}
//#endregion
export { getAwaitableValue, getInternalPlugins, getTrustedOrigins, getTrustedProviders, pickSource, resolveDynamicTrustedProxyHeaders, resolveRequestContext, runPluginInit };