Anmeldung, Rollen und Benutzerverwaltung #21

Merged
florian.wilde merged 1 commits from feat/7-auth-rollen into main 2026-09-25 01:35:03 +00:00
Owner

Anmeldung über einen generischen OIDC-Anbieter (Ziel authentik) mit Discovery, PKCE, state und nonce, dazu ein lokaler Login mit argon2 als Notfallzugang. Rechte in einer Tabelle, Prüfung ausschließlich serverseitig, Rolle und Aktiv-Status pro Anfrage aus der Datenbank — eine entzogene Rolle greift sofort, ein abgeschaltetes Konto kommt nicht mit gültigem Cookie noch zwölf Stunden weiter.

Kein Auth.js: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus, sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose. Die API von openid-client 6 habe ich vorher gegen die Typdefinitionen im Paket geprüft, nicht aus dem Gedächtnis geschrieben.

Der Blocker aus dem Review: offene Weiterleitung

Die Prüfung des Rücksprungziels verglich Präfixe (/, nicht //, nicht /\\). /<TAB>/fremd.example besteht alle drei. Browser entfernen Tabulator, Zeilenumbruch und Wagenrücklauf beim Verarbeiten einer URL — übrig bleibt //fremd.example und damit ein fremder Host. Genau das Phishing, das die Prüfung abwehren sollte.

Sie löst das Ziel jetzt gegen eine Wegwerf-Basis auf und vergleicht die Herkunft; eine zweite Runde fängt zusätzlich /..//fremd.example ab, das beim Auflösen zu //fremd.example wird.

Weitere Befunde, alle behoben

  • Jede OIDC-Anmeldung hätte ohne konfigurierte OIDC_GROUP_* die Rolle still auf LESER gesetzt — bei einem einzigen Administrator ohne Rückweg
  • Verknüpfung über die E-Mail-Adresse ohne email_verified wäre der Weg gewesen, den lokalen Notfall-Administrator zu übernehmen
  • Der letzte aktive Administrator konnte sich selbst herabstufen
  • ?fehler=constructor ergab eine 500er-Seite (Zugriff über die Prototypenkette)
  • Das Fluss-Cookie bekam sein secure-Flag vom Schema des Anbieters statt vom eigenen
  • Fehlendes Recht endete im 500er statt in einer Erklärung
  • Ein Servermodul reiste über eine Client-Komponente ins Browser-Bündel

Drei eigene Fehler

  1. Der Platzhalter-Hash gegen den Zeitkanal war erfunden und hätte sofort geworfen statt Zeit zu kosten — die Zusage wäre falsch gewesen. Jetzt ein echter argon2-Hash; nachgemessen rund 1 ms Unterschied über je 60 Anfragen.
  2. nextUrl.origin lieferte localhost statt 127.0.0.1 und wäre hinter einem Reverse Proxy der interne Host. Alle Umleitungen gehen jetzt über APP_URL.
  3. overflow-x: hidden auf body hat die eigene Mobil-Prüfung blind gemacht — auf der Benutzerseite war bei 390 px ein Knopf abgeschnitten, ohne dass scrollWidth es zeigen konnte. Das hidden ist raus, die Liste sind Karten statt einer Tabelle.

Prüfung

lint, typecheck, build, pruefe:doku grün. 274 Tests (vorher 106): vollständige Rechtematrix, Angriffe auf das Rücksprungziel, Trennung von Sitzungs- und Kurzzeit-Token, und ein struktureller Nachweis, dass jede Server Action mit einer Rechteprüfung beginnt — gegengeprüft, indem eine Prüfung testweise entfernt wurde.

Screenshots unter docs/screenshots/m2/ (Anmelden, Übersicht, Benutzer, je Desktop und Handy).

Closes #7

Anmeldung über einen generischen OIDC-Anbieter (Ziel authentik) mit Discovery, PKCE, `state` und `nonce`, dazu ein lokaler Login mit argon2 als Notfallzugang. Rechte in **einer** Tabelle, Prüfung ausschließlich serverseitig, Rolle und Aktiv-Status **pro Anfrage aus der Datenbank** — eine entzogene Rolle greift sofort, ein abgeschaltetes Konto kommt nicht mit gültigem Cookie noch zwölf Stunden weiter. Kein Auth.js: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus, sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen `openid-client` und `jose`. Die API von openid-client 6 habe ich vorher gegen die Typdefinitionen im Paket geprüft, nicht aus dem Gedächtnis geschrieben. ## Der Blocker aus dem Review: offene Weiterleitung Die Prüfung des Rücksprungziels verglich Präfixe (`/`, nicht `//`, nicht `/\\`). **`/<TAB>/fremd.example` besteht alle drei.** Browser entfernen Tabulator, Zeilenumbruch und Wagenrücklauf beim Verarbeiten einer URL — übrig bleibt `//fremd.example` und damit ein fremder Host. Genau das Phishing, das die Prüfung abwehren sollte. Sie löst das Ziel jetzt gegen eine Wegwerf-Basis auf und vergleicht die Herkunft; eine zweite Runde fängt zusätzlich `/..//fremd.example` ab, das beim Auflösen zu `//fremd.example` wird. ## Weitere Befunde, alle behoben - Jede OIDC-Anmeldung hätte ohne konfigurierte `OIDC_GROUP_*` die Rolle still auf `LESER` gesetzt — bei einem einzigen Administrator ohne Rückweg - Verknüpfung über die E-Mail-Adresse ohne `email_verified` wäre der Weg gewesen, den lokalen Notfall-Administrator zu übernehmen - Der letzte aktive Administrator konnte sich selbst herabstufen - `?fehler=constructor` ergab eine 500er-Seite (Zugriff über die Prototypenkette) - Das Fluss-Cookie bekam sein `secure`-Flag vom Schema des **Anbieters** statt vom eigenen - Fehlendes Recht endete im 500er statt in einer Erklärung - Ein Servermodul reiste über eine Client-Komponente ins Browser-Bündel ## Drei eigene Fehler 1. **Der Platzhalter-Hash gegen den Zeitkanal war erfunden** und hätte sofort geworfen statt Zeit zu kosten — die Zusage wäre falsch gewesen. Jetzt ein echter argon2-Hash; nachgemessen rund 1 ms Unterschied über je 60 Anfragen. 2. **`nextUrl.origin` lieferte `localhost` statt `127.0.0.1`** und wäre hinter einem Reverse Proxy der interne Host. Alle Umleitungen gehen jetzt über `APP_URL`. 3. **`overflow-x: hidden` auf `body` hat die eigene Mobil-Prüfung blind gemacht** — auf der Benutzerseite war bei 390 px ein Knopf abgeschnitten, ohne dass `scrollWidth` es zeigen konnte. Das `hidden` ist raus, die Liste sind Karten statt einer Tabelle. ## Prüfung `lint`, `typecheck`, `build`, `pruefe:doku` grün. **274 Tests** (vorher 106): vollständige Rechtematrix, Angriffe auf das Rücksprungziel, Trennung von Sitzungs- und Kurzzeit-Token, und ein struktureller Nachweis, dass jede Server Action mit einer Rechteprüfung beginnt — gegengeprüft, indem eine Prüfung testweise entfernt wurde. Screenshots unter `docs/screenshots/m2/` (Anmelden, Übersicht, Benutzer, je Desktop und Handy). Closes #7
florian.wilde added 1 commit 2026-09-25 01:34:43 +00:00
feat(auth): Anmeldung, Rollen und Benutzerverwaltung
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
ffb063b178
Anmeldung über einen generischen OIDC-Anbieter (Ziel authentik) mit Discovery,
PKCE, state und nonce, dazu ein lokaler Login mit argon2 als Notfallzugang. Die
Rechte stehen in einer Tabelle, die Prüfung liegt ausschließlich serverseitig,
und Rolle wie Aktiv-Status werden bei jeder Anfrage aus der Datenbank gelesen —
eine entzogene Rolle greift damit sofort, ein abgeschaltetes Konto kommt nicht
mit gültigem Cookie noch zwölf Stunden weiter.

Kein Auth.js: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus und
sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose,
wie in ProxLedger. Die API von openid-client 6 wurde vorher gegen die
Typdefinitionen im Paket geprüft, nicht aus dem Gedächtnis geschrieben.

Ein unabhängiger Review hat einen Blocker und acht weitere Befunde gefunden, alle
behoben. Der wichtigste war eine offene Weiterleitung: die Prüfung des
Rücksprungziels verglich Präfixe, und "/<TAB>/fremd.example" besteht alle drei
Bedingungen. Browser entfernen Tabulator, Zeilenumbruch und Wagenrücklauf beim
Verarbeiten einer URL, übrig bleibt "//fremd.example" und damit ein fremder
Host — genau das Phishing, das die Prüfung abwehren sollte. Sie löst das Ziel
jetzt gegen eine Wegwerf-Basis auf und vergleicht die Herkunft.

Weitere Befunde: jede OIDC-Anmeldung hätte ohne konfigurierte Gruppen die Rolle
still auf LESER gesetzt, bei einem einzigen Administrator ohne Rückweg; die
Verknüpfung über die E-Mail-Adresse geschah ohne email_verified und wäre der Weg
gewesen, den lokalen Notfall-Administrator zu übernehmen; der letzte aktive
Administrator konnte sich selbst herabstufen.

Drei Fehler waren meine eigenen. Der Platzhalter-Hash gegen den Zeitkanal war
erfunden und hätte sofort geworfen statt Zeit zu kosten, die Zusage wäre also
falsch gewesen. nextUrl.origin lieferte im Test localhost statt 127.0.0.1 und
wäre hinter einem Reverse Proxy der interne Host; alle Umleitungen gehen jetzt
über APP_URL. Und overflow-x: hidden auf body hat die eigene Mobil-Prüfung blind
gemacht — auf der Benutzerseite war bei 390 px ein Knopf abgeschnitten, ohne dass
scrollWidth es zeigen konnte.

274 Tests, darunter die vollständige Rechtematrix, die Angriffe auf das
Rücksprungziel, die Trennung von Sitzungs- und Kurzzeit-Token und ein
struktureller Nachweis, dass jede Server Action mit einer Rechteprüfung beginnt.

Closes #7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
florian.wilde merged commit 40e95dbe5f into main 2026-09-25 01:35:03 +00:00
florian.wilde deleted branch feat/7-auth-rollen 2026-09-25 01:35:03 +00:00
Sign in to join this conversation.