Commit Graph
69 Commits
Author SHA1 Message Date
sysops a70f439846 Merge branch 'feature/api-07-zentrale-webhook-registry-zustellung' into feature/qa-05-abnahme-compliance-pruefung-core 2026-08-29 17:05:24 +02:00
sysops 33a73bb64c Merge branch 'feature/api-06-wiederanlauf-nachsynchronisierung-nach-core-ausfall' into feature/qa-05-abnahme-compliance-pruefung-core
# Conflicts:
#	go.mod
#	internal/moduletrust/cache.go
2026-08-29 17:05:18 +02:00
sysops b347efed52 Merge branch 'feature/ops-03-metrics-aggregation-ueber-module-hinweg' into feature/qa-05-abnahme-compliance-pruefung-core 2026-08-29 17:05:03 +02:00
sysops 8d8266a2fd Merge branch 'feature/aud-02-unveraenderliches-protokoll-append-only' into feature/qa-05-abnahme-compliance-pruefung-core 2026-08-29 17:05:03 +02:00
sysops 37edf98618 Merge branch 'feature/ten-08-tenant-loeschung-unter-retention-vorbehalt-gobd' into feature/qa-05-abnahme-compliance-pruefung-core
# Conflicts:
#	internal/tenant/registry.go
2026-08-29 17:04:55 +02:00
sysops c7ff26d857 Merge branch 'feature/qa-03-pruefgate-rechte-policy' into feature/qa-05-abnahme-compliance-pruefung-core
# Conflicts:
#	internal/flag/flag.go
2026-08-29 17:03:56 +02:00
sysops 03cd1e01c5 Merge branch 'feature/ops-02-zentrale-statusseite' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	scripts/reset-test-env.sh
2026-08-29 10:02:30 +02:00
sysops 98944b1412 Merge branch 'feature/cfg-04-benachrichtigungs-einstellungen-oberflaeche' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 10:01:48 +02:00
sysops 93a49dd72c Merge branch 'feature/aud-04-audit-log-ansicht' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	go.mod
#	go.sum
#	scripts/reset-test-env.sh
2026-08-29 10:00:28 +02:00
sysops 8bd5bb5a52 Merge branch 'feature/lic-04-lizenz-modul-verwaltungsoberflaeche' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
#	go.mod
#	go.sum
#	internal/flag/flag.go
#	scripts/reset-test-env.sh
2026-08-29 09:59:50 +02:00
sysops 8a779531e1 Merge branch 'feature/rbac-05-rechte-administrationsoberflaeche' into feature/qa-09-barrierefreiheits-audit
# Conflicts:
#	DEVLOG.md
2026-08-29 09:58:58 +02:00
sysops ca547bcd54 Merge branch 'feature/api-10-master-key-verwaltung-tenant-schluesselhierarchie-kms-anbindung' into feature/qa-04-sicherheits-penetrationspruefung-core
# Conflicts:
#	go.mod
#	go.sum
#	scripts/reset-test-env.sh
2026-08-29 09:43:34 +02:00
sysops cbbf5a348a Merge branch 'feature/api-03-zentrales-rate-limiting-api-gateway-schicht' into feature/qa-04-sicherheits-penetrationspruefung-core
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 09:43:05 +02:00
sysops 550fc70d50 Merge branch 'feature/rbac-02-policy-enforcement-schicht-zentral' into feature/qa-04-sicherheits-penetrationspruefung-core
# Conflicts:
#	scripts/reset-test-env.sh
2026-08-29 09:42:39 +02:00
sysops 4f84ee23dc Merge branch 'feature/iam-14-passwort-richtlinien' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 09:22:40 +02:00
sysops 559b650af7 Merge branch 'feature/iam-13-oidc-provider-fuer-drittanwendungen' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	DEVLOG.md
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 09:22:10 +02:00
sysops fb0ff9e779 Merge branch 'feature/iam-12-aktive-sitzungen-verwaltung' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:12:06 +02:00
sysops 912857d35b Merge branch 'feature/iam-11-saml-2-0-anbindung' into feature/qa-02-pruefgate-identitaet-mandanten 2026-08-29 00:11:52 +02:00
sysops 2f832292b3 Merge branch 'feature/iam-10-passwortlose-anmeldung-via-webauthn-passkey' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:11:46 +02:00
sysops c6933f0e2f Merge branch 'feature/iam-09-api-token-service-accounts' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:11:35 +02:00
sysops 6c8582c1c2 Merge branch 'feature/iam-07-account-lockout-login-rate-limiting' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:11:09 +02:00
sysops 95d1a6810a Merge branch 'feature/iam-06-sso-anmeldung-ueber-oidc' into feature/qa-02-pruefgate-identitaet-mandanten 2026-08-29 00:10:39 +02:00
sysops ca07ae4d68 Merge branch 'feature/iam-05-ldap-active-directory-anbindung' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:10:39 +02:00
sysops 0a2e71aec9 Merge branch 'feature/iam-04-zwei-faktor-authentifizierung-totp' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	DEVLOG.md
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:10:25 +02:00
sysops ba23d4d90d Merge branch 'feature/rbac-04-modul-scoped-berechtigungen' into feature/qa-03-pruefgate-rechte-policy 2026-08-29 00:10:21 +02:00
sysops 24c8331d45 Merge branch 'feature/iam-03-passwort-reset-einladungs-flow' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
#	scripts/run-checks.sh
2026-08-29 00:10:04 +02:00
sysops 13874683e8 Merge branch 'feature/iam-02-login-session-jwt-grundgeruest' into feature/qa-02-pruefgate-identitaet-mandanten 2026-08-29 00:09:35 +02:00
sysops 96a54d93a4 Merge branch 'feature/ten-04-tenant-lifecycle-suspendieren-reaktivieren-loeschen' into feature/qa-02-pruefgate-identitaet-mandanten
# Conflicts:
#	scripts/reset-test-env.sh
2026-08-29 00:08:30 +02:00
sysops 22b3349b8e Merge branch 'feature/iam-02-login-session-jwt-grundgeruest' into feature/cfg-04-benachrichtigungs-einstellungen-oberflaeche
# Conflicts:
#	go.mod
#	go.sum
2026-08-28 23:48:13 +02:00
sysops 2df3f93373 CFG-04: backend teil 1 — internal/notifyprefs (praeferenz-store + enqueueifallowed-filter vor dispatcher) 2026-08-28 23:47:46 +02:00
sysops 9de9005ced Merge branch 'feature/iam-02-login-session-jwt-grundgeruest' into feature/rbac-05-rechte-administrationsoberflaeche 2026-08-28 23:38:57 +02:00
sysops 1b6a1591c7 Merge branch 'feature/rbac-03-gruppen-abteilungen' into feature/rbac-05-rechte-administrationsoberflaeche 2026-08-28 23:38:52 +02:00
sysops 42466c7b40 IAM-13: oidc-provider-fuer-drittanwendungen (client-registrierung, authorization-code-flow, jwks ueber API-05-schluessel) 2026-08-28 23:25:45 +02:00
sysops c344dea218 TEN-08: tenant-loeschung-unter-retention-vorbehalt-gobd (RetentionChecker-Schnittstelle gegen Archive RET-03/CMP-06, ProcessDueDeletions haelt gesperrte Tenants zurueck) 2026-08-28 22:51:05 +02:00
sysops bc2126f3b1 API-10: master-key-verwaltung-tenant-schluesselhierarchie-kms-anbindung (envelope encryption, isolierte tenant-keks, rotation) 2026-08-28 10:03:27 +02:00
sysops 0fd9856b10 API-06: wiederanlauf-nachsynchronisierung-nach-core-ausfall (postgres-puffer, service-credential, sofort-invalidate) 2026-08-28 09:22:08 +02:00
sysops 627961b972 API-07: zentrale-webhook-registry-zustellung (postgres-jobqueue, hmac-signatur, backoff) 2026-08-28 09:03:06 +02:00
sysops 814a7fda0a OPS-03: metrics-aggregation-ueber-module-hinweg (prometheus-textformat, dynamische quellen) 2026-08-28 08:39:54 +02:00
sysops dff760e1ba OPS-02: zentrale-statusseite (backend: poller + aggregierte uebersicht + verlauf) 2026-08-28 08:19:40 +02:00
sysops a67adcefa1 API-03: zentrales-rate-limiting-api-gateway-schicht (postgres-basierter shared state) 2026-08-28 08:14:20 +02:00
sysopsandClaude Sonnet 5 034865f5a0 CFG-03: benachrichtigungs-kanaele-e-mail-in-app
internal/channels: konkrete Zustellkanaele fuer CFG-02s Dispatcher.
TemplateStore.Resolve loest Vorlagen pro Tenant auf und faellt auf
GlobalTemplateScope zurueck, wenn ein Tenant keine eigene gesetzt hat
(Akzeptanzkriterium 3). Render nutzt text/template mit
Option("missingkey=error") — ein fehlender Platzhalter bricht das Rendering
MIT FEHLER ab, statt eine unvollstaendige Nachricht zu erzeugen
(Akzeptanzkriterium 1).

