import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import type { Benutzer } from "@/generated/prisma/client"; import type { Quelle } from "@/generated/prisma/enums"; import { prisma } from "@/lib/db"; import { SITZUNG_COOKIE, sitzungLesen } from "@/lib/sitzung"; import { darf, type Recht } from "@/server/auth/rechte"; import { keinRecht, nichtAngemeldet } from "@/server/fehler"; import { benutzerAusToken } from "@/server/services/tokens"; /** * Wer fragt an? Rolle und Aktiv-Status kommen **je Anfrage aus der Datenbank**, nie aus dem * Token. Jede Seite und jeder Route Handler prüft selbst – das Layout ist nur das zweite Netz, * weil Next es bei Navigation innerhalb desselben Layouts nicht neu rendert. */ export type Aufrufer = { benutzer: Benutzer; quelle: Quelle }; async function benutzerAusSitzung(jwt: string | undefined): Promise { const sitzung = await sitzungLesen(jwt); if (!sitzung) return null; const benutzer = await prisma.benutzer.findUnique({ where: { id: sitzung.benutzerId } }); return benutzer?.aktiv ? benutzer : null; } /** Für Server Components. */ export async function aktuellerBenutzer(): Promise { const speicher = await cookies(); return await benutzerAusSitzung(speicher.get(SITZUNG_COOKIE)?.value); } /** Für Seiten: leitet zur Anmeldung um oder zeigt „kein Recht“. */ export async function seiteSchuetzen(recht: Recht, pfad: string): Promise { const benutzer = await aktuellerBenutzer(); if (!benutzer) redirect(`/anmelden?ziel=${encodeURIComponent(pfad)}`); if (!darf(benutzer.rolle, recht)) redirect("/kein-recht"); return benutzer; } function cookieAusKopf(kopf: string | null, name: string): string | undefined { if (!kopf) return undefined; for (const teil of kopf.split(";")) { const [schluessel, ...wert] = teil.trim().split("="); if (schluessel === name) return decodeURIComponent(wert.join("=")); } return undefined; } /** * Für Route Handler: Bearer-Token (CLI) oder Sitzungscookie (Web) – hier an einer Stelle, damit * kein Handler zwei Wege kennen muss. Ein angegebenes, aber ungültiges Token gilt als nicht * angemeldet, auch wenn zusätzlich ein Cookie mitkommt. */ export async function anfrageAufrufer(anfrage: Request): Promise { const kopf = anfrage.headers.get("authorization"); if (kopf?.startsWith("Bearer ")) { const ausToken = await benutzerAusToken(kopf.slice("Bearer ".length).trim()); return ausToken ? { benutzer: ausToken, quelle: "CLI" } : null; } const benutzer = await benutzerAusSitzung(cookieAusKopf(anfrage.headers.get("cookie"), SITZUNG_COOKIE)); return benutzer ? { benutzer, quelle: "WEB" } : null; } export async function rechtErforderlich(anfrage: Request, recht: Recht): Promise { const aufrufer = await anfrageAufrufer(anfrage); if (!aufrufer) throw nichtAngemeldet(); if (!darf(aufrufer.benutzer.rolle, recht)) throw keinRecht(); return aufrufer; }