Merge remote-tracking branch 'public-gh/master'
* public-gh/master: fix(auth): apply effective trusted origins and honor allowed hostnames in public mode fix(onboard): preserve env-derived secrets defaults and report ignored exposure env in local_trusted mode fix: parseBooleanFromEnv silently treats common truthy values as false set `PAPERCLIP_PUBLIC_URL` in compose files centralize URLs into single canonical URL var
This commit is contained in:
@@ -42,13 +42,38 @@ function headersFromExpressRequest(req: Request): Headers {
|
||||
return headersFromNodeHeaders(req.headers);
|
||||
}
|
||||
|
||||
export function createBetterAuthInstance(db: Db, config: Config): BetterAuthInstance {
|
||||
export function deriveAuthTrustedOrigins(config: Config): string[] {
|
||||
const baseUrl = config.authBaseUrlMode === "explicit" ? config.authPublicBaseUrl : undefined;
|
||||
const trustedOrigins = new Set<string>();
|
||||
|
||||
if (baseUrl) {
|
||||
try {
|
||||
trustedOrigins.add(new URL(baseUrl).origin);
|
||||
} catch {
|
||||
// Better Auth will surface invalid base URL separately.
|
||||
}
|
||||
}
|
||||
if (config.deploymentMode === "authenticated") {
|
||||
for (const hostname of config.allowedHostnames) {
|
||||
const trimmed = hostname.trim().toLowerCase();
|
||||
if (!trimmed) continue;
|
||||
trustedOrigins.add(`https://${trimmed}`);
|
||||
trustedOrigins.add(`http://${trimmed}`);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(trustedOrigins);
|
||||
}
|
||||
|
||||
export function createBetterAuthInstance(db: Db, config: Config, trustedOrigins?: string[]): BetterAuthInstance {
|
||||
const baseUrl = config.authBaseUrlMode === "explicit" ? config.authPublicBaseUrl : undefined;
|
||||
const secret = process.env.BETTER_AUTH_SECRET ?? process.env.PAPERCLIP_AGENT_JWT_SECRET ?? "paperclip-dev-secret";
|
||||
const effectiveTrustedOrigins = trustedOrigins ?? deriveAuthTrustedOrigins(config);
|
||||
|
||||
const authConfig = {
|
||||
baseURL: baseUrl,
|
||||
secret,
|
||||
trustedOrigins: effectiveTrustedOrigins,
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema: {
|
||||
|
||||
@@ -130,9 +130,12 @@ export function loadConfig(): Config {
|
||||
AUTH_BASE_URL_MODES.includes(authBaseUrlModeFromEnvRaw as AuthBaseUrlMode)
|
||||
? (authBaseUrlModeFromEnvRaw as AuthBaseUrlMode)
|
||||
: null;
|
||||
const publicUrlFromEnv = process.env.PAPERCLIP_PUBLIC_URL;
|
||||
const authPublicBaseUrlRaw =
|
||||
process.env.PAPERCLIP_AUTH_PUBLIC_BASE_URL ??
|
||||
process.env.BETTER_AUTH_URL ??
|
||||
process.env.BETTER_AUTH_BASE_URL ??
|
||||
publicUrlFromEnv ??
|
||||
fileConfig?.auth?.publicBaseUrl;
|
||||
const authPublicBaseUrl = authPublicBaseUrlRaw?.trim() || undefined;
|
||||
const authBaseUrlMode: AuthBaseUrlMode =
|
||||
@@ -146,8 +149,24 @@ export function loadConfig(): Config {
|
||||
.map((value) => value.trim().toLowerCase())
|
||||
.filter((value) => value.length > 0)
|
||||
: null;
|
||||
const publicUrlHostname = authPublicBaseUrl
|
||||
? (() => {
|
||||
try {
|
||||
return new URL(authPublicBaseUrl).hostname.trim().toLowerCase();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
: null;
|
||||
const allowedHostnames = Array.from(
|
||||
new Set((allowedHostnamesFromEnv ?? fileConfig?.server.allowedHostnames ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean)),
|
||||
new Set(
|
||||
[
|
||||
...(allowedHostnamesFromEnv ?? fileConfig?.server.allowedHostnames ?? []),
|
||||
...(publicUrlHostname ? [publicUrlHostname] : []),
|
||||
]
|
||||
.map((value) => value.trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
),
|
||||
);
|
||||
const companyDeletionEnvRaw = process.env.PAPERCLIP_ENABLE_COMPANY_DELETION;
|
||||
const companyDeletionEnabled =
|
||||
|
||||
@@ -412,6 +412,7 @@ if (config.deploymentMode === "authenticated") {
|
||||
const {
|
||||
createBetterAuthHandler,
|
||||
createBetterAuthInstance,
|
||||
deriveAuthTrustedOrigins,
|
||||
resolveBetterAuthSession,
|
||||
resolveBetterAuthSessionFromHeaders,
|
||||
} = await import("./auth/better-auth.js");
|
||||
@@ -422,7 +423,25 @@ if (config.deploymentMode === "authenticated") {
|
||||
"authenticated mode requires BETTER_AUTH_SECRET (or PAPERCLIP_AGENT_JWT_SECRET) to be set",
|
||||
);
|
||||
}
|
||||
const auth = createBetterAuthInstance(db as any, config);
|
||||
const derivedTrustedOrigins = deriveAuthTrustedOrigins(config);
|
||||
const envTrustedOrigins = (process.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "")
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0);
|
||||
const effectiveTrustedOrigins = Array.from(new Set([...derivedTrustedOrigins, ...envTrustedOrigins]));
|
||||
logger.info(
|
||||
{
|
||||
authBaseUrlMode: config.authBaseUrlMode,
|
||||
authPublicBaseUrl: config.authPublicBaseUrl ?? null,
|
||||
trustedOrigins: effectiveTrustedOrigins,
|
||||
trustedOriginsSource: {
|
||||
derived: derivedTrustedOrigins.length,
|
||||
env: envTrustedOrigins.length,
|
||||
},
|
||||
},
|
||||
"Authenticated mode auth origin configuration",
|
||||
);
|
||||
const auth = createBetterAuthInstance(db as any, config, effectiveTrustedOrigins);
|
||||
betterAuthHandler = createBetterAuthHandler(auth);
|
||||
resolveSession = (req) => resolveBetterAuthSession(auth, req);
|
||||
resolveSessionFromHeaders = (headers) => resolveBetterAuthSessionFromHeaders(auth, headers);
|
||||
|
||||
Reference in New Issue
Block a user