import { createHmac, timingSafeEqual } from "crypto"; import { cookies } from "next/headers"; export const adminSessionCookie = "fs_admin_session"; const sessionMaxAgeSeconds = 60 * 60 * 8; function secret() { return process.env.ADMIN_SESSION_SECRET ?? "dev-admin-session-secret-change-me"; } function adminEmail() { return process.env.ADMIN_EMAIL; } function adminPassword() { return process.env.ADMIN_PASSWORD; } function sign(value: string) { return createHmac("sha256", secret()).update(value).digest("base64url"); } function safeEqual(left: string, right: string) { const leftBuffer = Buffer.from(left); const rightBuffer = Buffer.from(right); return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer); } export function validateAdminCredentials(email: string, password: string) { const configuredEmail = adminEmail(); const configuredPassword = adminPassword(); if (!configuredEmail || !configuredPassword) return false; return safeEqual(email.trim().toLowerCase(), configuredEmail.toLowerCase()) && safeEqual(password, configuredPassword); } export function createAdminSession() { const expiresAt = Date.now() + sessionMaxAgeSeconds * 1000; const payload = Buffer.from(JSON.stringify({ sub: adminEmail() ?? "admin", exp: expiresAt })).toString("base64url"); return `${payload}.${sign(payload)}`; } export function verifyAdminSession(token?: string) { if (!token) return false; const [payload, signature] = token.split("."); if (!payload || !signature || !safeEqual(signature, sign(payload))) return false; try { const session = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: number }; return typeof session.exp === "number" && session.exp > Date.now(); } catch { return false; } } export async function requireAdminSession() { const cookieStore = await cookies(); return verifyAdminSession(cookieStore.get(adminSessionCookie)?.value); } export function adminCookieOptions() { return { httpOnly: true, sameSite: "lax" as const, secure: process.env.AUTH_COOKIE_SECURE === "true", path: "/", maxAge: sessionMaxAgeSeconds, }; }