feat(auth): Anmeldung per OIDC mit Rollen aus Gruppen

Authorization Code mit PKCE, State und Nonce über openid-client 6.8.8; die Rolle kommt bei jeder Anmeldung aus dem Gruppen-Claim. Lokale Konten werden nie über den Namen übernommen, verknüpfen geht nur angemeldet. Getestet gegen einen Mock-IdP inklusive gefälschtem State, falscher Nonce und fremder Audience.

Closes #016

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 16:57:50 +02:00
co-authored by Claude Opus 5.5
parent 99357d59bd
commit 6d6b0ff121
15 changed files with 871 additions and 8 deletions
+12 -4
View File
@@ -1,5 +1,5 @@
# #016 Anmeldung per OIDC
**Status:** offen
**Status:** erledigt
**Meilenstein:** v0.5.0
**Labels:** feature, sicherheit
**Geschätzt:** 4 h **Tatsächlich:** –
@@ -8,13 +8,21 @@
Optionale OIDC-Anmeldung (z. B. authentik), Rollen über Gruppen-Claim.
## Aufgaben
- [ ] openid-client mit PKCE, Discovery, Gruppen → Rolle, Konto-Verknüpfung
- [x] openid-client 6.8.8 mit PKCE, State, Nonce, Discovery; Gruppen → Rolle bei jeder Anmeldung; Konto-Verknüpfung aus den Einstellungen
- [x] Anmeldeseite mit „Mit … anmelden“ und verständlichen Fehlermeldungen (`?sso=…`)
## Akzeptanzkriterien
- [ ] Tests gegen einen Mock-IdP (echter IdP später)
- [x] Tests gegen einen Mock-IdP (`tests/oidc-mock.ts`): Anlegen, Rollenwechsel, keine Gruppe, keine Übernahme per Name, Verknüpfen, gesperrt, gefälschter State, fehlendes Cookie, falsche Nonce, fremde Audience – echter IdP beim Feldtest (#020)
## Abhängigkeiten
#015
## Notizen / Entscheidungen
–
- **Keine automatische Übernahme lokaler Konten über den Namen** – sonst übernähme, wer beim IdP
„admin“ heißt, den lokalen Admin. Bei Namensgleichheit bekommt das SSO-Konto „name-2“.
- **IdP maßgeblich für die Rolle:** bei jeder Anmeldung aus den Gruppen; Wechsel kommt ins Audit-Log.
- Ablaufzustand (Verifier, State, Nonce, Ziel) in einem kurzlebigen signierten Cookie
(`pd_oidc`, 10 min, nur `/api/v1/auth/oidc`, SameSite=Lax) statt in der Datenbank.
- `redirect_uri` und die Rückruf-URL werden aus `APP_URL` gebildet – hinter dem Reverse-Proxy
trägt die Anfrage den internen Host.
- Client-Anmeldung `client_secret_post` (Standard von openid-client); authentik und Keycloak können beides.