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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
95 lines
3.1 KiB
TypeScript
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
|
|
}
|