import { notFound, redirect } from 'next/navigation' import { auth } from '@/auth' import { db } from '@/lib/db' import { loadSession } from '@/lib/auth/session' import { resolvePermissions } from '@/lib/auth/effective' import { ensureListening } from '@/lib/realtime/listener' import type { EffectivePermissions, Permission } from '@/lib/auth/permissions' export type CurrentUser = { id: string displayName: string username: string avatarHash: string | null sessionId: string permissions: EffectivePermissions } /** * Der angemeldete Nutzer für genau diesen Request, oder `null`. * * Drei Prüfungen, alle serverseitig: * 1. Das Cookie trägt eine gültige Signatur (Auth.js) * 2. Die Sitzungszeile existiert noch und ist nicht abgelaufen * 3. Der Nutzer trägt weiterhin eine Rolle, die zur Anmeldung berechtigt * * Schritt 3 ist doppelt gemoppelt — der Worker beendet Sitzungen beim * Rollenverlust bereits von sich aus — aber genau das ist der Sinn: fällt der * Worker aus, verliert trotzdem niemand unbemerkt seinen Ausschluss. */ export async function getCurrentUser(): Promise { const token = await auth() const sessionId = (token as { sessionId?: string } | null)?.sessionId if (!sessionId) return null // Der LISTEN-Client wird beim ersten Request des Prozesses aufgebaut. await ensureListening() const session = await loadSession(sessionId) if (!session) return null const user = await db.user.findUnique({ where: { id: session.userId }, select: { id: true, displayName: true, username: true, avatarHash: true, isActive: true }, }) if (!user || !user.isActive) return null const permissions = await resolvePermissions(session.userId) if (!permissions.hasMembership && !permissions.isAdmin) return null return { id: user.id, displayName: user.displayName, username: user.username, avatarHash: user.avatarHash, sessionId: session.id, permissions, } } /** Für Seiten, die eine Anmeldung voraussetzen. */ export async function requireUser(): Promise { const user = await getCurrentUser() if (!user) redirect('/anmelden') return user } /** * Für Seiten und Aktionen, die ein Recht voraussetzen. * * Fehlt das Recht, gibt es 404 statt 403. Existenz ist auch eine Information: * aus einem 403 liesse sich ablesen, dass es die Seite gibt. * Siehe docs/03-auth-und-rechte.md. */ export async function requirePermission(permission: Permission): Promise { const user = await requireUser() if (!user.permissions.isAdmin && !user.permissions.permissions.has(permission)) { notFound() } return user } /** * Für Server Actions: wirft, statt umzuleiten. Jede Server Action prüft die * Berechtigung erneut, egal was die Oberfläche vorher versteckt hat. * Siehe docs/07-dateien-und-sicherheit.md. */ export async function requirePermissionForAction(permission: Permission): Promise { const user = await getCurrentUser() if (!user) throw new Error('Nicht angemeldet') if (!user.permissions.isAdmin && !user.permissions.permissions.has(permission)) { throw new Error('Keine Berechtigung') } return user }