Files
caddy-proxy-manager/src/lib/config.ts
fuomag9 3be4e1bf7d Rewritten to use drizzle instead of prisma
commit c0894548dac5133bd89da5b68684443748fa2559
Author: fuomag9 <1580624+fuomag9@users.noreply.github.com>
Date:   Fri Nov 7 18:38:30 2025 +0100

    Update config.ts

commit 5a4f1159d2123ada0f698a10011c24720bf6ea6f
Author: fuomag9 <1580624+fuomag9@users.noreply.github.com>
Date:   Fri Nov 7 15:58:13 2025 +0100

    first drizzle rewrite
2025-11-07 19:26:32 +01:00

177 lines
5.6 KiB
TypeScript

const DEV_SECRET = "dev-secret-change-in-production-12345678901234567890123456789012";
const DEFAULT_ADMIN_USERNAME = "admin";
const DEFAULT_ADMIN_PASSWORD = "admin";
const DISALLOWED_SESSION_SECRETS = new Set([
"change-me-in-production",
"dev-secret-change-in-production-12345678901234567890123456789012"
]);
const DEFAULT_CADDY_URL = process.env.NODE_ENV === "development" ? "http://localhost:2019" : "http://caddy:2019";
const MIN_SESSION_SECRET_LENGTH = 32;
const MIN_ADMIN_PASSWORD_LENGTH = 12;
const isProduction = process.env.NODE_ENV === "production";
const isNodeRuntime = process.env.NEXT_RUNTIME === "nodejs";
const isDevelopment = process.env.NODE_ENV === "development";
// Only enforce strict validation in actual production runtime, not during build
const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build" || !process.env.NEXT_RUNTIME;
const isRuntimeProduction = isProduction && isNodeRuntime && !isBuildPhase;
function resolveSessionSecret(): string {
const rawSecret = process.env.SESSION_SECRET ?? null;
const secret = rawSecret?.trim();
// In development, allow missing secret
if (isDevelopment && !secret) {
return DEV_SECRET;
}
// In production build phase, allow temporary value
if (isProduction && !isNodeRuntime && !secret) {
return DEV_SECRET;
}
// Use provided secret or dev secret
const finalSecret = secret || DEV_SECRET;
// Strict validation in production runtime
if (isRuntimeProduction) {
if (!secret) {
throw new Error(
"SESSION_SECRET environment variable is required in production. " +
"Generate a secure secret with: openssl rand -base64 32"
);
}
if (DISALLOWED_SESSION_SECRETS.has(secret)) {
throw new Error(
"SESSION_SECRET is using a known insecure placeholder value. " +
"Generate a secure secret with: openssl rand -base64 32"
);
}
if (secret.length < MIN_SESSION_SECRET_LENGTH) {
throw new Error(
`SESSION_SECRET must be at least ${MIN_SESSION_SECRET_LENGTH} characters long in production. ` +
"Generate a secure secret with: openssl rand -base64 32"
);
}
}
return finalSecret;
}
function resolveAdminCredentials() {
const rawUsername = process.env.ADMIN_USERNAME ?? null;
const rawPassword = process.env.ADMIN_PASSWORD ?? null;
const username = rawUsername?.trim() || DEFAULT_ADMIN_USERNAME;
const password = rawPassword?.trim() || DEFAULT_ADMIN_PASSWORD;
// In development, allow defaults
if (isDevelopment) {
if (username === DEFAULT_ADMIN_USERNAME || password === DEFAULT_ADMIN_PASSWORD) {
console.log("Using default admin credentials for development (admin/admin)");
}
return { username, password };
}
// In production build phase, allow defaults temporarily
if (isProduction && !isNodeRuntime) {
return { username, password };
}
// Strict validation in production runtime
if (isRuntimeProduction) {
const errors: string[] = [];
// Username validation - just ensure it's set
if (!rawUsername || !username) {
errors.push(
"ADMIN_USERNAME must be set"
);
}
// Password validation - strict requirements
if (!rawPassword || password === DEFAULT_ADMIN_PASSWORD) {
errors.push(
"ADMIN_PASSWORD must be set to a custom value in production (not 'admin')"
);
} else {
if (password.length < MIN_ADMIN_PASSWORD_LENGTH) {
errors.push(
`ADMIN_PASSWORD must be at least ${MIN_ADMIN_PASSWORD_LENGTH} characters long`
);
}
if (!/[A-Z]/.test(password) || !/[a-z]/.test(password)) {
errors.push(
"ADMIN_PASSWORD must include both uppercase and lowercase letters"
);
}
if (!/[0-9]/.test(password)) {
errors.push(
"ADMIN_PASSWORD must include at least one number"
);
}
if (!/[^A-Za-z0-9]/.test(password)) {
errors.push(
"ADMIN_PASSWORD must include at least one special character"
);
}
}
if (errors.length > 0) {
throw new Error(
"Admin credentials validation failed:\n" +
errors.map(e => ` - ${e}`).join("\n") +
"\n\nSet secure credentials using ADMIN_USERNAME and ADMIN_PASSWORD environment variables."
);
}
}
return { username, password };
}
// Lazy initialization to avoid executing during build time
let _adminCredentials: { username: string; password: string } | null = null;
let _sessionSecret: string | null = null;
function getAdminCredentials() {
if (!_adminCredentials) {
_adminCredentials = resolveAdminCredentials();
}
return _adminCredentials;
}
function getSessionSecret() {
if (!_sessionSecret) {
_sessionSecret = resolveSessionSecret();
}
return _sessionSecret;
}
export const config = {
get sessionSecret() {
return getSessionSecret();
},
caddyApiUrl: process.env.CADDY_API_URL ?? DEFAULT_CADDY_URL,
baseUrl: process.env.BASE_URL ?? "http://localhost:3000",
get adminUsername() {
return getAdminCredentials().username;
},
get adminPassword() {
return getAdminCredentials().password;
}
};
/**
* Validates configuration at server startup in production.
* Throws if production is running with insecure default values.
* Safe to call during build - only validates when actually serving.
*/
export function validateProductionConfig() {
if (isRuntimeProduction) {
// Force validation by accessing the config values
// This will throw if defaults are being used in production
const _ = config.sessionSecret;
const __ = config.adminUsername;
const ___ = config.adminPassword;
}
}