EmailSender implementiert notify.Sender: rendert ZUERST die Vorlage, bevor
ueberhaupt eine SMTP-Verbindung aufgebaut wird — schlaegt das Rendering
fehl, wird nie ein Netzwerkzugriff versucht. Ein anschliessend fehl-
schlagender SMTP-Versand liefert einen Fehler, den CFG-02s bereits
getestete Wiederholungslogik verarbeitet (kein zweiter Retry-Mechanismus
hier). InAppSender persistiert In-App-Nachrichten ueber InAppStore
(Akzeptanzkriterium 2, ueber API abrufbar/als gelesen markierbar). Router
waehlt den Kanal anhand Notification.Channel — ein neuer Kanal wird per
Register() ergaenzt, ohne Dispatcher oder Router umzubauen.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Vorlagenrendering mit fehlenden Platzhaltern bricht kontrolliert ab —
   TestRender_MissingPlaceholderAborts und
   TestEmailSender_AbortsBeforeSMTPWhenTemplateMissing (Fehler kommt von der
   Vorlagenaufloesung, kein SMTP-Verbindungsversuch). PASS.
2. In-App-Benachrichtigung nach Markierung als gelesen korrekt gefuehrt —
   TestInAppStore_MarkReadIsReflectedCorrectly. PASS.
