Files
Calbook/lib/security/config-guard.ts

170 lines
4.5 KiB
TypeScript

type ConfigIssue = {
key: string;
message: string;
};
const WEAK_DEFAULT_VALUES = new Set([
"",
"calbook",
"calbook123",
"admin",
"admin123",
"passwort",
"password",
"123456",
"bitte-einen-langen-random-wert-setzen",
"bitte-einen-random-cron-secret-setzen",
"bitte-einen-random-salt-setzen",
"0123456789abcdef0123456789abcdef",
"bittesicher123!",
"change_me",
"changeme",
"replace_me",
"todo"
]);
function normalize(value: string | undefined | null) {
return (value ?? "").trim().toLowerCase();
}
function isWeakSecret(value: string | undefined | null) {
const normalized = normalize(value);
if (!normalized) return true;
if (WEAK_DEFAULT_VALUES.has(normalized)) return true;
if (normalized.startsWith("bitte-")) return true;
if (normalized.includes("random-wert-setzen")) return true;
if (normalized.includes("random-secret-setzen")) return true;
return false;
}
function validateRuntimeConfig(): ConfigIssue[] {
const issues: ConfigIssue[] = [];
const nextAuthSecret = process.env.NEXTAUTH_SECRET;
if (!nextAuthSecret || nextAuthSecret.trim().length < 32 || isWeakSecret(nextAuthSecret)) {
issues.push({
key: "NEXTAUTH_SECRET",
message: "muss gesetzt, einzigartig und mindestens 32 Zeichen lang sein."
});
}
const cronSecret = process.env.CRON_SECRET;
if (!cronSecret || cronSecret.trim().length < 24 || isWeakSecret(cronSecret)) {
issues.push({
key: "CRON_SECRET",
message: "muss gesetzt, einzigartig und mindestens 24 Zeichen lang sein."
});
}
const caldavKey = process.env.CALDAV_ENCRYPTION_KEY;
if (!caldavKey || caldavKey.trim().length < 32 || isWeakSecret(caldavKey)) {
issues.push({
key: "CALDAV_ENCRYPTION_KEY",
message: "muss gesetzt, einzigartig und mindestens 32 Zeichen lang sein."
});
}
const jitsiSalt = process.env.JITSI_ROOM_SALT;
if (!jitsiSalt || jitsiSalt.trim().length < 24 || isWeakSecret(jitsiSalt)) {
issues.push({
key: "JITSI_ROOM_SALT",
message: "muss gesetzt, einzigartig und mindestens 24 Zeichen lang sein."
});
}
const publicUrl = process.env.PUBLIC_URL;
if (publicUrl && publicUrl.trim()) {
try {
const parsed = new URL(publicUrl);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
issues.push({
key: "PUBLIC_URL",
message: "muss mit http:// oder https:// beginnen."
});
}
} catch {
issues.push({
key: "PUBLIC_URL",
message: "ist kein gültiger URL-Wert."
});
}
}
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl || !databaseUrl.trim()) {
issues.push({
key: "DATABASE_URL",
message: "muss gesetzt sein."
});
} else {
try {
const parsed = new URL(databaseUrl);
const dbPassword = decodeURIComponent(parsed.password ?? "");
if (isWeakSecret(dbPassword) || dbPassword.length < 12) {
issues.push({
key: "DATABASE_URL",
message: "nutzt ein schwaches Datenbank-Passwort."
});
}
} catch {
issues.push({
key: "DATABASE_URL",
message: "ist kein gültiger URL-Wert."
});
}
}
return issues;
}
function validateSeedConfig(): ConfigIssue[] {
const issues: ConfigIssue[] = [];
const adminPassword = process.env.ADMIN_PASSWORD;
if (!adminPassword || adminPassword.trim().length < 12 || isWeakSecret(adminPassword)) {
issues.push({
key: "ADMIN_PASSWORD",
message: "muss gesetzt, stark und mindestens 12 Zeichen lang sein."
});
}
return issues;
}
function formatIssues(label: string, issues: ConfigIssue[]) {
return [
`[security] ${label}`,
...issues.map((issue) => `- ${issue.key}: ${issue.message}`)
].join("\n");
}
function isProductionRuntime() {
if (process.env.NODE_ENV !== "production") return false;
if (process.env.NEXT_PHASE === "phase-production-build") return false;
return true;
}
export function assertSecureRuntimeConfig() {
if (process.env.NODE_ENV === "test") return;
const issues = validateRuntimeConfig();
if (issues.length === 0) return;
const message = formatIssues("Unsichere Runtime-Konfiguration erkannt.", issues);
if (isProductionRuntime()) {
throw new Error(message);
}
// eslint-disable-next-line no-console
console.warn(message);
}
export function assertSecureSeedConfig() {
if (process.env.NODE_ENV === "test") return;
const issues = validateSeedConfig();
if (issues.length === 0) return;
throw new Error(formatIssues("Unsichere Seed-Konfiguration erkannt.", issues));
}