import type { Request, Response, NextFunction } from 'express' import crypto from 'node:crypto' import { getDb } from '../db/connection.js' export function adminAuth(req: Request, res: Response, next: NextFunction): void { const token = req.cookies?.admin_session if (!token) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Authentication required' } }); return } const db = getDb() const session = db.prepare("SELECT token FROM admin_sessions WHERE token = ? AND expires_at > datetime('now')").get(token) as { token: string } | undefined if (!session) { res.status(401).json({ error: { code: 'SESSION_EXPIRED', message: 'Session expired' } }); return } next() } const loginAttempts = new Map() export function rateLimit(req: Request, res: Response, next: NextFunction): void { const ip = req.ip || 'unknown' const now = Date.now() const entry = loginAttempts.get(ip) if (entry) { if (now > entry.resetAt) { loginAttempts.set(ip, { count: 1, resetAt: now + 60_000 }) } else if (entry.count >= 5) { res.status(429).json({ error: { code: 'RATE_LIMITED', message: 'Too many attempts' } }); return } else { entry.count++ } } else { loginAttempts.set(ip, { count: 1, resetAt: now + 60_000 }) } next() } export function createSession(): string { const token = crypto.randomBytes(32).toString('hex') const db = getDb() db.prepare("INSERT INTO admin_sessions (token, expires_at) VALUES (?, datetime('now', '+24 hours'))").run(token) db.prepare("DELETE FROM admin_sessions WHERE expires_at < datetime('now')").run() return token } export function deleteSession(token: string): void { const db = getDb() db.prepare('DELETE FROM admin_sessions WHERE token = ?').run(token) } export function verifyPassword(input: string): boolean { const expected = process.env.ADMIN_PASSWORD if (!expected) return false const inputBuf = Buffer.from(input) const expectedBuf = Buffer.from(expected) if (inputBuf.length !== expectedBuf.length) return false return crypto.timingSafeEqual(inputBuf, expectedBuf) }