3. E-Mail-Versand bei nicht erreichbarem SMTP-Server loest dokumentiertes
   Retry-Verhalten ueber CFG-02 aus —
   TestEmailSender_TriggersDispatcherRetryOnUnreachableSMTP: echter
   EmailSender gegen unerreichbaren Host, ueber notify.Dispatcher
   eingereiht, nach ausgeschoepften Wiederholungen status=failed mit
   korrekter Versuchszahl. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:23:45 +02:00
sysopsandClaude Sonnet 5 0ce29acf3e IAM-11: saml-2-0-anbindung
internal/saml: RSA-SHA256-Signaturpruefung ueber die deterministisch
(re-)marshalte Assertion — deckt dieselbe Sicherheitseigenschaft ab wie
XML-DSig (nur eine gueltig signierte Assertion eines vertrauten IdP wird
akzeptiert), implementiert aber NICHT die vollstaendige W3C-Exclusive-C14N
mit allen Randfaellen echter Drittprodukt-IdPs (ADFS/Okta/Azure AD) — das
Ticket erlaubt ausdruecklich einen "simulierten IdP" fuer die Pruefungen,
Simulator (Sign/BuildResponse) und Verifier nutzen folgerichtig dieselbe
deterministische Kodierung.

CompleteSAMLLogin mappt Rollen aus SAML-Attributen ueber DIESELBE Erlaubnis-
Matrix wie IAM-05/LDAP und IAM-06/OIDC (ldapsync.RoleMappingStore, kein
dritter paralleler Mapping-Mechanismus — Akzeptanzkriterium 3) und stellt
ein IAM-02-Sitzungs-Token aus. saml_config ist wie ldap_config/oidc-Kontext
eine Singleton-Zeile je Tenant-Datenbank (Modell C) — SAML und OIDC koennen
dadurch strukturell fuer verschiedene Tenants nebeneinander konfiguriert
sein, ohne dass sich beide je begegnen (Akzeptanzkriterium 2).

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. End-to-End-Test gegen simulierten SAML-IdP erfolgreich —
   TestCompleteSAMLLogin_EndToEnd: signierte Assertion vom simulierten IdP,
   vollstaendiger Login liefert gueltiges Sitzungs-Token. PASS.
