ts-node prisma/seed.ts lief bisher live im Container - der
TypeScript-Compiler dafuer braucht deutlich mehr Heap als das
512Mi-Pod-Limit, daher sporadische "JavaScript heap out of memory"-
Crashes beim Start (manchmal lief's, manchmal nicht, reine
GC-Zufaelligkeit an der Speichergrenze).
Fix: seed.ts wird jetzt wie main.ts beim Docker-Build vorkompiliert
(tsconfig.seed.json, rootDir bewusst auf prisma/ statt Projekt-Root
gesetzt, damit der relative Import '../generated/prisma' nach dem
Kompilieren weiterhin auf denselben Pfad zeigt). Laufzeit-CMD startet
nur noch plain `node dist/seed.js` - kein TypeScript-Compiler mehr im
Container. ts-node/typescript zurueck zu devDependencies (nur noch
fuer den Build-Schritt gebraucht, nicht mehr zur Laufzeit).
incremental in tsconfig.seed.json explizit deaktiviert - ein stale
tsconfig.seed.tsbuildinfo liess tsc beim zweiten Testlauf sonst
faelschlich "nichts zu tun" melden (derselbe Fallstrick wie bei
tsconfig.build.tsbuildinfo).
Verifiziert: tsc/eslint clean, 304/304 Tests, Build reproduzierbar
(zweimal hintereinander ohne Cache-Reset getestet), node dist/seed.js
lief erfolgreich gegen die echte lokale DB (erkannte den bestehenden
Admin korrekt und legte ihn nicht erneut an).
Bisher wurde nirgends automatisch ein erster Admin-User angelegt -
prisma/seed.ts existierte, war aber weder im Dockerfile-CMD noch sonst
irgendwo verdrahtet. Auf einer frischen Datenbank haette also niemand
sich einloggen koennen.
- Dockerfile-CMD: migrate:deploy && seed && node dist/main.js
- ts-node/typescript von devDependencies zu dependencies verschoben
(seed.ts laeuft als rohes TypeScript zur Laufzeit, kein
Kompilierschritt), tsconfig.json zusaetzlich ins Runtime-Image kopiert
- seed.ts gegen Unique-Constraint-Race bei mehreren gleichzeitig
startenden Pod-Replicas gehaertet (P2002 abgefangen statt Absturz)
- Login-Banner im Log nur beim tatsaechlichen Erst-Anlegen (URL aus
FRONTEND_ORIGIN, Benutzername, Passwort) - bewusst nicht bei jedem
Neustart, sonst wuerden veraltete/geaenderte Zugangsdaten weiter
geloggt und Credentials landen unnoetig dauerhaft in den Logs
Seed ist bereits idempotent per E-Mail-Check (auch bei deaktivierten
Usern), UsersService.assertNotLastSystemRoleUser verhindert ohnehin
das Deaktivieren des letzten aktiven Admins - kein Aenderungsbedarf
dort, siehe Konversation.
Verifiziert: tsc/eslint clean, 297/297 Tests, Build clean. Migration
und Schema live gegen die lokale DB bestaetigt (ueber WSL direkt, da
Windows->WSL-Portweiterleitung weiterhin kaputt ist); der eigentliche
Seed-Lauf selbst konnte nicht End-to-End gegen die DB verifiziert
werden (gleiches Verbindungsproblem), Banner-Ausgabe isoliert
smoke-getestet.
Benutzer koennen jetzt selbst Zwei-Faktor-Authentifizierung einrichten
und ihr Profil verwalten (Name, E-Mail, Passwort, Profilbild).
Backend:
- Neues MfaModule (src/auth/mfa/): AES-256-GCM-verschluesselte
TOTP-Secrets (otplib), QR-Code-Erzeugung, kurzlebige Setup-/
Challenge-JWTs fuer den 2-Schritt-Login-Flow
- Neues ProfileModule (src/profile/): PATCH /profile,
POST /profile/password, POST/DELETE/GET /profile/avatar,
POST /profile/mfa/enroll(/confirm), POST /profile/mfa/disable -
alle self-scoped ueber actor.id, ohne @RequirePermission (siehe
ALLOWLIST in authorization-coverage.e2e-spec.ts)
- AuthService.login() zweistufig: bei aktivem MFA erst
{ mfaRequired, mfaToken } statt Tokens, neuer Endpunkt
POST /auth/mfa/verify schliesst den Flow ab
- Admin-Recovery: POST /users/:id/mfa/reset (users:update) fuer
verlorene MFA-Geraete, da es keinen E-Mail-Reset-Flow gibt
Frontend:
- Login-Seite mit zweitem Schritt fuer den TOTP-Code
- Neue Profilseite (/profile, ausserhalb des admin-only
Settings-Bereichs): Profilbild, Name/E-Mail, Passwort, MFA-Ein-/
Ausrichtung mit QR-Code-Dialog
- Header zeigt jetzt Avatar mit Dropdown (Profil/Abmelden) statt
nur E-Mail-Text
- Admin-Users-Tabelle zeigt MFA-Status + Reset-Button
Haertung nach Security-Review (Backend/Frontend-Diff, kein CRITICAL,
mehrere MEDIUM behoben):
- Access-Token wird bei Passwortwechsel sofort ungueltig
(User.passwordChangedAt + JWT-iat-Vergleich in
PermissionResolverService.isTokenValid, vorher isUserActive) statt
erst nach bis zu 15 Minuten
- E-Mail-Aenderung verlangt jetzt aktuelles Passwort
- POST /profile/password und /profile/mfa/disable haben jetzt das
gleiche strikte Throttling wie Login (SENSITIVE_ACTION_THROTTLE)
- Kein stiller leerer Verschluesselungs-Key-Fallback mehr (wirft
jetzt explizit)
Bewusst zurueckgestellt (dokumentiert, kein aktiver Exploit-Pfad):
dedizierter Verschluesselungs-Key fuer MFA-Secrets statt
JWT_ACCESS_SECRET-Ableitung, dedizierte users:mfa-reset-Permission
statt users:update, separate JWT-Secrets pro Token-Zweck.
Verifiziert: tsc/eslint clean, 297/297 Backend- + 31/31
Frontend-Unit-Tests, Build clean auf beiden Projekten. e2e weiterhin
durch bekanntes WSL2/Postgres-Problem blockiert. Unabhaengiges
Security- und Code-Quality-Review durchgefuehrt (0 CRITICAL, 0 HIGH).
Bisher lief keine Migration automatisch: dem Runtime-Image fehlten
Prisma-CLI, prisma/migrations/ und prisma.config.ts (nur dist+generated
wurden kopiert, prisma war devDependency). Jetzt:
- prisma von devDependencies zu dependencies verschoben (wird fuer
migrate:deploy im --omit=dev Runtime-Image gebraucht)
- neues npm-Script migrate:deploy (prisma migrate deploy)
- Dockerfile kopiert prisma/, prisma.config.ts zusaetzlich ins Runtime-Image
- deploy/k8s/backend/migrate-job.yaml: Job, der dasselbe Backend-Image mit
ueberschriebenem command nutzt statt eines eigenen Migrations-Images,
laeuft einmalig vor dem Rollout (Reihenfolge/kubectl-Befehle als
Kommentar in der Datei)
Verifiziert: tsc, 253/253 Unit-Tests, Build clean, `npx prisma --version`
laedt prisma.config.ts korrekt.
Security review findings (HIGH-1/2/3, MEDIUM):
- Rate limiting via @nestjs/throttler: global 100 req/min default, stricter
10 req/min on login and refresh. Also closes the MEDIUM finding (public
batch-lookup enumeration) since it now sits behind the global limit too.
Trust the K8s ingress's single proxy hop so throttling keys off the real
client IP, not the ingress pod's IP.
- Auth events (login success/failure, logout, token refresh) are now
audit-logged via new AuditAction values, closing the gap where the
project's own audit-log principle never actually covered the Auth module.
- Refresh-token cookie now defaults to Secure=true (opt out only via
COOKIE_SECURE=false for local HTTP dev), instead of keying off NODE_ENV
which was never set in the K8s deploy templates. Added a TLS block +
cert-manager annotation to both the backend and frontend ingress
templates, which previously had no HTTPS termination configured at all.
QrModule mit GET /qr/:entityType/:id.svg fuer colonies (Deep-Link
direkt zu /inspections/new), apiaries, queens, honey-batches und
storage-containers. Nutzt pdfkit-Nachbar qrcode zur SVG-Erzeugung.
ExportModule mit GET /export/colonies.csv, /export/honey-batches.csv,
/export/:entity.json (apiaries/colonies/queens/honey-batches/tasks)
und /export/:report.pdf (honey-batch-traceability via pdfkit).
Ausserdem fehlendes "typecheck"-Skript in package.json ergaenzt
(von CLAUDE.md dokumentiert, war aber nicht vorhanden).
ARCHITECTURE.md §3 legt fest: Access-Token im Memory/Context, Refresh-Token
als httpOnly-Cookie. Vorherige Implementierung gab beide Tokens im JSON-Body
zurueck - vor dem Frontend-Aufbau (das genau gegen diesen Vertrag baut)
korrigiert.
/api/v1/auth/login und /refresh setzen jetzt ein httpOnly-Cookie
(Path /api/v1/auth, SameSite=Lax, secure in production) und liefern nur
noch { accessToken } im Body. /refresh und /logout lesen den Refresh-Token
aus dem Cookie statt aus dem Request-Body.
e2e-Tests und Live-Smoke-Test (curl mit Cookie-Jar) verifizieren
Login/Rotation/Logout-Invalidierung. 12 e2e- + 26 Unit-Tests, Lint,
Typecheck und Build gruen.