import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { createHmac, timingSafeEqual, randomBytes, scryptSync } from "crypto"; import { getDb } from "./db"; const SESSION_SECRET = process.env.SESSION_SECRET!; const COOKIE_NAME = "session"; const MAX_AGE = 60 * 60 * 24 * 7; // 7 dagen // impersonatorId: admin die via "Inloggen als" de pagina van een expert bekijkt (alleen lezen) type SessionPayload = { userId: number; email: string; name: string; impersonatorId?: number }; function sign(payload: SessionPayload): string { const data = JSON.stringify(payload); const encoded = Buffer.from(data).toString("base64url"); const sig = createHmac("sha256", SESSION_SECRET).update(encoded).digest("base64url"); return `${encoded}.${sig}`; } function verify(token: string): SessionPayload | null { const [encoded, sig] = token.split("."); if (!encoded || !sig) return null; const expected = createHmac("sha256", SESSION_SECRET).update(encoded).digest("base64url"); try { if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return null; } catch { return null; } return JSON.parse(Buffer.from(encoded, "base64url").toString()); } export async function getSession(): Promise { const jar = await cookies(); const token = jar.get(COOKIE_NAME)?.value; if (!token) return null; return verify(token); } export async function createSession(payload: SessionPayload) { const jar = await cookies(); jar.set(COOKIE_NAME, sign(payload), { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: MAX_AGE, path: "/", }); } export async function destroySession() { const jar = await cookies(); jar.delete(COOKIE_NAME); } export function hashPassword(password: string): string { const salt = randomBytes(16).toString("hex"); const hash = scryptSync(password, salt, 64).toString("hex"); return `${salt}:${hash}`; } export function verifyPassword(password: string, stored: string): boolean { // Uitgenodigde experts hebben nog geen wachtwoord (lege hash) en kunnen niet inloggen if (!stored || !stored.includes(":")) return false; const [salt, hash] = stored.split(":"); const attempt = scryptSync(password, salt, 64).toString("hex"); return timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(attempt, "hex")); } export function getUserByEmail(email: string) { const db = getDb(); return db.prepare("SELECT * FROM users WHERE email = ?").get(email) as | { id: number; email: string; name: string; password_hash: string; role: string } | undefined; } export type Role = "admin" | "expert"; export type CurrentUser = { id: number; email: string; name: string; role: Role; // Gezet als een admin via "Inloggen als" meekijkt; alleen geldig als die gebruiker nog admin is impersonator: { id: number; email: string; name: string } | null; }; // Ingelogde gebruiker met zijn rol uit de database (niet uit de cookie), zodat een // gewijzigde of verwijderde gebruiker direct geen toegang meer heeft. export async function getCurrentUser(): Promise { const session = await getSession(); if (!session) return null; const user = getDb().prepare("SELECT id, email, name, role FROM users WHERE id = ?").get(session.userId) as | { id: number; email: string; name: string; role: string } | undefined; if (!user) return null; let impersonator: CurrentUser["impersonator"] = null; if (session.impersonatorId) { const admin = getDb() .prepare("SELECT id, email, name FROM users WHERE id = ? AND role = 'admin'") .get(session.impersonatorId) as { id: number; email: string; name: string } | undefined; // Een meekijk-sessie van een gebruiker die geen admin (meer) is, is ongeldig if (!admin) return null; impersonator = { id: admin.id, email: admin.email, name: admin.name }; } return { id: user.id, email: user.email, name: user.name, role: user.role === "expert" ? "expert" : "admin", impersonator, }; } export function homeForRole(role: Role): string { return role === "expert" ? "/expert" : "/tenants"; } // Voor alle beheerpagina's en -acties: alleen admins. Experts gaan naar hun eigen pagina. export async function requireAdmin(): Promise { const user = await getCurrentUser(); if (!user) redirect("/login"); if (user.role !== "admin") redirect(homeForRole(user.role)); return user; } // Voor de expertpagina: alleen experts. export async function requireExpert(): Promise { const user = await getCurrentUser(); if (!user) redirect("/login"); if (user.role !== "expert") redirect(homeForRole(user.role)); return user; }