import bcrypt from 'bcryptjs'; import { randomBytes } from 'node:crypto'; import { getDb } from './db/index.js'; const SESSION_DAYS = 30; const COOKIE_NAME = 'oradio_sid'; export function hashPassword(plain) { return bcrypt.hashSync(plain, 10); } export function verifyPassword(plain, hash) { return bcrypt.compareSync(plain, hash); } export function createSession(userId) { const token = randomBytes(32).toString('hex'); const expires = new Date(Date.now() + SESSION_DAYS * 86400e3).toISOString(); getDb().prepare('INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)') .run(token, userId, expires); return { token, expires }; } export function destroySession(token) { if (token) getDb().prepare('DELETE FROM sessions WHERE token = ?').run(token); } export function getUserBySession(token) { if (!token) return null; return getDb().prepare(` SELECT u.id, u.username, u.role FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token = ? AND s.expires_at > datetime('now') `).get(token); } export function readSessionToken(req) { const raw = req.headers.cookie || ''; for (const part of raw.split(';')) { const [k, v] = part.trim().split('='); if (k === COOKIE_NAME) return decodeURIComponent(v || ''); } return null; } export function setSessionCookie(res, token, expires) { const attrs = [ `${COOKIE_NAME}=${encodeURIComponent(token)}`, 'Path=/', 'HttpOnly', 'SameSite=Lax', `Expires=${new Date(expires).toUTCString()}` ]; res.setHeader('Set-Cookie', attrs.join('; ')); } export function clearSessionCookie(res) { res.setHeader('Set-Cookie', `${COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`); } export function authMiddleware(req, _res, next) { const token = readSessionToken(req); req.session = { token }; req.user = getUserBySession(token); next(); } export function requireUser(req, res, next) { if (!req.user) return res.status(401).json({ error: 'auth required' }); next(); } export function requireAdmin(req, res, next) { if (!req.user) return res.status(401).json({ error: 'auth required' }); if (req.user.role !== 'admin') return res.status(403).json({ error: 'admin only' }); next(); } export function ensureBootstrapAdmin({ username, password }) { if (!username || !password) return; const db = getDb(); const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(username); if (existing) return; const info = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)') .run(username, hashPassword(password), 'admin'); db.prepare('INSERT INTO profiles (user_id, display_name) VALUES (?, ?)') .run(info.lastInsertRowid, username); console.log(`[auth] bootstrap admin '${username}' created`); }