import bcrypt from 'bcryptjs'; import { randomBytes } from 'node:crypto'; import { db, getSetting, setSetting } from './db.js'; import { config } from './config.js'; export const COOKIE_NAME = 'pg_session'; // Resolve a stable cookie-signing secret (persisted so sessions survive restarts). export function getCookieSecret() { if (config.cookieSecret) return config.cookieSecret; let secret = getSetting('cookie_secret'); if (!secret) { secret = randomBytes(32).toString('hex'); setSetting('cookie_secret', secret); } return secret; } // Resolve the bcrypt password hash. The environment (PG_PASSWORD / PG_PASSWORD_HASH) // is authoritative: when set, it overwrites the stored hash on every boot so that // platform-managed password changes (e.g. the StartOS "Set Login Password" action, // which restarts the service with a new PG_PASSWORD) actually take effect. export function initPassword() { if (config.passwordHash) { setSetting('password_hash', config.passwordHash); return config.passwordHash; } const stored = getSetting('password_hash'); if (config.password) { // Re-hash only when the password actually changed (bcrypt salts differ each run). if (!stored || !bcrypt.compareSync(config.password, stored)) { const hash = bcrypt.hashSync(config.password, 10); setSetting('password_hash', hash); // Password changed out from under existing sessions — invalidate them. if (stored) db.prepare('DELETE FROM sessions').run(); return hash; } return stored; } if (stored) return stored; const hash = bcrypt.hashSync('gunner', 10); setSetting('password_hash', hash); console.warn('\n⚠ No PG_PASSWORD set — using default password "gunner". Set PG_PASSWORD before deploying.\n'); return hash; } export function verifyPassword(plain) { const hash = config.passwordHash || getSetting('password_hash'); if (!hash) return false; return bcrypt.compareSync(String(plain || ''), hash); } export function setPassword(plain) { const hash = bcrypt.hashSync(String(plain), 10); setSetting('password_hash', hash); // Invalidate existing sessions when the password changes. db.prepare('DELETE FROM sessions').run(); } export function createSession() { const token = randomBytes(32).toString('hex'); const expires = new Date(Date.now() + config.sessionDays * 86400_000).toISOString(); db.prepare('INSERT INTO sessions (token, expires_at) VALUES (?, ?)').run(token, expires); return token; } export function isValidSession(token) { if (!token) return false; const row = db.prepare('SELECT expires_at FROM sessions WHERE token = ?').get(token); if (!row) return false; if (new Date(row.expires_at).getTime() < Date.now()) { db.prepare('DELETE FROM sessions WHERE token = ?').run(token); return false; } return true; } export function destroySession(token) { if (token) db.prepare('DELETE FROM sessions WHERE token = ?').run(token); } export function cleanupExpiredSessions() { db.prepare("DELETE FROM sessions WHERE expires_at < datetime('now')").run(); }