Commit Graph

19 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 e2af87a7e5 fix(a11y): Beschriftungen verknuepfen, Reiter umbrechen, Badge-Farbton zurueck
- Alle 14 <label> ohne Bezug haben jetzt htmlFor/id — davon 9 in den
  Fuehrer-Formularen, also genau dort, wo am Handy getippt wird. Die
  GPS-Gruppenueberschrift im Benutzerformular ist keine Feldbeschriftung
  mehr, sondern eine Ueberschrift.
- Das Auf- und Zuklappen im Audit-Log lag auf einem <div> und war mit der
  Tastatur nicht erreichbar; jetzt ein <button> mit aria-expanded, das bei
  fehlenden Details disabled ist. .audit-item-header war ausserdem zweimal
  in derselben Datei definiert — zusammengefasst.
- Die Reiter im Admin-Panel standen in einem inline-flex ohne Umbruch: bei
  360 px war die Leiste 395 px breit und die Seite lief horizontal ueber.
  Die Datei hatte gar keine Media-Query. Jetzt Umbruch, ein Breakpoint fuer
  <=480px und 44 px Mindesthoehe je Reiter.
- REGRESSION der letzten Runde: die geskriptete Token-Migration hat #e8eaf6
  (Indigo-Toenung) als neutrale Flaeche eingestuft, wodurch .badge-bulk-update
  im Hellmodus nicht mehr von .badge-default zu unterscheiden war. Farbton
  zurueckgesetzt.
- .settings-section-count und .settings-rule-index nutzten --color-border als
  Flaeche und lagen bei 4,49:1 — jetzt --color-surface-alt.
- Checkbox, Radio und Schieberegler folgen ueber accent-color der Markenfarbe
  statt dem Browser-Blau.

Geprueft: rechnerischer Kontrast-Durchlauf ueber alle Regeln mit Flaeche und
Text in beiden Ansichten; kein Ueberlauf mehr bei 360 px in Admin-Panel und
Fuehrer-Dashboard, hell wie dunkel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 20:47:44 +02:00
thomas 87011eee3c fix(pwa): Offline-Seite ausliefern, Fremdabhaengigkeiten der Karte entfernen
- Der Service-Worker-Cache wurde nie invalidiert: die letzte Runde hat
  offline.html in allen vier Zielen ersetzt, sw.js aber nicht angefasst.
  Ein Browser installiert einen Service Worker nur neu, wenn sich sw.js
  selbst aendert — bestehende Installationen haetten die alte, englische,
  ungestaltete Offline-Seite dauerhaft behalten. CACHE_NAME hochgezaehlt.
  Dabei faengt der Install jetzt einen fehlgeschlagenen Abruf ab; vorher
  wurde bei einem Fehler der ganze Service Worker nicht aktiv.
- Die Leaflet-Marker kamen von cdnjs.cloudflare.com — in einer PWA, die im
  Wald ohne Empfang benutzt wird, waren sie damit kaputt, und die IP jedes
  Besuchers ging an ein fremdes CDN. Verwiesen wurde ausserdem auf Leaflet
  1.7.1 bei installiertem 1.9.4. Die Bilder liegen im Paket und werden jetzt
  gebuendelt; Vite bettet sie als data:-URI ein (zusammen ~4 KB).
- Die Adress-Autovervollstaendigung im Benutzerformular rief Nominatim
  weiterhin direkt aus dem Browser auf, obwohl d9fecb6 dafuer einen Proxy
  eingefuehrt hat — nur die PLZ-Suche nutzte ihn. Neu: searchAddresses() in
  utils/geocode.js (teilt Wartezeit und Cache mit geocodeAddress) plus
  GET /api/public/geocode/search unter demselben geocodeLimiter. Die Antwort
  ist auf die Felder beschraenkt, die das Formular liest. Der Debounce steigt
  von 400 auf 600 ms, weil die Anfragen jetzt durch die serverseitige
  Mindestwartezeit laufen.

Geprueft: im Bundle kein cdnjs und kein nominatim mehr; der neue Endpunkt
liefert Treffer mit den erwarteten Feldern, zu kurze Begriffe 400.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 20:47:26 +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 dd39a7aff2 feat(design): Bildsprache des Portals übernehmen, Nachtansicht ergänzen
Die visuelle Ebene war bisher nie geprüft. Zwei Entscheidungen lagen dem
zugrunde: die Apps folgen der Bildsprache des Portals, und es gibt eine
Nachtansicht über prefers-color-scheme — Nachsuchen finden in der
Dämmerung und nachts statt, ein weißes Vollbild blendet dann.

