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>
internal/auth: Login/Logout ueber httpOnly/Secure/SameSite=Strict-Cookie mit
HS256-JWT (30min TTL), bcrypt-Passwort-Hashing (Cost 12, explizit begruendet
und benchmarkt statt DefaultCost uebernommen), RequireAuth-Middleware fuer
geschuetzte Routen. LoginService ist strukturell auf einen Tenant gescopt
(nutzt user.TenantUserStore, dessen Pool = eine Tenant-DB — derselbe
Mechanismus wie in TEN-01/TEN-02), liefert bei falscher E-Mail und falschem
Passwort denselben Fehler (User-Enumeration-Schutz) inkl. Dummy-bcrypt-
Vergleich gegen Timing-Seitenkanal bei unbekannter E-Mail.
user.TenantUserStore erweitert um SetPasswordHash/GetByEmailForAuth
(password_hash bleibt ausserhalb des regulaeren User-Typs/JSON-Pfads).
Migration 0002 fuegt password_hash-Spalte hinzu (Default '', da IAM-01
User ohne Passwort anlegt).
Login-Handler ist wie IAM-01/TEN-02 aus denselben Gruenden (Tenant-
Connection-Routing = TEN-06, noch nicht gebaut) nicht in cmd/core/main.go
verdrahtet — Package ist eigenstaendig nutzbar/getestet.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Login-Query tenant-gescopt — TestLoginService_NoCrossTenantLogin: gleiche
E-Mail in zwei Tenant-DBs mit unterschiedlichem Passwort, Login gegen
Tenant A mit Tenant-B-Passwort schlaegt fehl. PASS.
2. Session-Fixation/Token-Manipulation — TestTokenVerify_RejectsManipulatedPayload
und TestTokenVerify_RejectsWrongSecret: manipuliertes/falsch signiertes
Token wird abgelehnt. PASS.
3. Abgelaufenes Token erzwingt Neuanmeldung — TestTokenVerify_RejectsExpiredToken
und TestRequireAuth_BlocksWithoutValidCookie. PASS.
4. Login-Latenz mit Kostenfaktor 12 gemessen: 294ms (Ziel < 400ms) —
TestBcryptCostAgainstLatencyTarget. PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Benutzer-Datenmodell + CRUD fuer Tenant-User (tenant-scoped, keine
tenant_id-Spalte noetig, Tenant ergibt sich aus der DB-Verbindung, Modell C)
und getrennt dafuer SuperadminStore fuer mandantenuebergreifende Konten in
der Registry-DB — First-Class-Typ statt tenant_id-NULL-Sonderfall im
Tenant-User-Code (bekannter archivdms-Fehler vermieden).
E-Mail-Eindeutigkeit: tenant-scoped fuer normale Benutzer (UNIQUE-Constraint
gilt nur innerhalb der jeweiligen Tenant-DB), global fuer Superadmins
(eine Registry-DB, ein UNIQUE-Constraint).
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. CRUD automatisiert getestet inkl. Negativfaellen — TestTenantUserStore_CRUD
deckt doppelte E-Mail (ErrEmailTaken) und unbekannte ID ab. PASS.
2. Superadmin-Anlage ohne Tenant-Kontext — TestSuperadminStore_CreateWithoutTenantContext:
SuperadminStore.Create hat syntaktisch keinen Tenant-Parameter, kein
if-Zweig fuer "kein Tenant" im Code. PASS.
3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT
durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen.
Tenant-User-Handler ist im Code vorhanden, aber in cmd/core/main.go noch
nicht geroutet — braucht Connection-Routing pro Mandant (TEN-06), das nicht
Teil dieser Kachel ist. Nur der Superadmin-Endpunkt ist verdrahtet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registry-DB (nur Tenant-Metadaten), Provisioning-Routine legt pro Mandant
eine physisch isolierte Postgres-DB an und registriert sie transaktional
(Rollback der DB bei fehlgeschlagener Registrierung). Schlanker HTTP-Handler
als Schnittstellen-Vorbereitung fuer API-01/TEN-02, kein eigenes REST-Grundgerüst.
Pruefungen:
1. Migration up/down geschrieben (0001_tenant_registry.{up,down}.sql) — nicht
gegen echte DB ausgefuehrt, da auf dieser Maschine kein Go/Postgres-Test-
Setup verfuegbar ist. Offen zur Ausfuehrung.
2. Integrationstest TestProvision_CreatesIsolatedDatabases geschrieben (zwei
Mandanten, prueft unterschiedliche db_name und current_database()) —
ebenfalls nicht ausgefuehrt, guarded per TEST_ADMIN_DSN env var. Offen.
3. Slug-Validierung (unit test TestValidateSlug) deckt SQL-Injection-Versuch
im Datenbanknamen ab — ebenfalls nicht lokal ausgefuehrt, da kein Go
Compiler auf dieser Maschine vorhanden ist. Offen.
Alle drei Pruefungen sind vorbereitet, aber NICHT durchgefuehrt worden —
zaehlen laut Vorgabe als offen bis auf einer Maschine mit Go+Postgres verifiziert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>