Files
planer/src/server/berechtigung.ts
T
Florian WildeandClaude Opus 5 e3390c88f4 refactor: middleware in proxy umbenennen
Next 16 hat die Dateikonvention "middleware" abgekündigt und in "proxy"
umbenannt; der Bau warnte bei jedem Lauf. Umbenannt sind Datei und
Funktion, die Aufgabe bleibt dieselbe.

Die E2E-Tests laufen unverändert durch: die Umleitung samt gemerktem Ziel
funktioniert weiter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:29:52 +02:00

87 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'server-only'
import { headers } from 'next/headers'
import { redirect } from 'next/navigation'
import type { Session } from 'next-auth'
import type { Rolle } from '@/generated/prisma/enums'
import { rolleReichtAus } from '@/lib/rollen'
import { auth } from '@/server/auth'
import { PFAD_KOPFZEILE } from '@/proxy'
/**
* Rollenpruefung.
*
* Jede schreibende Aktion prueft serverseitig – in der Seite und noch einmal
* in der Server Action. Die Oberflaeche blendet Knoepfe nur zusaetzlich aus;
* verlassen wird sich darauf nie.
*/
export type Angemeldet = Session['user']
/** Fehler, den Server Actions dem Aufrufer zurueckgeben. */
export class NichtErlaubt extends Error {
constructor(nachricht = 'Dazu fehlt dir die Berechtigung.') {
super(nachricht)
this.name = 'NichtErlaubt'
}
}
/** Die angemeldete Person, oder null. */
export async function angemeldet(): Promise<Angemeldet | null> {
const sitzung = await auth()
return sitzung?.user?.id ? sitzung.user : null
}
/**
* Riegel fuer Seiten: ohne Anmeldung geht es zur Anmeldeseite zurueck,
* mit Ziel, damit es danach dort weitergeht, wo man hinwollte.
*/
export async function verlangeAnmeldung(zielPfad?: string): Promise<Angemeldet> {
const benutzer = await angemeldet()
if (!benutzer) {
const ziel = zielPfad ? `?weiter=${encodeURIComponent(zielPfad)}` : ''
redirect(`/anmelden${ziel}`)
}
return benutzer
}
/** Riegel fuer Seiten, die eine bestimmte Rolle verlangen. */
export async function verlangeRolle(
verlangt: Rolle,
zielPfad?: string,
): Promise<Angemeldet> {
const benutzer = await verlangeAnmeldung(zielPfad)
if (!rolleReichtAus(benutzer.rolle, verlangt)) {
redirect('/keine-berechtigung')
}
return benutzer
}
/**
* Riegel fuer Server Actions. Hier wird nicht umgeleitet, sondern geworfen –
* der Aufrufer wandelt das in eine Meldung fuer die Oberflaeche.
*/
export async function pruefeRolle(verlangt: Rolle): Promise<Angemeldet> {
const benutzer = await angemeldet()
if (!benutzer) throw new NichtErlaubt('Bitte zuerst anmelden.')
if (!rolleReichtAus(benutzer.rolle, verlangt)) throw new NichtErlaubt()
return benutzer
}
/**
* Riegel fuer Route Handler: gibt null zurueck, statt umzuleiten. Der
* Aufrufer antwortet dann mit 401.
*/
export async function verlangeAnmeldungFuerApi(): Promise<Angemeldet | null> {
return angemeldet()
}
/**
* Der angefragte Pfad, den der Proxy durchgereicht hat. Wird gebraucht,
* um nach der Anmeldung wieder dorthin zurueckzufuehren.
*/
export async function angefragterPfad(): Promise<string | undefined> {
const kopfzeilen = await headers()
const pfad = kopfzeilen.get(PFAD_KOPFZEILE)
return pfad && pfad.startsWith('/') ? pfad : undefined
}