Design-Tokens (App.css ist jetzt die einzige Farbquelle):
- Palette, Georgia-Serif für die Überschriftenebene und die kantigen
  2px-Ecken vom Portal übernommen; Sans bleibt für funktionale UI-Texte,
  wie es das Portal ebenfalls handhabt.
- Aktionsfarben haben jetzt eine zugehörige --color-on-*-Textfarbe. Ohne
  das wäre in der Nachtansicht weißer Text auf hellgrünem Grund gelandet.
- 307 fest verdrahtete Farbwerte pro App auf Tokens umgestellt,
  eigenschafts-abhängig (color/background/border getrennt). Das Schlüssel-
  wort "white" war dabei der gefährlichste Fall: als Textfarbe auf einer
  Vollton-Fläche musste es zu --color-on-* werden, als Fläche zu
  --color-surface.
- theme-color war dreifach verschieden (#2d6a2d gehörte zu keiner der
  beiden Paletten); Portal und alle Apps nutzen jetzt einen Wert.

Behobene Kontrastfehler (alle nachgerechnet, nicht geschätzt):
- .btn-success 3,13:1 und .btn-primary 4,33:1 lagen unter AA — der
  Hauptaktionsbutton der gesamten Anwendung.
- Feldrahmen 2,27:1 und Kartenrahmen 1,60:1 (Soll 3,0 für Bedienelemente).
- .badge-restore 3,46:1 und .badge-password 2,49:1 im Audit-Log.
Geprüft: 36 Paare in hell UND dunkel, 0 Verstöße.

Weitere visuelle Befunde:
- .type-badge war in PublicUserList.css und UserCard.css doppelt
  definiert. CSS ist global, die Admin-Variante gewann — die öffentliche
  Liste zeigte die falsche Badge-Optik. Fünf solcher Kollisionen zwischen
  öffentlicher Liste und Admin-Karte sind jetzt auf ihren Container
  eingegrenzt.
- Formularfelder hatten keine Hintergrundfarbe und nahmen den Browser-
  Standard: in der Nachtansicht leuchtete jedes Eingabefeld weiß. Fiel
  erst im Screenshot auf.
- Die Kartenkacheln sind immer taghell und waren nachts die mit Abstand
  hellste Fläche. Sie werden jetzt abgedunkelt, Marker bleiben hell.
- Die Badge-Definitionen im Audit-Log standen doppelt in derselben Datei
  (8 Regeln, identische Werte).

Mobiltauglichkeit:
- .user-phone war ein 14 px hoher Textlink — der tel:-Link ist der Zweck
  der öffentlichen Liste. Jetzt ein Ziel mit 44 px Mindesthöhe.
- Kopfzeile: Logo verkleinert, .app-title umbricht statt nowrap,
  Navigationsschaltflächen auf 44 px, Breakpoint für ≤480px ergänzt,
  auf schmalen Geräten nicht mehr klebend.
- 100vh -> 100dvh (Adressleiste erzeugte sonst Überlauf).
- :focus-visible für Buttons; prefers-reduced-motion respektiert.
- Geprüft mit Screenshots bei 360 px und 768 px, hell und dunkel:
  kein horizontaler Überlauf.

Aufgeräumt:
- logo-fallingbostel.png und logo-ljn.png entfernt: 898 KB pro App, von
  keiner Zeile Code referenziert, 60 % der Buildgröße und fast doppelt so
  groß wie der gesamte Anwendungscode. Build 1,5 MB -> 632 KB.
- Tote Stile (.login-prompt, .btn-success, .panel, .user-gps u. a.),
  doppelte body-Regel in index.css, Variablentippfehler --color-bg-muted.
- Die Offline-Seite der Apps war lang="en" und ungestaltet — genau die
  Seite, die im Funkloch erscheint. Jetzt gestaltet, deutsch, mit
  Nachtansicht, für Portal und Apps identisch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:43:57 +02:00
thomas f4939ef6c5 fix(frontend): Reset-Link auswerten, Absturz bei defektem localStorage verhindern
- PasswordReset las den Query-Parameter token nie aus. Wer auf den Link
  in der Reset-Mail klickte, landete wieder auf Schritt 1 mit leerem
  Token-Feld — der Mailversand aus d9fecb6 lief damit ins Leere. Der
  Token wird jetzt aus der URL übernommen und direkt Schritt 2 gezeigt.
- "Zurück zum Login" und die Weiterleitung nach dem Zurücksetzen zeigten
  auf "/" und damit beim Unterpfad-Deployment auf das Portal.
- PublicUserList rief JSON.parse(localStorage.getItem(...)) ungeschützt
  im Render-Pfad auf. Ein beschädigter Eintrag — oder ein Browser, der
  Site-Data blockiert — ließ die gesamte öffentliche Liste weiß werden.
  Lesen und Schreiben laufen jetzt über try/catch, und der Wert wird auf
  Plausibilität geprüft.
