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>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
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
|
||
}
|