feat(auth): Anmeldung, Rollen und Benutzerverwaltung (#21)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

OIDC mit Discovery, PKCE, state und nonce; lokaler Login mit argon2 als
Notfallzugang; Rechte in einer Tabelle, Prüfung ausschließlich serverseitig,
Rolle und Aktiv-Status pro Anfrage aus der Datenbank.

Ein unabhängiger Review fand eine offene Weiterleitung: die Präfix-Prüfung des
Rücksprungziels ließ /<TAB>/fremd.example durch, woraus der Browser
//fremd.example macht. Das Ziel wird jetzt aufgelöst und die Herkunft
verglichen. Dazu acht weitere Befunde, alle behoben.

274 Tests.

Closes #7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #21.
This commit is contained in:
2026-09-25 01:35:02 +00:00
co-authored by Claude Opus 5
parent 7a0f875f55
commit 40e95dbe5f
43 changed files with 3426 additions and 56 deletions
+25
View File
@@ -0,0 +1,25 @@
import { headers } from "next/headers";
import { Huelle } from "@/components/huelle";
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
/**
* Alles unter dieser Huelle verlangt eine Anmeldung.
*
* Der Proxy leitet zwar schon um, wenn gar kein Cookie da ist – geprueft wird
* aber hier, gegen die Datenbank. Der Proxy ist nur das billige Tor, damit nicht
* jede Anfrage ohne Cookie erst eine Datenbankabfrage kostet.
*/
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const kopfzeilen = await headers();
const pfad = kopfzeilen.get("x-pfad") ?? "/";
// Der Abfrageteil gehoert zum Ziel: wer mit abgelaufenem Cookie auf
// /laeufe?filter=offen kommt, soll auch dorthin zurueck und nicht auf /laeufe.
const ziel = pfad + (kopfzeilen.get("x-abfrage") ?? "");
const { benutzer } = await sitzungErforderlich(undefined, ziel);
return (
<Huelle aktiv={pfad} benutzer={{ name: benutzer.name, rolle: benutzer.rolle }}>
{children}
</Huelle>
);
}