- Der Typfilter-Effekt feuerte auch beim Mounten, obwohl useUsers bereits
  selbst lädt: jeder Aufruf der öffentlichen Liste setzte zwei identische
  Anfragen ab. Der erste Lauf wird jetzt übersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:42:37 +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 474383d17b fix(nginx): Security-Header in überschreibenden location-Blöcken wiederholen
nginx vererbt add_header nicht in Blöcke, die eigene add_header setzen.
Dadurch gingen X-Frame-Options, X-Content-Type-Options und
X-XSS-Protection ausgerechnet bei den HTML-Antworten verloren – jeder
location-Block mit Cache-Control hat sie stillschweigend abgeschaltet.

Verifiziert: nginx -t ist für alle drei Konfigurationen erfolgreich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 08:12:15 +02:00
thomas 81b21da499 fix(frontend): kaputtes UserCard-JSX reparieren, Einladungs-Token erzeugbar machen
- UserCard.js ließ sich nicht übersetzen: die öffnende Hälfte des Ternary
  um den GPS-Bereich ({isEditingGPS ? () fehlte, während das ") : (" und
  das ")}" stehen geblieben waren. isEditingGPS wurde dadurch nur noch
  gesetzt, nie gelesen. Da die Datei in allen drei Apps identisch ist,
  scheiterte überall der Build.
- Der Einladungs-Flow aus 8384ad9 war nur zur Hälfte da: Backend und
  Login-Formular verlangen einen Invite-Token, aber kein Frontend hat
  /invite-token je aufgerufen – der Admin konnte gar keinen erzeugen und
  ein Führer ohne Passwort kam nicht ins System. Die Benutzerkarte hat
  jetzt einen Button dafür (nur bei hinterlegter E-Mail), verdrahtet über
  UserList und AdminPanel.
- api.js: bei einem Fehler ohne Request-Config lief der Retry-Zweig in
  "config.retry = 2" auf undefined und verdeckte den echten Fehler.
- useUsers: "mehr laden" hat die aktiven Filter verworfen und die
  ungefilterte zweite Seite angehängt. Mit dem serverseitigen Typfilter
  aus d9fecb6 fällt das jetzt deutlich stärker auf.
- Die öffentliche Liste zeigte statt des Telefon-Emojis ein kaputtes
  Ersatzzeichen.
- Der Logo-Upload nannte SVG als erlaubtes Format, das Backend lehnt es
  bewusst ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 08:11:55 +02:00
thomas 63f6d97318 fix(frontend): Vite-Build reparieren und Unterpfad-Deployment korrigieren
Der Build läuft über Vite ("build": "vite build"), der Code las
Umgebungsvariablen aber über process.env – das gibt es im Browser-Bundle
nicht. Damit war der Build-Arg REACT_APP_API_URL wirkungslos und der
Zugriff selbst lief in "process is not defined".

- constants.js nutzt import.meta.env und leitet den Deployment-Basispfad
  aus import.meta.env.BASE_URL ab (Fallback: Laufzeit-Erkennung).
  REACT_APP_* heißt überall VITE_* (Dockerfile, Compose, .env.example,
  Doku).
- /verwaltung und /passwort-zuruecksetzen wurden gegen
  window.location.pathname ohne Basispfad verglichen und trafen unter
  /nachsuche/ nie zu – die Passwort-Reset-Seite war produktiv nicht
  erreichbar. Neue Helfer withBase()/normalizePath().
- Der Service Worker wurde als '/sw.js' registriert und war damit der des
  Portals mit Scope '/'. Registrierung läuft jetzt über BASE_URL, inkl.
  passendem Scope.
- index.html verlinkte '/manifest.json' absolut, also das Portal-Manifest:
  die App hätte sich als Portal installiert. Jetzt %BASE_URL%.
- public/index.html (CRA-Rest) kollidierte im Vite-Build mit der
  index.html im Projektwurzelverzeichnis und wurde entfernt.
- manifest.json verwies auf ein favicon.ico, das es nicht gibt.
- package.json: Scripts auf Vite umgestellt. react-scripts,
  @testing-library/* und web-vitals entfernt – nichts davon wird
  importiert, und react-scripts stand im Widerspruch zum Build.
- Die ungenutzten Konstanten USER_TYPES/USER_TYPE_LABELS/RULES sind
  entfallen (die echten Werte kommen aus der Datenbank).
- Header bekam eine Prop onXLogin, die er gar nicht entgegennimmt und die
  den Logout-Handler durchreichte.

Verifiziert: alle drei Frontends bauen, im Bundle kein process.env mehr,
alle Pfade korrekt auf den jeweiligen Basispfad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 08:10:55 +02:00
thomas 718d84a545 fix(backend): Seeding beendet nicht mehr den Server, Routing und Shutdown korrigiert
- REGRESSION aus d9fecb6: server.js ruft seed.js seit dem Wechsel von
  exec('node seed.js') auf require('./seed') im selben Prozess auf.
  seedDatabase() endet aber mit mongoose.connection.close() und
  process.exit(0) – auf einer frischen Datenbank hat sich der Server damit
  unmittelbar nach dem Seeding selbst beendet. Verbindungsabbau und Exit
  macht jetzt nur noch der CLI-Aufruf (npm run seed); zusätzlich verbindet
  seed.js nicht erneut, wenn die Verbindung bereits steht.
- Seeding hing an User.countDocuments(). Die Seed-Liste ist bewusst leer,
  die Bedingung war also bei jedem Start wahr, und seed.js hat die Config
  per findOneAndUpdate überschrieben: App-Name, Regeln und Texte des
  Admins gingen bei jedem Neustart verloren. Bedingung hängt jetzt an
  Admin und Config, die Config wird nur noch angelegt, nie überschrieben.
- DELETE /api/users/bulk war nicht erreichbar: die Route stand hinter
  /users/:id, Express hat den Pfad an deleteUser mit id="bulk" gegeben
  (CastError -> 404). Bulk-Routen stehen jetzt davor.
- getAdminActivity rief mongoose.Types.ObjectId() ohne new auf. Seit
  bson 5 ist das eine echte Klasse, der Endpunkt lief immer in einen
  500er. Ungültige IDs liefern jetzt 400 statt 500.
- connectDB beendete den Prozess mit process.exit(1), womit die
  Retry-Schleife in server.js toter Code war. Jetzt wird geworfen.
- geocode.js rief in seinen Signal-Handlern process.exit(0) und hat den
  Graceful Shutdown mittendrin abgeschnitten. Der Cache wird nur noch
  geschrieben; das Beenden gehört server.js, das jetzt auch SIGINT
  behandelt und einen Timeout hat.
- errorHandler stand vor dem Static-/SPA-Fallback und war damit nicht
  mehr letzte Middleware.
- Jagdsprache in seed.js: "Verbreiten Sie den Standort und den Anschuss"
  -> "Verbrechen". Da der Seed die Config anlegt, ist das der Text, den
  die Nutzer sehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 08:09:53 +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 770b0b1d38 Fix app labels: replace Nachsuchen with app-specific names in drohnenfuehrer and stoeberhunde
- drohnenfuehrer: all Nachsuchenführer/Hundeführer labels replaced with Drohnenführer
- stoeberhunde: all Nachsuchenführer/Hundeführer labels replaced with Stöberhundeführer
- Fixed backend config, controllers, logger, env, package.json, seed.js
- Fixed frontend components: Header, UserList, UserForm, PublicUserList, HandlerLogin, AdminPanel, RulesDisplay
- Fixed Dockerfiles (PUBLIC_URL), nginx configs, podman-compose.yml, CONTAINER.md, docs
- Fixed service worker registration path: /sw.js -> ./sw.js
- Fixed portal/index.html
2026-05-03 08:24:28 +02:00
User edcce520d4 Commit aller Änderungen: Audit- und Produktionsfixes und weitere Anpassungen 2026-05-02 22:55:22 +02:00
User 6c4d55bdf0 Audit- und Produktionsfixes: sichere Handler-Set-Passwort-Flow, PII-Schutz, sichere Cookies, Upload-MIME-Whitelist, Health-Endpoint, Graceful Shutdown, HSTS/CSP, Seed-Datenbereinigung und Log-Rotation 2026-05-02 22:54:46 +02:00
User 52f21d964c fix: PWA install banner und Icons
- Dockerfile: icon-192.png und icon-512.png in Container aufgenommen
- manifest.json: favicon.ico entfernt (wurde als text/html geliefert)
- manifest.json: Cache-Busting ?v=2 für alle Icons
- index.html: Logo-Bild auf ?v=2 aktualisiert
- extra8002.conf: manifest.json Content-Type auf application/manifest+json gesetzt
- Alle Sub-App manifest.json: scope von '/' auf jeweiligen Pfad korrigiert
  (/nachsuche/, /drohnenfuehrer/, /stoeberhunde/)
- icon-192.png und icon-512.png: aus logo-fallingbostel.png generiert
2026-05-02 22:04:24 +02:00
thomas 5eb14a7826 init: Jagd Apps Heidekreis – Portal, Nachsuche, Drohnenführer, Stöberhunde 2026-05-02 17:31:20 +02:00