Files
CC-Manager/server/src/env.ts
T
Harlekin bc86e2df26 Datenminimierung: Rohdaten, Protokoll und Loeschung
Drei Befunde aus einer Durchsicht der gespeicherten personenbezogenen Daten:

Der Import legte bisher jede Spalte der hochgeladenen Datei dauerhaft ab,
auch Mailadressen oder Rechnungsdaten, die nie zugeordnet wurden. Nach dem
Buchen oder Verwerfen bleiben jetzt nur noch die gemappten Spalten stehen.

Ein fehlgeschlagener Login protokollierte den eingetippten Benutzernamen.
Wer sein Passwort ins Namensfeld tippt, hatte es damit im Klartext im
Audit-Log. Der Name wird nur noch gespeichert, wenn das Konto existiert.

Das Loeschen eines Spielers liess Spuren in den Import-Zeilen und in den
Details frueherer Audit-Eintraege zurueck. Beides wird jetzt mit abgeraeumt;
die Vorgaenge bleiben als Eintrag bestehen, nur ohne Inhalt.

Dazu eine Aufbewahrungsfrist fuer das Audit-Log (Standard 12 Monate, ueber
AUDIT_RETENTION_DAYS einstellbar). Die Guthaben-Historie ist davon nicht
betroffen - sie bleibt die Quelle der Wahrheit fuer bezahlte Ansprueche.
2026-09-18 15:22:02 +02:00

55 lines
1.9 KiB
TypeScript

import { z } from 'zod';
const schema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().positive().default(3000),
HOST: z.string().default('0.0.0.0'),
DATABASE_URL: z.string().min(1, 'DATABASE_URL fehlt'),
SESSION_SECRET: z.string().min(32, 'SESSION_SECRET muss mindestens 32 Zeichen lang sein'),
// Lebensdauer einer Session in Stunden.
SESSION_TTL_HOURS: z.coerce.number().int().positive().default(12),
// Wird beim ersten Start angelegt, solange noch kein Account existiert.
BOOTSTRAP_ADMIN_USER: z.string().min(3).default('admin'),
BOOTSTRAP_ADMIN_PASSWORD: z.string().min(12).optional(),
// Hinter einem Reverse Proxy noetig, damit Client-IPs im Audit-Log stimmen.
TRUST_PROXY: z
.string()
.default('true')
.transform((v) => v === 'true' || v === '1'),
// Muss in Produktion true sein (Cookies nur ueber HTTPS).
COOKIE_SECURE: z
.string()
.optional()
.transform((v) => (v === undefined ? undefined : v === 'true' || v === '1')),
// Aufbewahrung der Audit-Eintraege in Tagen. 0 schaltet das Aufraeumen ab.
// Die Guthaben-Historie ist davon nicht betroffen, die bleibt dauerhaft.
AUDIT_RETENTION_DAYS: z.coerce.number().int().min(0).default(365),
LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
});
const parsed = schema.safeParse(process.env);
if (!parsed.success) {
const issues = parsed.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n');
console.error(`Konfiguration unvollstaendig:\n${issues}`);
process.exit(1);
}
const raw = parsed.data;
export const env = {
...raw,
isProduction: raw.NODE_ENV === 'production',
// In Produktion ohne expliziten Wert: sichere Cookies erzwingen.
cookieSecure: raw.COOKIE_SECURE ?? raw.NODE_ENV === 'production',
};
export type Env = typeof env;