Commit Graph
11 Commits
Author SHA1 Message Date
Nick SzaunigandClaude Opus 5.5 53fab102f2 Merge main checkpoint c01a388 into feat/erp-inventar-export
Brings the ERP inventory export up to date with the platform backbone and
field capture work. Conflicts in ARCHITECTURE.md, the API key service and
the API key settings tab resolved by keeping both sides:
- read and write keys stay separate; erp-inventory:read is read-only and
  cannot be combined with a write scope
- the failed-attempt lockout counts in the shared ThrottlerHit table so it
  holds across API pods
- the UI offers only scopes the user holds and creates one scope per key

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-13 22:49:53 +02:00
Nick SzaunigandClaude Opus 5.5 c01a38835a feat: platform backbone and field capture, first part (checkpoint)
Platform backbone (plan 2026-10-13-plattform-rueckgrat), reviewed tasks:
- run roles api/worker/job/all with shared startup checks (Task 1)
- schema and migration for DomainEvent, deliveries, JobRun, ThrottlerHit (Task 2)
- audit, notifications and domain events written in the same transaction (Task 3)
- dispatcher with SKIP LOCKED claims, retries and stale release (Task 4)
- worker building blocks: tick, LISTEN/NOTIFY wake-up, health (Task 5)
- worker process and entry points (Task 6)
- throttler counters in Postgres (Task 19)
- separate keys per purpose with key ring (Task 20)
- public origin page without exact location (Task 22)
- shop keys split into read and write (Task 23)
- Kustomize base, components, overlay encry, CI validation (Task 26)

Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), reviewed tasks:
- brood frames, read endpoints, apiary overview and offline pack
- network basics, offline queue v3, navigation sheets, form building blocks
- new inspection with last-time hints and weather, varroa per day,
  treatment prefill, apiary page with round
- inspection photo (Task 10) is implemented, review pending

Not rolled out. Remaining tasks follow in later commits.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-13 17:57:54 +02:00
Nick SzaunigandClaude Opus 5.5 fe7b61c3c1 fix(api-keys): gültige Schlüssel nie durch die Fehlversuchs-Sperre abweisen
Befunde aus dem Sicherheitsreview des Branches:

- MEDIUM: Die Sperre lief vor der Schlüsselsuche und zählte auch falsch
  aufgebaute Tokens. Wer die gemeinsame Adresse (Cluster-Ausgang, Proxy)
  nutzt oder per X-Forwarded-For vortäuscht, hätte ERPNext und den Shop
  15 Minuten aussperren können. Jetzt: erst Suche über den Hash; ein
  gültiger, nicht widerrufener, nicht abgelaufener Schlüssel kommt immer
  durch; 429 nur für unbekannte, widerrufene und abgelaufene Schlüssel;
  falsch aufgebaute Tokens sofort 401 ohne Zählung. Beim Start warnt
  ApiCore, wenn TRUST_PROXY in Produktion fehlt (im Cluster gesetzt).
- LOW: Bei vollem Speicher verdrängt der Limiter zuerst nicht gesperrte
  Einträge, gesperrte erst zuletzt.
- LOW: Bewegungs-Cursor mit Jahr außerhalb 2000-2100 ergab 500, jetzt 400.
- LOW/INFO: Vertrag legt fest, dass ERPNext Produkte und Chargen bei jedem
  Lauf vollständig neu liest (Voll-Spiegel) und Bewegungen inkrementell;
  remainingQuantity ist live, Bewegungen kommen 5 Minuten verzögert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 12:00:03 +02:00
Nick SzaunigandClaude Opus 5.5 d9670dcda5 fix: TRUST_PROXY-Kommentar aktualisieren und Modus beim Start loggen
Die Kommentare beschrieben noch einen einzelnen Reverse-Proxy-Hop. Sie
beschreiben jetzt die Cloudflare/Traefik-Liste. Beim Start wird einmal
auf info-Level geloggt, ob Hop-Zahl, Liste oder der Default aktiv ist
(nur Anzahl, keine Adressen), damit eine fehlende Produktionseinstellung
auffaellt. Das Standardverhalten bleibt unveraendert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 11:23:41 +02:00
Nick SzaunigandClaude Opus 5.5 9ae1d96a32 fix(api): throttle before auth, configurable proxy trust, no-store and a strict CSP
- ThrottlerGuard runs first, so requests with wrong tokens or API keys
  count against the rate limit before any database lookup.
- TRUST_PROXY (hop count or Express list) replaces the hard-coded 1.
- Every API answer except the public lot pages is Cache-Control: no-store
  without ETag; helmet sends default-src 'none' / frame-ancestors 'none'
  (Swagger keeps the page policy when enabled) and same-site CORP.
