import { createHash, randomBytes } from 'crypto'; import type { RowDataPacket } from 'mysql2'; import { cookies } from 'next/headers'; import { getPool } from '@/lib/db'; export const SESSION_COOKIE = 'admin_session'; const SESSION_MAX_AGE_SEC = 60 * 60 * 24 * 7; // 7 days export type AdminUserRow = { id: number; email: string; password_hash: string; is_approved: boolean; is_admin: boolean; }; function hashToken(token: string): string { return createHash('sha256').update(token, 'utf8').digest('hex'); } export async function createSession(userId: number): Promise { const token = randomBytes(32).toString('hex'); const tokenHash = hashToken(token); const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_SEC * 1000); const pool = getPool(); await pool.query( `INSERT INTO admin_sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)`, [userId, tokenHash, expiresAt] ); const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE, token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: SESSION_MAX_AGE_SEC, path: '/', }); return token; } export async function destroySession(): Promise { const cookieStore = await cookies(); const token = cookieStore.get(SESSION_COOKIE)?.value; if (token) { const pool = getPool(); await pool.query( 'DELETE FROM admin_sessions WHERE token_hash = ?', [hashToken(token)] ); } cookieStore.delete(SESSION_COOKIE); } export async function getSessionUser(): Promise { const cookieStore = await cookies(); const token = cookieStore.get(SESSION_COOKIE)?.value; if (!token) { return null; } const tokenHash = hashToken(token); const pool = getPool(); const [rows] = await pool.query( `SELECT u.id, u.email, u.password_hash, u.is_approved, u.is_admin FROM admin_sessions s JOIN admin_users u ON u.id = s.user_id WHERE s.token_hash = ? AND s.expires_at > NOW()`, [tokenHash] ); if (rows.length === 0) { cookieStore.delete(SESSION_COOKIE); return null; } const row = rows[0] as Record; return { id: row.id as number, email: row.email as string, password_hash: row.password_hash as string, is_approved: Boolean(row.is_approved), is_admin: Boolean(row.is_admin), }; }