Commit Graph

13 Commits

Author SHA1 Message Date
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 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 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
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 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