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; }; type SessionRecord = { token: string; username: string; expiresAt: number; }; function hashPassword(password: string, salt: string): string { return scryptSync(password, salt, 64).toString('hex'); } export function seedDefaultAdminIfMissing(): { username: string; tempPassword?: string } { const existing = readJson(ADMIN_FILE, null); if (existing) return { username: existing.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) }; writeJson(ADMIN_FILE, record); return { username, tempPassword: password }; } export function verifyCredentials(username: string, password: string): boolean { const record = readJson(ADMIN_FILE, null); if (!record) return false; if (record.username !== username) 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(currentPassword: string, newPassword: string): boolean { const record = readJson(ADMIN_FILE, null); if (!record) return false; if (!verifyCredentials(record.username, currentPassword)) return false; const salt = randomBytes(16).toString('hex'); writeJson(ADMIN_FILE, { username: record.username, salt, hash: hashPassword(newPassword, salt) }); return 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; return { username: session.username }; }