/** * Serverseitige Zugriffskontrolle fuer den Adminbereich. Wird von JEDER * Admin-Seite, Server-Action und jedem Admin-Endpunkt aufgerufen — der * Proxy (src/proxy.ts) leitet nur vorab um und ist keine Sicherheitsgrenze. */ import { headers } from "next/headers"; import { redirect } from "next/navigation"; import type { Actor } from "./audit"; import { getAuth } from "./auth"; export type AdminUser = { id: string; email: string; name: string; twoFactorEnabled: boolean }; async function currentAdmin(): Promise { // Zuerst die Request-Header lesen: das macht jede Admin-Route dynamisch. // Sonst versucht "next build" sie vorab zu rendern und scheitert ohne // Secrets im Container (getAuth() braucht BETTER_AUTH_SECRET). const requestHeaders = await headers(); const session = await getAuth().api.getSession({ headers: requestHeaders }); if (!session) return null; const user = session.user as typeof session.user & { twoFactorEnabled?: boolean | null }; return { id: user.id, email: user.email, name: user.name, twoFactorEnabled: Boolean(user.twoFactorEnabled) }; } /** Fuer Seiten: leitet zur Anmeldung bzw. zur 2FA-Einrichtung um. */ export async function requireAdminPage(options: { allowWithout2fa?: boolean } = {}): Promise { const admin = await currentAdmin(); if (!admin) redirect("/admin/anmelden"); if (!admin.twoFactorEnabled && !options.allowWithout2fa) redirect("/admin/sicherheit"); return admin; } /** Fuer Server-Actions und Endpunkte: wirft statt umzuleiten. */ export async function requireAdminAction(): Promise { const admin = await currentAdmin(); if (!admin || !admin.twoFactorEnabled) throw new Error("Nicht berechtigt."); return admin; } /** Fuer Route-Handler: null statt Ausnahme. */ export async function adminOrNull(): Promise { const admin = await currentAdmin(); return admin?.twoFactorEnabled ? admin : null; } export function adminActor(admin: AdminUser): Actor { return { type: "ADMIN", id: admin.id }; }