Commit Graph

7 Commits

Author SHA1 Message Date
thomas 2950f09485 chore: JSON-404 fuer die API, Login-Validator entschlacken, Altlasten entfernen
- Unbekannte Pfade unter /api fielen bis zum Express-Standard durch und
  lieferten eine HTML-Seite ("Cannot GET /api/foo"), mit der ein JSON-Client
  nichts anfangen kann. Es gab keinen 404-Handler; errorHandler greift nur
  bei next(err). Jetzt antwortet /api mit JSON und Status 404.
- validateLogin verlangte fuer das EINGEGEBENE Passwort mindestens 6 Zeichen.
  Eine Policy gehoert nicht in den Login-Pfad: kurze Eingaben lieferten 400
  "Validierungsfehler" statt 401 und verrieten damit etwas ueber die Regeln;
  ausserdem widersprach der Wert dem minlength: 12 des Admin-Schemas.
  Geprueft: kurzes Passwort liefert jetzt 401.
- d9fecb6 hatte versehentlich ein backend/-Verzeichnis im Repo-Wurzel
  eingecheckt (leere .gitignore, zwei 0-Byte-Logdateien) — ausserhalb jeder
  App. Entfernt und im Wurzel-.gitignore gegen Wiederkehr abgesichert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 20:48:10 +02:00
thomas d0e48f3385 fix(deploy): Backend startet wieder — JWT-Secret kam leer im Container an
Startblocker aus d9fecb6: die Compose-Dateien lesen seither
${NACHSUCHE_JWT_SECRET} (bzw. DROHNENFUEHRER_/STOEBERHUNDE_), die .env-Dateien
definieren aber weiterhin JWT_SECRET. Compose ersetzte die unbekannte Variable
still durch einen Leerstring, config/env.js beendete den Prozess daraufhin mit
Exit-Code 1 — mit restart: unless-stopped ein Crash-Loop. Nachgewiesen mit
`docker compose config` (JWT_SECRET: "") und einem Startversuch.

- Compose nutzt jetzt ${..._JWT_SECRET:?...}: fehlt der Wert, bricht compose
  mit einer Meldung ab, statt ihn stillschweigend zu leeren.
- Die .env-Dateien (nicht versioniert) tragen die korrekten Namen und je App
  ein EIGENES 32-Byte-Secret. Vorher stand in allen drei derselbe 16-Zeichen-
  Wert, wodurch die App-Trennung aus d9fecb6 wirkungslos war.
  FOLGE: alle laufenden Admin-Sitzungen und Fuehrer-Tokens sind ungueltig,
  alle muessen sich einmal neu anmelden.
- Fuehrer-Tokens tragen jetzt app: config.appName, und handlerAuth.js sowie
  die umbenannten Varianten pruefen den Claim. Vorher trugen sie nur
  { id, role } — die app-Pruefung in middleware/auth.js lief bei ihnen ins
  Leere, weil sie an `decoded.app &&` haengt.
  Geprueft: Fuehrer-Token aus App A gegen /me von App B -> 403, gegen die
  eigene -> 200.
- APP_URL steht in Compose wieder auf ${APP_URL:-}. Der Default aus der
  letzten Runde hat die Warnung aus config/env.js unterdrueckt, die auf eine
  fehlende Konfiguration hinweisen sollte.
- .env.example dokumentiert die neuen Namen, den openssl-Aufruf und dass
  CORS_ORIGIN/APP_URL produktiv nicht auf localhost stehen duerfen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 20:47:02 +02:00
thomas 868699a1bc fix(backend): Geocode-Cache begrenzen, Reset-Link reparieren
- Der Geocode-Cache war nur auf dem Erfolgspfad begrenzt. Die beiden
  Negativ-Pfade (cache.set(key, null)) schrieben ungebremst — über den
  öffentlichen GET /api/public/geocode genügten erfundene Postleitzahlen,
  um Speicher und die alle 60 s geschriebene Cache-Datei beliebig wachsen
  zu lassen. Bei NODE_OPTIONS=--max_old_space_size=256 eine reale Grenze.
  Alle Schreibpfade laufen jetzt über rememberInCache(); loadCache()
  beschneidet eine bereits gewachsene Datei auf das Limit.
  Geprüft: eine künstlich auf 5000 Einträge aufgeblähte Cache-Datei wird
  beim Start auf 1000 reduziert.
- Der Cache war trotz Kommentar kein LRU, sondern ein FIFO: ein Treffer
  hat den Eintrag nicht ans Ende gerückt, häufig genutzte Adressen flogen
  zuerst raus. Treffer werden jetzt neu einsortiert.
