# #007 M2 – Auth, Rollen, Benutzer **Status:** offen **Meilenstein:** v0.1.0 **Labels:** phase-1, backend, frontend, security **Geschätzt:** 8 h **Tatsächlich:** – ## Ziel Anmeldung per generischem OIDC (Ziel authentik) und als Notfallzugang lokal per E-Mail und Passwort, dazu die serverseitige Rechteprüfung, auf der alle weiteren Services aufsetzen. ## Aufgaben - [ ] `src/server/auth/oidc.ts`: `openid-client` 6.x mit Discovery, Umgebungsvariablen `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_NAME`; Rücksprung-URL aus `APP_URL` abgeleitet, damit es hinter dem Reverse Proxy stimmt - [ ] `src/lib/sitzung.ts`: `jose`-signiertes Sitzungscookie (HS256, `APP_SECRET` mit mindestens 32 Zeichen erzwungen, Hinweis auf `openssl rand -base64 48`), Nutzlast `{benutzerId, email, name, herkunft}`, Laufzeit 12 h - [ ] Lokaler Login mit `@node-rs/argon2`, nur aktiv bei `AUTH_LOCAL_ENABLED=true` - [ ] Rolle bei jedem OIDC-Login aus dem `groups`-Claim über `OIDC_GROUP_ADMIN`, `OIDC_GROUP_EDITOR`, `OIDC_GROUP_TECHNICIAN`, `OIDC_GROUP_VIEWER` aktualisieren; JIT-Provisioning unbekannter Benutzer beim ersten Login - [ ] `src/server/auth/rollen.ts`: `RECHTE` als eine Tabelle (`satisfies Record`) plus `darf()` und `ROLLEN_TEXT` - [ ] `src/server/auth/sitzungspflicht.ts`: `angemeldet()` (React-`cache`, ohne Weiterleitung) und `rechtErforderlich(recht)`; Rolle und Aktiv-Status pro Request **aus der Datenbank**, nicht aus dem Token; `sitzungenUngueltigAb` für Widerruf; reine, testbare `sitzungBewerten()` - [ ] `src/proxy.ts`: Allowlist öffentlicher Pfade, Weiterleitung auf `/anmelden?ziel=…` - [ ] Login-Seite im Design, OIDC-Knopf und lokales Formular, Rücksprung auf die ursprünglich angeforderte URL (nötig für `/t/[tagCode]`) - [ ] Admin → Benutzer: Liste mit Rolle und Aktiv-Status, lokale Benutzer anlegen wenn lokaler Login aktiv - [ ] Screenshots Login und Benutzerliste in `docs/screenshots/m2/` ## Akzeptanzkriterien - [ ] Anmeldung mit dem Seed-Admin gelingt, Abmelden beendet die Sitzung - [ ] Bei `AUTH_LOCAL_ENABLED=false` ist das lokale Formular nicht vorhanden und der Endpunkt lehnt ab - [ ] Tests fahren jede Rolle gegen jedes Recht: `LESER` darf nichts schreiben, `TECHNIKER` darf keine Verwaltung, `REDAKTEUR` darf Vorlagen, `ADMIN` alles - [ ] Eine Rollenänderung in der Datenbank greift ohne neue Anmeldung - [ ] Ein deaktivierter Benutzer wird mit bestehendem Cookie abgewiesen - [ ] Aufruf von `/t/LZ-7K3F9Q` ohne Anmeldung führt nach dem Login genau dorthin zurück - [ ] Passwörter stehen nur als argon2-Hash in der Datenbank, nie im Klartext und nie in einer API-Antwort