Backend (kleinstmoeglich, wie im Ticket vorgesehen):
- internal/auth: TokenIssuer.IssueWithTTL, LoginService.LoginWithTTL
(additiv, bestehende Issue/Login unveraendert)
- internal/totp: LoginWithTOTPTTL (durchlaeuft denselben 2FA-Ablauf)
- cmd/account-api: neue Route POST /auth/mobile-login (JSON-Token statt
Cookie, 90 Tage TTL statt 30 Minuten)
Android-App (android/dms-capture, Kotlin, Gradle-Projekt):
- CameraX-Erfassung mit Live-Vorschau, erneuter Aufnahme, Mehrseiten-
Sammlung (AC2)
- Ruft ausschliesslich die bestehende DOC-01-Upload-API auf, kein neuer
Backend-Uploadpfad (AC3) -- echter Architektur-Fund dabei: dms-app
erwartet ein audience-gebundenes Modul-Token (aud=dms), kein rohes
Sitzungstoken; App tauscht ueber den bereits bestehenden Endpunkt
POST /auth/module-token (Cookie-Header), dokumentiert im Pruefprotokoll
- Room-basierte Offline-Warteschlange mit sichtbarem Status, WorkManager
fuer automatische Nachreichung ohne Nutzeraktion (AC4)
- Verstaendliche Fehlermeldungen bei Groessen-/Typ-Ablehnung statt nur
Fehlercode (AC5)
- Internes APK, kein Play-Store-Zwang (AC6)
Real getestet: Ende-zu-Ende-Auth-Kette (Login -> Modul-Token -> Bearer
gegen echten dms-app) auf Host 204, 13/13 Kotlin-Unit-Tests gruen, echter
APK-Build auf Host 236 (Android-SDK dort vorhanden, kein Install auf der
Entwicklungsmaschine, Gradle projektlokal nachgezogen statt System-Install).
Offen (Pruefprotokoll): Kamera-/Offline-/Smoke-Install-Pruefungen
brauchen einen Android-Emulator; kein System-Image auf Host 236
installiert, Nachinstallation bewusst nicht ohne Rueckfrage vorgenommen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix: Massenwiderrufs-Stichtagsvergleich nutzt jetzt IssuedAtNano statt
claims.IssuedAt, da RegisteredClaims.IssuedAt beim JWT-Roundtrip auf volle
Sekunden rundet und sonst ein Login in derselben Sekunde wie ein
RevokeAllForUser-Aufruf faelschlich als widerrufen gelten koennte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
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>