Files
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

95 lines
3.1 KiB
TypeScript

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<CurrentUser | null> {
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<CurrentUser> {
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<CurrentUser> {
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<CurrentUser> {
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
}