2. Zwei Tenants mit unterschiedlichem Anmeldeweg (SAML vs. OIDC) parallel
   funktionsfaehig — TestSAMLAndOIDC_WorkInParallelForDifferentTenants: zwei
   physisch getrennte Tenant-Datenbanken, eine mit SAML-, eine mit
   OIDC-Login, beide liefern unabhaengig gueltige Tokens. PASS.
3. Rollenzuordnung aus SAML-Attributen korrekt —
   TestCompleteSAMLLogin_EndToEnd (Positivfall: gemappte Rolle greift) und
   TestCompleteSAMLLogin_UnmappedRoleGrantsNothing (Negativfall: Rollen-
   Attribute wie "tenant_admin"/"superadmin", die nie gemappt wurden,
   vergeben keine Rolle — keine Privilege-Escalation). PASS.

Zusaetzlich: TestVerify_RejectsTamperedAssertion, TestVerify_RejectsWrongIdPKey,
TestVerify_RejectsExpiredAssertion, TestVerify_RejectsWrongIssuer belegen die
Kern-Sicherheitseigenschaften der Signaturpruefung. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:12:03 +02:00
sysopsandClaude Sonnet 5 ba71bb6350 IAM-10: passwortlose-anmeldung-via-webauthn-passkey
internal/webauthn: vereinfachte, aber kryptographisch echte Challenge-
Response-Zeremonie (Ed25519) statt des vollstaendigen W3C-WebAuthn-
Drahtformats (CBOR-attestationObject/COSE), das ohne Browser-Umgebung hier
nicht erzeugt/geprueft werden kann (siehe Paket-Dokumentation, analoge
Einschraenkung wie IAM-05/IAM-06). Deckt dieselben Sicherheitseigenschaften
ab: Public-Key-Challenge-Response, Replay-Schutz durch Einmal-Challenge
(WHERE used_at IS NULL, Muster aus IAM-03/06/09), mehrere Authenticatoren
pro Benutzer (Akzeptanzkriterium 2, jede Registrierung eine eigene Zeile).

CompleteLogin komponiert auth.TokenIssuer (IAM-02) fuer die Sitzungs-Token-
Ausstellung nach erfolgreicher Signaturpruefung, ohne LoginService/IAM-02
selbst zu veraendern — Passwort+TOTP bleiben vollstaendig unberuehrt und
funktionsfaehig (Akzeptanzkriterium 3, kein Zwang auf WebAuthn).

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Registrierung und Anmeldung mit simuliertem Authenticator erfolgreich —
   TestRegisterAndLogin_WithoutPassword: kompletter Ablauf ganz ohne
   Passworteingabe, gueltiges Sitzungs-Token am Ende. PASS.
2. Zweiter Authenticator registriert, beide funktionsfaehig —
   TestMultipleCredentials_BothWork: zwei unabhaengige Schluesselpaare,
   beide melden sich erfolgreich an. PASS.
3. Login mit Passwort+TOTP funktioniert weiterhin unveraendert —
   TestPasswordLoginStillWorks_AfterWebAuthnRegistration: IAM-02-Login nach
   WebAuthn-Registrierung unveraendert erfolgreich. PASS.

Zusaetzlich: TestFinishRegistration_RejectsWrongSignature und
TestChallenge_CannotBeReplayed belegen die Kernsicherheitseigenschaften der
Zeremonie. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:05:42 +02:00
sysopsandClaude Sonnet 5 e4856dfc9d IAM-06: sso-anmeldung-ueber-oidc
internal/oidc: JWKS-Parser (RSA-Schluessel, RFC 7517) + Verifier prueft
ID-Tokens gegen den geparsten Schluesselsatz (Signatur, Ablauf ueber die
jwt-Bibliothek, Aussteller) — Akzeptanzkriterium 3. VerifyWithNonce prueft
zusaetzlich, dass der nonce-Claim exakt dem beim Redirect ausgestellten
Nonce entspricht (Replay-Schutz).

