import { cookies } from "next/headers"; import crypto from "crypto"; const COOKIE_NAME = "bh-admin-token"; const TOKEN_TTL = 24 * 60 * 60 * 1000; // 24 hours function getSecret(): string { const secret = process.env.AUTH_SECRET; if (!secret) throw new Error("AUTH_SECRET is not set"); return secret; } function getAdminPassword(): string { const pw = process.env.ADMIN_PASSWORD; if (!pw) throw new Error("ADMIN_PASSWORD is not set"); return pw; } export function verifyPassword(password: string): boolean { const expected = getAdminPassword(); if (password.length !== expected.length) return false; const a = Buffer.from(password); const b = Buffer.from(expected); // Pad to equal length for timingSafeEqual if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b); } export function signToken(): string { const payload = { role: "admin", exp: Date.now() + TOKEN_TTL, }; const data = Buffer.from(JSON.stringify(payload)).toString("base64url"); const sig = crypto .createHmac("sha256", getSecret()) .update(data) .digest("base64url"); return `${data}.${sig}`; } export async function isAuthenticated(): Promise { const cookieStore = await cookies(); const token = cookieStore.get(COOKIE_NAME)?.value; if (!token) return false; return verifyTokenNode(token); } /** Node.js runtime token verification (for API routes / server components) */ function verifyTokenNode(token: string): boolean { try { const [data, sig] = token.split("."); if (!data || !sig) return false; const expectedSig = crypto .createHmac("sha256", getSecret()) .update(data) .digest("base64url"); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSig))) return false; const payload = JSON.parse( Buffer.from(data, "base64url").toString() ) as { role: string; exp: number }; return payload.role === "admin" && payload.exp > Date.now(); } catch { return false; } } export const CSRF_COOKIE_NAME = "bh-csrf-token"; export function generateCsrfToken(): string { return crypto.randomBytes(32).toString("base64url"); } export { COOKIE_NAME };