import { readJson, writeJson } from './storage'; import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'; import type { Cookies } from '@sveltejs/kit'; const ADMIN_FILE = 'admin.json'; const SESSION_FILE = 'sessions.json'; const SESSION_COOKIE = 'gds_admin'; const SESSION_TTL_MS = 1000 * 60 * 60 * 24 * 7; // 7 days type AdminRecord = { username: string; salt: string; hash: string; createdAt?: number; }; type SessionRecord = { token: string; username: string; expiresAt: number; }; function hashPassword(password: string, salt: string): string { return scryptSync(password, salt, 64).toString('hex'); } function loadAdmins(): AdminRecord[] { const raw = readJson(ADMIN_FILE, null); if (!raw) return []; if (Array.isArray(raw)) return raw; // Legacy single-record format — migrate in-memory (persisted on next write). return [raw]; } function saveAdmins(admins: AdminRecord[]): void { writeJson(ADMIN_FILE, admins); } export function seedDefaultAdminIfMissing(): { username: string; tempPassword?: string } { const admins = loadAdmins(); if (admins.length > 0) { // Ensure migration is persisted if legacy format was detected. const raw = readJson(ADMIN_FILE, null); if (raw && !Array.isArray(raw)) saveAdmins(admins); return { username: admins[0].username }; } const username = process.env.ADMIN_USERNAME || 'admin'; const password = process.env.ADMIN_PASSWORD || 'changeme'; const salt = randomBytes(16).toString('hex'); const record: AdminRecord = { username, salt, hash: hashPassword(password, salt), createdAt: Date.now() }; saveAdmins([record]); return { username, tempPassword: password }; } export function verifyCredentials(username: string, password: string): boolean { const record = loadAdmins().find((a) => a.username === username); if (!record) return false; const candidate = Buffer.from(hashPassword(password, record.salt), 'hex'); const stored = Buffer.from(record.hash, 'hex'); if (candidate.length !== stored.length) return false; return timingSafeEqual(candidate, stored); } export function changePassword( username: string, currentPassword: string, newPassword: string ): boolean { if (!verifyCredentials(username, currentPassword)) return false; const admins = loadAdmins(); const idx = admins.findIndex((a) => a.username === username); if (idx === -1) return false; const salt = randomBytes(16).toString('hex'); admins[idx] = { ...admins[idx], salt, hash: hashPassword(newPassword, salt) }; saveAdmins(admins); return true; } export type AdminSummary = { username: string; createdAt?: number }; export function listAdmins(): AdminSummary[] { return loadAdmins().map(({ username, createdAt }) => ({ username, createdAt })); } export function createAdmin(username: string, password: string): { ok: true } | { ok: false; error: string } { const clean = username.trim(); if (!clean) return { ok: false, error: 'Username obbligatorio.' }; if (clean.length < 3) return { ok: false, error: 'Username troppo corto (min 3 caratteri).' }; if (!/^[a-zA-Z0-9_.-]+$/.test(clean)) { return { ok: false, error: 'Username: usa solo lettere, numeri, . _ -' }; } if (password.length < 8) return { ok: false, error: 'Password troppo corta (min 8 caratteri).' }; const admins = loadAdmins(); if (admins.some((a) => a.username === clean)) { return { ok: false, error: 'Username già in uso.' }; } const salt = randomBytes(16).toString('hex'); admins.push({ username: clean, salt, hash: hashPassword(password, salt), createdAt: Date.now() }); saveAdmins(admins); return { ok: true }; } export function deleteAdmin(username: string): { ok: true } | { ok: false; error: string } { const admins = loadAdmins(); if (admins.length <= 1) { return { ok: false, error: 'Impossibile eliminare l\'ultimo amministratore.' }; } const next = admins.filter((a) => a.username !== username); if (next.length === admins.length) return { ok: false, error: 'Amministratore non trovato.' }; saveAdmins(next); // Revoke any active sessions for the removed user. const sessions = readJson(SESSION_FILE, []).filter((s) => s.username !== username); writeJson(SESSION_FILE, sessions); return { ok: true }; } function loadSessions(): SessionRecord[] { const now = Date.now(); const all = readJson(SESSION_FILE, []); const live = all.filter((s) => s.expiresAt > now); if (live.length !== all.length) writeJson(SESSION_FILE, live); return live; } export function createSession(username: string, cookies: Cookies): string { const sessions = loadSessions(); const token = randomBytes(32).toString('hex'); const record: SessionRecord = { token, username, expiresAt: Date.now() + SESSION_TTL_MS }; sessions.push(record); writeJson(SESSION_FILE, sessions); cookies.set(SESSION_COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: SESSION_TTL_MS / 1000 }); return token; } export function destroySession(cookies: Cookies): void { const token = cookies.get(SESSION_COOKIE); if (token) { const sessions = loadSessions().filter((s) => s.token !== token); writeJson(SESSION_FILE, sessions); } cookies.delete(SESSION_COOKIE, { path: '/' }); } export function getSessionUser(cookies: Cookies): { username: string } | null { const token = cookies.get(SESSION_COOKIE); if (!token) return null; const session = loadSessions().find((s) => s.token === token); if (!session) return null; // If the user has been removed since, invalidate. const exists = loadAdmins().some((a) => a.username === session.username); if (!exists) return null; return { username: session.username }; }