StateStore ist der CSRF-/Replay-Schutz (Akzeptanzkriterium/Pruefung 3):
Generate stellt state+nonce aus, Consume loest den state ATOMAR und EINMALIG
ein (WHERE used_at IS NULL, analog IAM-03/IAM-09-Muster) — ein abgefangener
und wiederverwendeter Redirect-Callback schlaegt fehl.

CompleteOIDCLogin mappt Rollen aus OIDC-Rollen-Claims ueber DIESELBE
Erlaubnis-Matrix wie IAM-05/LDAP (ldapsync.RoleMappingStore.HighestRoleFor,
keine zweite parallele Implementierung — Akzeptanzkriterium 2) und stellt
bei Erfolg ein normales IAM-02-Sitzungs-Token aus. Lokaler Login (IAM-02
LoginService) bleibt vollstaendig unangetastet und damit als Fallback nutzbar.

WICHTIGER HINWEIS: kein registrierter externer OIDC-Provider (Google/Okta/
Azure AD) in dieser Umgebung verfuegbar fuer einen echten Authorization-
Code-Redirect (analog IAM-05/AUD-05). ANDERS als dort ist die eigentliche
Token-Validierung aber rein kryptographisch und ohne Netzwerkabhaengigkeit
zur Testzeit vollstaendig echt geprueft: Tests erzeugen ein eigenes
RSA-Schluesselpaar, signieren ID-Tokens selbst und verifizieren sie exakt
wie bei einem echten Provider. Nur der Live-Redirect zu einem realen
externen IdP bleibt ungeprueft.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Rollen-Erlaubnis-Matrix fuer OIDC-Claims automatisiert getestet (keine
   Privilege-Escalation) — TestCompleteOIDCLogin_UnmappedRoleGrantsNothing:
   Claims mit "tenant_admin"/"superadmin" als Rollen-Strings, die NIE gemappt
   wurden, vergeben keine Rolle. PASS.
2. Token-Signatur- und Ablaufpruefung gegen JWKS automatisiert getestet —
   TestVerify_RejectsExpiredToken, TestVerify_RejectsWrongSigningKey,
   TestVerify_RejectsWrongIssuer, TestParseJWKS_RoundTrip. PASS.
3. State/Nonce-Handling gegen CSRF und Replay geprueft —
   TestStateStore_ConsumeIsSingleUse (State-Replay abgewiesen),
   TestVerifyWithNonce_RejectsMismatch (Nonce-Mismatch abgewiesen). PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:53:42 +02:00
sysopsandClaude Sonnet 5 1a82da211d IAM-05: ldap-active-directory-anbindung
internal/ldapsync: Client ist eine Schnittstelle (Search() liefert Entry-
Liste) — in dieser Umgebung steht kein echter LDAP/AD-Server zur Verfuegung
(analog AUD-05/Archive), daher ist die Sync-/Rollenzuordnungs-Logik
vollstaendig gegen einen Fake getestet, eine echte Verbindungspruefung gegen
LDAP/AD steht noch aus.

RoleMappingStore (Akzeptanzkriterium 3): explizite Erlaubnis-Matrix
LDAP-Gruppe -> Rolle. HighestRoleFor vergibt NUR fuer explizit gemappte
Gruppen eine Rolle — eine unbekannte Gruppe, auch wenn ihr Name zufaellig
wie eine interne Rolle aussieht ("tenant_admin", "superadmin"), traegt
strukturell nichts bei (kein Code-Pfad, der eine ungemappte Gruppe je einer
Rolle zuordnet) — behebt die aus archivmail bekannte Privilege-Escalation-
Fehlerklasse von Grund auf statt nachtraeglich zu haerten.

Syncer.Run ruft Search() als ALLERERSTES auf; schlaegt es fehl, wird ohne
jede Aenderung an bestehenden Konten abgebrochen (Akzeptanzkriterium 2).
Pro Eintrag isolierte Fehler landen in SyncResult.Failed, ohne andere
Eintraege zu beeintraechtigen. Deaktivierung in LDAP wird als
users.Deactivate uebernommen (Akzeptanzkriterium 3). ldap_config speichert
bewusst nur den NAMEN einer Umgebungsvariable fuer das Bind-Passwort, nie
das Passwort selbst.

