- 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>
- 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>
- 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>
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>
- 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