feat(anmeldung): lokale Benutzerkonten mit Pflicht-TOTP (#035)

Ohne authentik gab es nur den Notfall-Admin – ein geteiltes Konto ohne
Rollen und ohne erkennbare Akteure im Audit-Log. Admins legen jetzt
lokale Konten an; sshkv erzeugt ein Einmal-Passwort, das bei der ersten
Anmeldung ersetzt wird, danach ist TOTP Pflicht.

Rollen, Sperren (beendet Sitzungen sofort) und Zurücksetzen unter
Einstellungen → Benutzer, nur für Admins mit frischer Bestätigung.
SSHKV_LOKALE_KONTEN=0 schaltet lokale Konten ab; der Notfall-Admin
bleibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 22:36:25 +02:00
co-authored by Claude Opus 5.5
parent d9eec3d991
commit 4219701b29
41 changed files with 1295 additions and 109 deletions
+21 -19
View File
@@ -148,33 +148,35 @@ enum ProtokollErgebnis {
/// Ein Konto der Oberflaeche. Rolle und Aktiv-Status werden pro Anfrage hier gelesen.
model Benutzer {
id String @id @default(cuid())
id String @id @default(cuid())
/// `sub` des OIDC-Anbieters; leer beim Notfall-Admin.
oidcSub String? @unique
email String? @unique
oidcSub String? @unique
email String? @unique
/// Hat der OIDC-Anbieter die Adresse bestaetigt (`email_verified`)? Nur dann verknuepft der
/// Self-Service das Konto per E-Mail mit einer vorhandenen Person (#013).
emailBestaetigt Boolean @default(false)
/// Anmeldename des Notfall-Admins.
anmeldename String? @unique
name String
rolle Rolle @default(LESER)
aktiv Boolean @default(true)
istNotfall Boolean @default(false)
passwortHash String?
emailBestaetigt Boolean @default(false)
/// Anmeldename lokaler Konten: Notfall-Admin und von Admins angelegte Konten (#035).
anmeldename String? @unique
name String
rolle Rolle @default(LESER)
aktiv Boolean @default(true)
istNotfall Boolean @default(false)
passwortHash String?
/// Einmal-Passwort vom Admin: vor allem anderen muss ein eigenes gesetzt werden (#035).
passwortWechselNoetig Boolean @default(false)
/// TOTP-Geheimnis, verschluesselt mit dem aus SSHKV_APP_SECRET abgeleiteten Schluessel
/// (nicht mit dem Master-Key: der liegt nur im Worker, geprueft wird aber im Web).
totpGeheimnis Bytes?
totpEingerichtetAm DateTime?
totpGeheimnis Bytes?
totpEingerichtetAm DateTime?
/// Letzter angenommener TOTP-Zeitschritt – derselbe Code gilt nur einmal.
totpLetzterSchritt BigInt?
totpLetzterSchritt BigInt?
/// Sitzungen, die vor diesem Zeitpunkt ausgestellt wurden, sind ungueltig.
sitzungenGueltigAb DateTime @default(now())
sitzungenGueltigAb DateTime @default(now())
/// Zeitpunkt der letzten erneuten Bestaetigung (Re-Auth) – gilt 5 Minuten.
bestaetigtAm DateTime?
letzteAnmeldungAm DateTime?
angelegtAm DateTime @default(now())
geaendertAm DateTime @updatedAt
bestaetigtAm DateTime?
letzteAnmeldungAm DateTime?
angelegtAm DateTime @default(now())
geaendertAm DateTime @updatedAt
person Person?
angeforderteVorgaenge Vorgang[]