- Request log lines carry the path only - no query strings (the SSO
  callback's code and state no longer reach the logs).
- In production the server refuses to start without an https
  FRONTEND_ORIGIN or with COOKIE_SECURE=false.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:37:31 +02:00
Nick Szaunig 135738fdfc fix: Profilbild-Upload auf JSON/Base64 umgestellt statt multipart
POST /profile/avatar schlug live mit net::ERR_FAILED fehl (Verbindung
abgebrochen, keine Antwort) - selbst bei einer winzigen Testdatei.
CORS-Preflight lief sauber durch, nur der eigentliche multipart-POST
nicht; passt zum bereits dokumentierten Cloudflare-Verhalten bei
multipart-Uploads (siehe .gitea/workflows/*-image.yml). Umgangen durch
Umstellung auf denselben JSON-Content-Type, der fuer alle anderen
Endpunkte nachweislich zuverlaessig funktioniert: Bild wird
clientseitig als data:-URL kodiert und im JSON-Body gesendet.
Globales JSON-Body-Limit auf 5mb angehoben (deckt 2MB-Bild plus
Base64-Overhead).
2026-09-02 08:32:08 +02:00
Nick Szaunig ce3a0c44ac feat: globales HTTP-Access-Log und Exception-Logging
Backend Image / build-and-push (push) Successful in 2m50s
Frontend Image / build-and-push (push) Successful in 2m22s
Bisher waren 4xx-Fehler (falsches Passwort, Validierungsfehler,
fehlende Berechtigung) im Log komplett unsichtbar - Nest loggt
standardmaessig nur unbehandelte 5xx-Fehler. Ein Login, das aus
Infra-Gruenden fehlschlaegt (siehe TLS/Cloudflare-Vorfall), war dadurch
allein aus den Logs nicht diagnostizierbar.

- AllExceptionsFilter (global): loggt jede Exception mit Methode/Pfad/
  Status (4xx als warn, 5xx als error mit Stacktrace). Client-Antwort
  bleibt unveraendert - HttpExceptions liefern weiter exakt ihren
  bisherigen Body, unbehandelte Fehler weiter nur eine generische
  500-Meldung ohne interne Details.
- requestLoggerMiddleware (global): eine Zeile pro Request (Methode,
  Pfad, Status, Dauer), damit auch erfolgreiche Requests im Log
  auftauchen - vorher gab's dafuer gar keine Spur.
- AuthService.login()/verifyMfaAndLogin(): konkrete Ursache pro
  Fehlschlag (unbekannter User, deaktiviert, falsches Passwort,
  falscher MFA-Code) zusaetzlich zum bestehenden Audit-Log-Eintrag.

Nirgends werden Passwoerter, Tokens oder Request-Bodies geloggt - nur
Methode, Pfad, Status, Dauer und die (bereits client-sichere)
Fehlermeldung.

Verifiziert: tsc/eslint clean, 304/304 Tests (7 neu), Build clean.
2026-08-27 07:29:51 +02:00
Nick Szaunig f1298eff08 fix: harden auth against brute force, add auth audit logging, fix cookie/TLS gaps
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.
2026-08-25 10:49:25 +02:00
Nick Szaunig 029484f9d5 feat(frontend): Login + Benutzerverwaltung (Modul 1)
/login: Formular gegen AuthProvider (Access-Token im Memory, Refresh
per httpOnly-Cookie), Fehlermeldung bei falschen Zugangsdaten.

(app)/layout.tsx: geschuetztes Layout, leitet ohne Session zu /login um,
Header mit Nutzer-E-Mail + Abmelden. settings/layout.tsx: zusaetzliches
Admin-Gate fuer /settings/users.

/settings/users: Liste, Anlegen-Dialog (shadcn/Base UI Dialog+Select),
Deaktivieren - alles ueber TanStack Query gegen die Users-API.

proxy.ts (Next 16 Nachfolger von middleware.ts): optimistische
Cookie-Praesenz-Pruefung fuer /login und /settings/*, echte
Autorisierung bleibt serverseitig (RolesGuard).

Backend-Fixes waehrend Browser-Verifikation gefunden und behoben:
- CORS fehlte komplett (app.enableCors, FRONTEND_ORIGIN) - jeder
  Cross-Origin-Request von :3000 nach :3001 wurde geblockt.
- React-StrictMode-Doppel-Effect liess zwei parallele Refresh-Aufrufe
  laufen; da Refresh-Tokens rotierend/einmalig sind, gewann einer und
  der andere bekam 401 -> Nutzer flog nach jedem Reload aus der Session.
  Fix: AbortController im Session-Restore-Effect.
- Manifest verwies auf nicht existente PNG-Icons (404); durch
  public/icon.svg ersetzt.

7 Vitest-Tests (Login-Formular, Users-Liste, JWT-Decode), Lint,
Typecheck und Build gruen. Kompletter Flow (Login, Reload-Persistenz,
Benutzer anlegen/deaktivieren, Abmelden, Redirect ohne Session) live
im Browser gegen den echten Server verifiziert.
2026-08-21 08:45:37 +02:00
Nick Szaunig 370102387f fix(backend): Refresh-Token als httpOnly-Cookie statt Response-Body
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.
2026-08-21 08:27:58 +02:00
Nick Szaunig ae8f6d4435 feat(backend): Auth/Users/Audit-Modul (Modul 1)
AuthModule: Login/Refresh/Logout mit JWT-Access-Token + rotierendem,
gehashtem Refresh-Token (RefreshToken-Tabelle). JwtAuthGuard + RolesGuard
global via APP_GUARD registriert (gilt fuer jeden kuenftigen Endpunkt),
@Public()-Decorator als Ausnahme (Auth-Endpunkte, /health).

UsersModule: CRUD + Deactivate, nur ADMIN-Rolle, Passwoerter mit bcrypt
gehasht, nie im Response enthalten.

AuditModule: AuditLogService (Grundstruktur), wird von UsersService bei
create/update/deactivate injiziert und geschrieben.

Geteilte App-Konfiguration (Prefix/Versioning/Pipes/Helmet) aus main.ts
in src/app.config.ts extrahiert; e2e-Tests nutzten das vorher nicht und
bekamen dadurch 404 auf jede /api/v1/... Route - jetzt ueber
test/utils/create-test-app.ts geteilt, betrifft alle kuenftigen
e2e-Tests gleichermassen.

prisma/seed.ts legt einen Admin-User fuer lokale Entwicklung an.

26 Unit-Tests + 11 e2e-Tests (gegen echte Postgres-Testdatenbank) gruen,
Lint/Typecheck/Build gruen, Login/Refresh/Logout/Rollenpruefung manuell
gegen laufenden Server verifiziert.
2026-08-21 08:22:16 +02:00