69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
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,
|
|
};
|
|
}
|