funktechnik-schubert-website/lib/admin/auth.ts
2026-07-03 22:25:04 +02:00

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,
};
}