- /api/public/geocode bekommt einen eigenen Limiter (20/15 min). Der
  Endpunkt löst ausgehende Nominatim-Anfragen aus und teilt sich mit dem
  Geocoding im Admin-Bereich die globale Wartezeit von 1100 ms.
  Geprüft: Anfragen 21-24 liefern 429.
- resetPassword prüfte 6 Zeichen, das Admin-Schema verlangt 12. Passwörter
  dazwischen kamen durch und scheiterten erst an der Mongoose-Validierung
  — beim Nutzer kam "Serverfehler" (500) an. Jetzt 400 mit klarer Meldung.
- APP_URL wird aktiv gesetzt statt auskommentiert, und der Rückfallwert
  hängt den Unterpfad an. Vorher fiel appUrl auf CORS_ORIGIN zurück, und
  der Link in der Reset-Mail zeigte auf das Portal statt in die App.
  Geprüft: erzeugte URL lautet .../nachsuche/passwort-zuruecksetzen?token=…
- Die beim Zusammenführen verlorengegangene Platzhalter-Prüfung für
  JWT_SECRET ist wieder da (fängt auch CHANGE_ME_IN_PRODUCTION).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:41:08 +02:00
thomas 4b61db82fd security: Rechte-Eskalation über Handler-Tokens schließen
Ergänzt die Absicherung aus 8384ad9/d9fecb6 um die Lücken, die dort offen
geblieben sind.

- authenticateToken prüft jetzt die Rolle. Der C-01-Fix vergleicht nur
  `decoded.app`, und genau den Claim tragen Handler-Tokens nicht
  ({ id, role: 'handler' }) – `if (decoded.app && ...)` lief bei ihnen
  also ins Leere. Ein eingeloggter Hundeführer konnte seinen Bearer-Token
  weiterhin gegen /api/users, /api/config und /api/audit-logs schicken und
  hatte volle Admin-Rechte. Admin-Tokens tragen zusätzlich role: 'admin';
  Tokens ohne role bleiben bis zum Ablauf gültig, niemand wird ausgeloggt.
- Logout wird als LOGOUT protokolliert statt als LOGIN, und die Route
  liest den Benutzernamen über attachUserIfPresent (neu). Vorher lag gar
  keine Auth-Middleware auf /logout, der Eintrag lautete immer "unknown".
- forgotPassword sucht case-insensitive wie der Login. Ein als "Thorsten"
  angelegtes Konto konnte sich als "thorsten" anmelden, aber kein Passwort
  zurücksetzen.
- CSV-Export escaped führende =, +, -, @ (neues utils/csv.js, von
  userController und auditController gemeinsam genutzt). Ein Name wie
  =HYPERLINK(...) wurde sonst von Excel als Formel ausgeführt.
- Massen-Operationen filtern soft-deleted Einträge; der pre(/^find/)-Hook
  des Modells greift bei updateMany nicht.
- Die öffentliche Liste liefert wieder phone, landline, address und photo.
  Die UI zeigt diese Felder seit jeher an, die API lieferte sie seit dem
  PII-Commit nicht mehr – Anrufen war öffentlich nicht mehr möglich.
  E-Mail, Hashes und Invite-Felder bleiben ausgeschlossen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 08:09:34 +02:00
User d9fecb6914 security: implement audit fixes and backend optimizations
- L-03: PasswordReset.js minLength 6 -> 12 for all 3 apps
- B-01: PLZ geocoding proxy endpoint (GET /api/public/geocode) in all 3 backends;
  frontend PublicUserList now uses backend instead of direct Nominatim calls
- B-02: type filter server-side via onRefetch useEffect; removed redundant local
  available/type filters from PublicUserList useMemo
- audit fixes: app-specific JWT secrets, bcrypt cost 12, LRU geocode cache,
  auth middleware app-claim check, nginx CSP script-src cleanup,
  nginx.conf renamed to nginx.conf.dev, geocode-cache Docker volume
- add mailer.js utility (password reset emails)
2026-05-03 11:20:45 +02:00
User 8384ad9432 security: port binding, invite token auth, cookie flags, rate limiting
- Docker: bind all backend/frontend ports to 127.0.0.1 only (was 0.0.0.0)
- Docker: add shared jagd-network; portal uses container names instead of host ports
- Fix: set-password endpoints now require valid invite token (drohnenfuehrer, stoeberhunde)
- Fix: auth cookie secure flag enabled in production
- Fix: password reset token no longer logged in production
- Add: inviteLimiter (10/15min) on set-password routes in all three apps
- Add: importUsers capped at 500 entries to prevent DoS
- Refactor: rename handler -> drohnenfuehrer/stoeberhundefuehrer across all apps
2026-05-03 10:15:03 +02:00
thomas 5eb14a7826 init: Jagd Apps Heidekreis – Portal, Nachsuche, Drohnenführer, Stöberhunde 2026-05-02 17:31:20 +02:00