import { createHmac, timingSafeEqual } from "node:crypto"; import { cookies } from "next/headers"; import { isSecureDeployment } from "@/lib/security/cookie-security"; import { getManagerConfig } from "./manager-config"; const COOKIE_NAME = "nextchair_manager_session"; function signature(value: string, secret: string) { return createHmac("sha256", secret).update(value).digest("hex"); } export function isManagerConfigured() { const config = getManagerConfig(); return Boolean(config.adminEmail && config.adminPassword && config.authSecret); } export function authenticateManager(email: string, password: string) { const config = getManagerConfig(); if (!isManagerConfigured() || email.trim().toLowerCase() !== config.adminEmail.trim().toLowerCase()) return false; const expected = Buffer.from(config.adminPassword); const received = Buffer.from(password); return expected.length === received.length && timingSafeEqual(expected, received); } export async function createManagerSession() { const config = getManagerConfig(); const value = `${config.adminEmail.trim().toLowerCase()}.${Date.now()}`; (await cookies()).set(COOKIE_NAME, `${value}.${signature(value, config.authSecret)}`, { httpOnly: true, sameSite: "strict", secure: isSecureDeployment(), // The Manager UI is under /manager, but its authenticated API is under // /api/manager, so the cookie must cover both paths. path: "/", maxAge: 60 * 60 * 8, }); } export async function clearManagerSession() { (await cookies()).delete(COOKIE_NAME); } export async function isManagerAuthenticated() { const config = getManagerConfig(); if (!config.authSecret) return false; const rawValue = (await cookies()).get(COOKIE_NAME)?.value; const value = rawValue ? decodeURIComponent(rawValue) : undefined; if (!value) return false; const separator = value.lastIndexOf("."); if (separator < 1) return false; const payload = value.slice(0, separator); const received = Buffer.from(value.slice(separator + 1)); const expected = Buffer.from(signature(payload, config.authSecret)); return received.length === expected.length && timingSafeEqual(received, expected); }