internal/rbac (RBAC-01) wurde 1:1 aus dem rbac-01-Branch uebernommen (git
show aus derselben Repo-Historie) — IAM-05 haengt an RBAC-01 fuer die
Rollenzuweisung, beide Boards leben aber auf getrennten, noch nicht
gemergten Feature-Branches ohne gemeinsame Historie.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Rollen-Erlaubnis-Matrix mit Positiv- und Negativfaellen automatisiert
   getestet (keine Privilege-Escalation) — TestRoleMapping_PositiveAndNegativeCases:
   gemappte Gruppe liefert Rolle, unbekannte Gruppe und rollen-aehnlich
   benannte, aber nie gemappte Gruppen liefern keine. PASS.
2. Synchronisationslauf mit fehlerhafter/nicht erreichbarer LDAP-Quelle
   bricht kontrolliert ab, ohne bestehende Konten zu beschaedigen —
   TestSyncer_AbortsCleanlyOnSourceError: bestehendes Konto bleibt nach
   fehlgeschlagenem Lauf unveraendert aktiv. PASS.
3. Deaktivierung eines Benutzers in LDAP wird bei naechster Synchronisation
   korrekt uebernommen — TestSyncer_AppliesDeactivationOnNextRun: erster
   Lauf legt aktiven Benutzer an, zweiter Lauf mit Disabled=true setzt ihn
   auf inaktiv. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:39:10 +02:00
sysopsandClaude Sonnet 5 f224b5c9be IAM-04: zwei-faktor-authentifizierung-totp
internal/totp/totp.go: RFC-6238-TOTP (HOTP RFC 4226) selbst implementiert
mit stdlib crypto/hmac+sha1 — kein externes Modul. DefaultSkewSteps=1
dokumentiert die Zeitversatz-Toleranz (+/-30s um die Serverzeit,
Akzeptanzkriterium/Pruefung 3). ProvisioningURI liefert die otpauth://-URI
fuer den QR-Code (Akzeptanzkriterium 1) — das Rendering selbst ist
Frontend-Sache (IAM-08).

internal/totp/store.go: BeginSetup speichert ein neues Secret als NICHT
bestaetigt; ConfirmSetup aktiviert 2FA erst nach einmaliger erfolgreicher
Code-Eingabe (Akzeptanzkriterium 1) und erzeugt 10 Wiederherstellungscodes
(nur Hash gespeichert, Klartext einmalig zurueckgegeben). VerifyLoginCode
akzeptiert TOTP-Code ODER Wiederherstellungscode; consumeRecoveryCode
markiert einen Code atomar als verwendet (WHERE used_at IS NULL) — kein
doppeltes Einloesen moeglich (Akzeptanzkriterium 3).

internal/totp/login.go: LoginWithTOTP komponiert IAM-02s LoginService, ohne
ihn zu veraendern — ist 2FA fuer den Benutzer aktiv, wird ein fehlender/
falscher Code zuverlaessig abgewiesen, selbst bei korrektem Passwort
(Akzeptanzkriterium 2).

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Login-Versuch ohne zweiten Faktor bei aktivem 2FA zuverlaessig abgewiesen —
   TestLoginWithTOTP_RequiresSecondFactorWhenEnabled: korrektes Passwort ohne
   Code -> ErrSecondFactorRequired, mit gueltigem Code -> Token. PASS.
2. Wiederherstellungscode nach Nutzung als verbraucht getestet —
   TestVerifyLoginCode_RecoveryCodeIsSingleUse: erste Nutzung erfolgreich,
   zweite abgelehnt. PASS.
3. Zeitversatz-Toleranz dokumentiert und getestet —
   TestValidate_ClockSkewTolerance: Code aus 25s Vergangenheit gueltig
   (innerhalb dokumentierter Toleranz), Code aus 5min Vergangenheit
   ungueltig. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:34:34 +02:00
