internal/audit: eigenes, strukturiertes Audit-Datenmodell (Akteur, Aktion,
Zielobjekt, Zeitpunkt, Tenant) in der Registry-DB, getrennt von jedem
allgemeinen Anwendungs-Log (eigenes Paket, eigene Tabelle audit_events,
kein Logging-Framework). Log.Record ist der EINE zentrale Schreibpfad —
es gibt keine zweite Schreibmoeglichkeit, ueber die ein Handler die
Validierung umgehen koennte.
Fehlender Tenant-Bezug wird zweifach verhindert (Akzeptanzkriterium 2):
Log.Record weist leeren TenantSlug direkt ab (ErrMissingTenant), zusaetzlich
erzwingt eine CHECK-Constraint in der Migration dasselbe auf Datenbankebene,
selbst wenn Log.Record umgangen wuerde. Mandantenuebergreifende Ereignisse
(z.B. Superadmin-Aktionen) nutzen den reservierten Wert audit.SystemTenant
statt NULL oder leerem String — es gibt keinen Weg, ganz ohne Tenant-Bezug
zu schreiben.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Automatisierter Test belegt genau einen Audit-Eintrag pro
sicherheitsrelevantem Vorgang — TestRecord_PersistsExactlyOneEventPerSecurityIncident
(simulierter fehlgeschlagener Login), Feldinhalte verifiziert. PASS.
2. Fehlender Tenant-Bezug durch Constraint/Test verhindert —
TestRecord_RejectsMissingTenant (App-Ebene) UND
TestConstraint_RejectsMissingTenantAtDatabaseLevel (direkter INSERT unter
Umgehung von Log.Record, durch CHECK-Constraint abgewiesen). PASS.
3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT
durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
internal/license: Ed25519-signierte Lizenzschluessel (stdlib crypto/ed25519,
keine neue Abhaengigkeit). Issuer haelt den privaten Schluessel (lebt beim
Lizenzgeber), Validator nur den oeffentlichen (lebt im Core-Prozess) — klare
Trennung Ausstellung/Pruefung nach Unleash-Vorbild (Flag-Verwaltung vs.
Flag-Auswertung).
Store.Install prueft NUR die Signatur und persistiert den Lizenzumfang
(Plan, Modul-Liste, Laufzeit) in tenant_licenses (Registry-DB, 1:1 zu
tenants). Eine bereits abgelaufene, aber korrekt signierte Lizenz laesst
sich trotzdem einspielen — der Ablauf wird erst bei Store.RequireActive
bewertet (liefert ErrLicenseExpired statt Panic/Absturz), waehrend
Store.Status den Umfang unabhaengig vom Ablauf weiterhin liefert.
Neu: scripts/run-checks.sh buendelt reset-test-env.sh + go build/vet/test
(-p 1) zu einem Ein-Kommando-Check fuer den Testhost.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Manipulierter Lizenzschluessel zuverlaessig erkannt —
TestParse_RejectsTamperedKey, TestParse_RejectsWrongKeyPair,
TestStore_InstallRejectsInvalidSignature. PASS.
2. Ablauf loest definierten eingeschraenkten Zustand aus, kein harter
Systemausfall — TestStore_RequireActive_DetectsExpiry (inkl. recover()-
Absicherung im Test, dass kein Panic auftritt), ErrLicenseExpired statt
Absturz; Status bleibt trotzdem abfragbar. PASS.
3. Signaturpruefung von zweiter Person gegen Dokumentation nachvollzogen —
NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar).
Offen.
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>