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>
43 lines
1.3 KiB
Go
43 lines
1.3 KiB
Go
package auth
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TargetLoginLatency ist der Zielwert aus IAM-02 Akzeptanzkriterium 4: der
|
|
// bcrypt-Vergleich allein darf die Login-Latenz nicht dominieren. 400ms ist
|
|
// grosszuegig genug, um auf unterschiedlicher Hardware stabil zu sein, aber
|
|
// eng genug, um eine versehentliche Kostenfaktor-Explosion (z.B. 16 statt 12)
|
|
// zuverlaessig aufzudecken.
|
|
const TargetLoginLatency = 400 * time.Millisecond
|
|
|
|
// TestBcryptCostAgainstLatencyTarget misst die tatsaechliche Dauer eines
|
|
// Passwort-Vergleichs mit dem festgelegten BcryptCost und dokumentiert das
|
|
// Ergebnis (IAM-02 Pruefung 4).
|
|
func TestBcryptCostAgainstLatencyTarget(t *testing.T) {
|
|
hash, err := HashPassword("benchmark-passwort")
|
|
if err != nil {
|
|
t.Fatalf("hash: %v", err)
|
|
}
|
|
|
|
start := time.Now()
|
|
if !VerifyPassword(hash, "benchmark-passwort") {
|
|
t.Fatal("verifikation haette erfolgreich sein muessen")
|
|
}
|
|
elapsed := time.Since(start)
|
|
|
|
t.Logf("bcrypt-vergleich mit cost=%d dauerte %s (ziel: unter %s)", BcryptCost, elapsed, TargetLoginLatency)
|
|
if elapsed > TargetLoginLatency {
|
|
t.Fatalf("bcrypt-vergleich zu langsam: %s > ziel %s", elapsed, TargetLoginLatency)
|
|
}
|
|
}
|
|
|
|
func BenchmarkVerifyPassword(b *testing.B) {
|
|
hash, _ := HashPassword("benchmark-passwort")
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
VerifyPassword(hash, "benchmark-passwort")
|
|
}
|
|
}
|