sysopsandClaude Sonnet 5 5f02e08bc4 IAM-12: aktive-sitzungen-verwaltung
internal/session: Selbstbedienungs-Sitzungsuebersicht mit widerrufbarem,
SERVERSEITIGEM Zustand — bewusst ein separater Mechanismus neben IAM-02s
zustandslosem JWT (das fuer schnelle Modul-zu-Modul-Verifikation ohne
Core-Rueckfrage gewaehlt wurde, siehe API-05). Sofortige Widerrufbarkeit ist
fuer dieses Selbstbedienungs-Sicherheitsfeature wichtiger als
Zustandslosigkeit — kein Konflikt mit der API-05-Entscheidung, da es sich um
verschiedene Anwendungsfaelle handelt.

Store.Create gibt den Klartext-Sitzungs-Token nur einmal zurueck, gespeichert
wird ausschliesslich der SHA-256-Hash. Validate prueft direkt gegen die
Datenbank (kein Cache) und aktualisiert last_seen_at bei jedem Zugriff
(Akzeptanzkriterium 1). Revoke/RevokeAllExcept setzen revoked_at — ein
widerrufenes Token ist ab dem naechsten Validate-Aufruf sofort ungueltig
(Akzeptanzkriterium 2), RevokeAllExcept beendet gezielt alle Sitzungen ausser
der aktuellen (Akzeptanzkriterium 3).

LoginAndCreateSession verwendet auth.VerifyPassword (IAM-02) fuer den
timing-safen Credential-Check — kein zweiter Passwort-Pruefmechanismus,
liefert aber ein Sitzungs-Token statt eines JWT zurueck.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Zwei Sitzungen desselben Nutzers angelegt, beide in der Uebersicht
   sichtbar — TestListForUser_ShowsAllActiveSessions. PASS.
2. Widerruf einer Sitzung macht das Token sofort ungueltig —
   TestRevoke_InvalidatesTokenImmediately. PASS.
3. "Alle anderen beenden" funktioniert korrekt, aktuelle bleibt aktiv —
   TestRevokeAllExcept_KeepsCurrentSessionActive: zwei fremde Sitzungen
   widerrufen, aktuelle bleibt gueltig und einzig uebrige in der Liste. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:29:58 +02:00
sysopsandClaude Sonnet 5 3c4e9d45b8 IAM-14: passwort-richtlinien
internal/pwpolicy: Validate ist eine schmale Regelschicht VOR dem
bcrypt-Hashing aus IAM-02, kein eigenes Policy-Framework. Prueft
Mindestlaenge, Zeichenklassen (Gross-/Kleinbuchstaben, Ziffern,
Sonderzeichen je nach Policy) und eine eingebettete Sperrliste haeufig
verwendeter Passwoerter (case-insensitive) — Akzeptanzkriterium 1 + 2.
Store haelt die Richtlinie als eine Zeile je Tenant-Datenbank (Singleton,
Modell C), DefaultPolicy() greift, solange kein Tenant eine eigene gesetzt hat.

LoginAndCheckPolicy komponiert IAM-02s LoginService, OHNE ihn zu veraendern:
der Login selbst schlaegt bei einem alten, nicht mehr konformen Passwort
NICHT fehl (Akzeptanzkriterium 3 — kein rueckwirkendes Aussperren), die
Funktion liefert zusaetzlich mustChangePassword=true. Die Pruefung ist nur
im Login-Moment moeglich, da dort kurzzeitig das Klartext-Passwort vorliegt
— der gespeicherte bcrypt-Hash laesst sich nicht rueckwirkend gegen eine
neue Richtlinie pruefen.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Zu kurzes/zu einfaches Passwort bei Registrierung/Aenderung abgelehnt —
   TestValidate_RejectsTooShortOrSimple. PASS.
2. Passwort aus Sperrliste abgelehnt — TestValidate_RejectsBlocklistedPassword
   (inkl. Gross-/Kleinschreibung). PASS.
3. Bestehender Nutzer mit altem, nicht-konformem Passwort kann sich noch
   einloggen, wird aber zur Aenderung aufgefordert —
   TestLoginAndCheckPolicy_FlagsNonConformantExistingPassword: Login mit
   schwachem Altpasswort gelingt, mustChangePassword=true; mit konformem
   Passwort mustChangePassword=false. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:26:23 +02:00
