// Admin-Authentifizierung: automatisch generiertes Passwort (20 Zeichen), persistent in // data/admin.json (Klartext, chmod 600 -> per CLI abrufbar). Sessions liegen im Speicher. import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { randomBytes, randomInt, timingSafeEqual } from 'node:crypto'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DATA_DIR = join(__dirname, '..', 'data'); const ADMIN_FILE = join(DATA_DIR, 'admin.json'); const LOWER = 'abcdefghijkmnopqrstuvwxyz'; const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; const DIGITS = '23456789'; const SPECIAL = '!@#$%&*()-_=+?'; const ALL = LOWER + UPPER + DIGITS + SPECIAL; const PW_LEN = 20; const SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage const sessions = new Map(); // token -> expiresAt function generatePassword() { // je 1 Zeichen aus jeder Klasse garantieren, Rest zufaellig, dann mischen const pick = (set) => set[randomInt(set.length)]; const chars = [pick(LOWER), pick(UPPER), pick(DIGITS), pick(SPECIAL)]; while (chars.length < PW_LEN) chars.push(pick(ALL)); for (let i = chars.length - 1; i > 0; i--) { const j = randomInt(i + 1); [chars[i], chars[j]] = [chars[j], chars[i]]; } return chars.join(''); } function write(data) { if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true }); writeFileSync(ADMIN_FILE, JSON.stringify(data, null, 2)); try { chmodSync(ADMIN_FILE, 0o600); } catch {} } // Liest die Admin-Daten; legt beim ersten Mal Passwort + Secret an. function ensure() { if (existsSync(ADMIN_FILE)) { try { const data = JSON.parse(readFileSync(ADMIN_FILE, 'utf8')); if (data && data.password) return data; } catch {} } const data = { password: generatePassword(), secret: randomBytes(32).toString('hex') }; write(data); return data; } export function getPassword() { return ensure().password; } export function resetPassword() { const data = ensure(); data.password = generatePassword(); write(data); return data.password; } export function verifyPassword(input) { const real = ensure().password; const a = Buffer.from(String(input || '')); const b = Buffer.from(real); if (a.length !== b.length) return false; return timingSafeEqual(a, b); } // --- Sessions (In-Memory) --- export function createSession() { const token = randomBytes(32).toString('hex'); sessions.set(token, Date.now() + SESSION_TTL_MS); return token; } export function validSession(token) { if (!token) return false; const exp = sessions.get(token); if (!exp) return false; if (Date.now() > exp) { sessions.delete(token); return false; } return true; } export function destroySession(token) { if (token) sessions.delete(token); } export const SESSION_COOKIE = 'admin_session'; export const SESSION_MAX_AGE = SESSION_TTL_MS;