/opt/canhelp/node_modules/better-auth/dist/utils
Edit: /opt/canhelp/node_modules/better-auth/dist/utils/url.mjs (8295B)
import { wildcardMatch } from "./wildcard.mjs";
import { env } from "@better-auth/core/env";
import { BetterAuthError } from "@better-auth/core/error";
//#region src/utils/url.ts
function checkHasPath(url) {
try {
return (new URL(url).pathname.replace(/\/+$/, "") || "/") !== "/";
} catch {
throw new BetterAuthError(`Invalid base URL: ${url}. Please provide a valid base URL.`);
}
}
function assertHasProtocol(url) {
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new BetterAuthError(`Invalid base URL: ${url}. URL must include 'http://' or 'https://'`);
} catch (error) {
if (error instanceof BetterAuthError) throw error;
throw new BetterAuthError(`Invalid base URL: ${url}. Please provide a valid base URL.`, { cause: error });
}
}
function withPath(url, path = "/api/auth") {
assertHasProtocol(url);
if (checkHasPath(url)) return url;
const trimmedUrl = url.replace(/\/+$/, "");
if (!path || path === "/") return trimmedUrl;
path = path.startsWith("/") ? path : `/${path}`;
return `${trimmedUrl}${path}`;
}
function validateProxyHeader(header, type) {
if (!header || header.trim() === "") return false;
if (type === "proto") return header === "http" || header === "https";
if (type === "host") {
if ([
/\.\./,
/\0/,
/[\s]/,
/^[.]/,
/[<>'"]/,
/javascript:/i,
/file:/i,
/data:/i
].some((pattern) => pattern.test(header))) return false;
return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(:[0-9]{1,5})?$/.test(header) || /^(\d{1,3}\.){3}\d{1,3}(:[0-9]{1,5})?$/.test(header) || /^\[[0-9a-fA-F:]+\](:[0-9]{1,5})?$/.test(header) || /^localhost(:[0-9]{1,5})?$/i.test(header);
}
return false;
}
function getBaseURL(url, path, request, loadEnv, trustedProxyHeaders) {
if (url) return withPath(url, path);
if (loadEnv !== false) {
const fromEnv = env.BETTER_AUTH_URL || env.NEXT_PUBLIC_BETTER_AUTH_URL || env.PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_AUTH_URL || (env.BASE_URL !== "/" ? env.BASE_URL : void 0);
if (fromEnv) return withPath(fromEnv, path);
}
const fromRequest = request?.headers.get("x-forwarded-host");
const fromRequestProto = request?.headers.get("x-forwarded-proto");
if (fromRequest && fromRequestProto && trustedProxyHeaders) {
if (validateProxyHeader(fromRequestProto, "proto") && validateProxyHeader(fromRequest, "host")) try {
return withPath(`${fromRequestProto}://${fromRequest}`, path);
} catch (_error) {}
}
if (request) {
const url = getOrigin(request.url);
if (!url) throw new BetterAuthError("Could not get origin from request. Please provide a valid base URL.");
return withPath(url, path);
}
if (typeof window !== "undefined" && window.location) return withPath(window.location.origin, path);
}
function getOrigin(url) {
try {
const parsedUrl = new URL(url);
return parsedUrl.origin === "null" ? null : parsedUrl.origin;
} catch {
return null;
}
}
function getProtocol(url) {
try {
return new URL(url).protocol;
} catch {
return null;
}
}
function getHost(url) {
try {
return new URL(url).host;
} catch {
return null;
}
}
/**
* Checks if the baseURL config is a dynamic config object
*/
function isDynamicBaseURLConfig(config) {
return typeof config === "object" && config !== null && "allowedHosts" in config && Array.isArray(config.allowedHosts);
}
/**
* Extracts the host from the request headers.
* Tries x-forwarded-host first (for proxy setups), then falls back to host header.
*
* @param request The incoming request
* @returns The host string or null if not found
*/
function getHostFromRequest(request) {
const forwardedHost = request.headers.get("x-forwarded-host");
if (forwardedHost && validateProxyHeader(forwardedHost, "host")) return forwardedHost;
const host = request.headers.get("host");
if (host && validateProxyHeader(host, "host")) return host;
try {
return new URL(request.url).host;
} catch {
return null;
}
}
/**
* Extracts the protocol from the request headers.
* Tries x-forwarded-proto first (for proxy setups), then infers from request URL.
*
* @param request The incoming request
* @param configProtocol Protocol override from config
* @returns The protocol ("http" or "https")
*/
function getProtocolFromRequest(request, configProtocol) {
if (configProtocol === "http" || configProtocol === "https") return configProtocol;
const forwardedProto = request.headers.get("x-forwarded-proto");
if (forwardedProto && validateProxyHeader(forwardedProto, "proto")) return forwardedProto;
try {
const url = new URL(request.url);
if (url.protocol === "http:" || url.protocol === "https:") return url.protocol.slice(0, -1);
} catch {}
return "https";
}
/**
* Matches a hostname against a host pattern.
* Supports wildcard patterns like `*.vercel.app` or `preview-*.myapp.com`.
*
* @param host The hostname to test (e.g., "myapp.com", "preview-123.vercel.app")
* @param pattern The host pattern (e.g., "myapp.com", "*.vercel.app")
* @returns {boolean} true if the host matches the pattern, false otherwise.
*
* @example
* ```ts
* matchesHostPattern("myapp.com", "myapp.com") // true
* matchesHostPattern("preview-123.vercel.app", "*.vercel.app") // true
* matchesHostPattern("preview-123.myapp.com", "preview-*.myapp.com") // true
* matchesHostPattern("evil.com", "myapp.com") // false
* ```
*/
const matchesHostPattern = (host, pattern) => {
if (!host || !pattern) return false;
const normalizedHost = host.replace(/^https?:\/\//, "").split("/")[0].toLowerCase();
const normalizedPattern = pattern.replace(/^https?:\/\//, "").split("/")[0].toLowerCase();
if (normalizedPattern.includes("*") || normalizedPattern.includes("?")) return wildcardMatch(normalizedPattern)(normalizedHost);
return normalizedHost.toLowerCase() === normalizedPattern.toLowerCase();
};
/**
* Resolves the base URL from a dynamic config based on the incoming request.
* Validates the derived host against the allowedHosts allowlist.
*
* @param config The dynamic base URL config
* @param request The incoming request
* @param basePath The base path to append
* @returns The resolved base URL with path
* @throws BetterAuthError if host is not in allowedHosts and no fallback is set
*/
function resolveDynamicBaseURL(config, request, basePath) {
const host = getHostFromRequest(request);
if (!host) {
if (config.fallback) return withPath(config.fallback, basePath);
throw new BetterAuthError("Could not determine host from request headers. Please provide a fallback URL in your baseURL config.");
}
if (config.allowedHosts.some((pattern) => matchesHostPattern(host, pattern))) return withPath(`${getProtocolFromRequest(request, config.protocol)}://${host}`, basePath);
if (config.fallback) return withPath(config.fallback, basePath);
throw new BetterAuthError(`Host "${host}" is not in the allowed hosts list. Allowed hosts: ${config.allowedHosts.join(", ")}. Add this host to your allowedHosts config or provide a fallback URL.`);
}
/**
* Resolves the base URL from any config type (static string or dynamic object).
* This is the main entry point for base URL resolution.
*
* @param config The base URL config (string or object)
* @param basePath The base path to append
* @param request Optional request for dynamic resolution
* @param loadEnv Whether to load from environment variables
* @param trustedProxyHeaders Whether to trust proxy headers (for legacy behavior)
* @returns The resolved base URL with path
*/
function resolveBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders) {
if (isDynamicBaseURLConfig(config)) {
if (request) return resolveDynamicBaseURL(config, request, basePath);
if (config.fallback) return withPath(config.fallback, basePath);
return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
}
if (typeof config === "string") return getBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders);
return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
}
//#endregion
export { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
//# sourceMappingURL=url.mjs.map