sysopsandClaude Sonnet 5 95275407b1 IAM-09: api-token-service-accounts
internal/serviceaccount: Service-Accounts als eigenstaendige Identitaetsklasse
(eigene Tabelle service_accounts, getrennt von internal/user — Zitadel-
Vorbild). Store.IssueToken gibt den Klartext-Token NUR einmal an den
Aufrufer zurueck, gespeichert wird ausschliesslich der SHA-256-Hash
(Akzeptanzkriterium 1). Scopes und optionale Ablaufzeit sind Teil des
Tokens selbst (Akzeptanzkriterium 2).

Store.Verify prueft Widerruf/Ablauf/Scope bei JEDEM Aufruf direkt gegen die
Datenbank — kein Cache dazwischen, ein widerrufenes Token wird ab dem
naechsten Request sofort abgewiesen (Akzeptanzkriterium 3).

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Token ausschliesslich gehasht gespeichert (Stichprobe in der Datenbank) —
   TestIssueToken_StoresOnlyHash: token_hash-Spalte direkt gelesen, enthaelt
   nachweislich nicht den Klartext, 32-Byte-SHA-256-Laenge bestaetigt. PASS.
2. Widerrufenes Token wird beim naechsten Request zuverlaessig abgewiesen —
   TestRevoke_TakesEffectImmediately: Verify vor Widerruf erfolgreich, sofort
   danach ErrTokenInvalid. PASS.
3. Scope-Verletzung wird korrekt abgewiesen —
   TestVerify_RejectsInsufficientScope: Token mit scope=read wird fuer
   scope=write abgewiesen (ErrScopeInsufficient), fuer scope=read akzeptiert. PASS.

Zusaetzlich: TestVerify_RejectsExpiredToken belegt die optionale zeitliche
Befristung aus Akzeptanzkriterium 2. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:19:39 +02:00
sysopsandClaude Sonnet 5 ac070c160a IAM-07: account-lockout-login-rate-limiting
internal/lockout: Store.RecordFailure erhoeht failed_count ATOMAR ueber ein
einziges Postgres-UPSERT und setzt locked_until, sobald die konfigurierte
Schwelle erreicht ist — Zustand lebt ausschliesslich in Postgres, mehrere
Core-Instanzen teilen sich denselben Zaehler (Akzeptanzkriterium 2, bekannter
Fehler aus archivdms internal/auth/ratelimit.go vermieden: kein
In-Process-Zaehler). IsLocked vergleicht nur locked_until gegen die aktuelle
Zeit — eine abgelaufene Sperre gilt automatisch als aufgehoben, ohne
explizite Entsperr-Aktion (Akzeptanzkriterium 3). Unlock erlaubt zusaetzlich
sofortige Entsperrung durch Administratoreingriff.

GuardedLogin komponiert IAM-02s LoginService mit dem Lockout-Zustand, ohne
LoginService selbst zu veraendern: prueft die Sperre vor jedem Versuch,
vermerkt Erfolg/Fehlschlag danach.

Bugfix waehrend Tests: die Sperrzeit wurde als Ganzzahl-Sekunden in die
Postgres-INTERVAL-Berechnung eingesetzt (int(duration.Seconds())), wodurch
Sperrzeiten unter 1 Sekunde (z.B. in Tests) auf 0 abgerundet wurden und die
Sperre sofort wieder als abgelaufen galt — auf Fliesskomma-Sekunden
umgestellt.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Zwei parallel laufende Dienstinstanzen teilen sich denselben Zaehler —
   TestRecordFailure_SharedAcrossInstances: zwei unabhaengige pgxpool.Pool-
   Verbindungen, Fehlversuche abwechselnd ueber beide, gemeinsame Schwelle
   wird erreicht. PASS.
2. Brute-Force-Sperre greift nach definierten Fehlversuchen zuverlaessig —
   TestRecordFailure_LocksAfterThreshold. PASS.
3. Zeitversatz zwischen Sperre und Entsperrung automatisiert getestet —
   TestIsLocked_AutoUnlocksAfterExpiry: gesperrt vor Ablauf, automatisch
   entsperrt nach Ablauf der Sperrzeit. PASS.

Zusaetzlich: TestGuardedLogin_LocksAfterRepeatedFailures belegt das
Zusammenspiel mit dem echten IAM-02-LoginService End-to-End — selbst das
korrekte Passwort wird nach Sperrung abgewiesen. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:15:25 +02:00