From 770b0b1d383480f5e54ee22fba41f41a7049295f Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 3 May 2026 08:23:19 +0200 Subject: [PATCH] Fix app labels: replace Nachsuchen with app-specific names in drohnenfuehrer and stoeberhunde MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- drohnenfuehrer/CONTAINER.md | 26 +- drohnenfuehrer/backend/.env.example | 4 +- drohnenfuehrer/backend/config/env.js | 4 +- .../backend/controllers/authController.js | 24 +- .../backend/controllers/handlerController.js | 71 +--- .../backend/controllers/userController.js | 4 +- drohnenfuehrer/backend/package.json | 4 +- drohnenfuehrer/backend/seed.js | 2 +- drohnenfuehrer/backend/utils/logger.js | 2 +- drohnenfuehrer/docs/API.md | 18 +- drohnenfuehrer/docs/ARCHITECTURE.md | 6 +- drohnenfuehrer/frontend/Dockerfile | 2 +- drohnenfuehrer/frontend/package.json | 2 +- drohnenfuehrer/frontend/public/index.html | 6 +- drohnenfuehrer/frontend/public/offline.html | 2 +- .../src/components/admin/AdminPanel.js | 24 +- .../frontend/src/components/common/Header.js | 2 +- .../src/components/handler/HandlerLogin.js | 2 +- .../src/components/public/PublicUserList.js | 12 +- .../src/components/public/RulesDisplay.js | 4 +- .../frontend/src/components/users/UserForm.js | 10 +- .../frontend/src/components/users/UserList.js | 8 +- drohnenfuehrer/frontend/src/index.js | 2 +- drohnenfuehrer/frontend/src/services/users.js | 2 +- .../frontend/src/utils/constants.js | 36 +- drohnenfuehrer/nginx/extra8002.conf | 10 +- drohnenfuehrer/podman-compose.yml | 10 +- portal/index.html | 324 +++++++++++------- stoeberhunde/CONTAINER.md | 26 +- stoeberhunde/backend/.env.example | 4 +- stoeberhunde/backend/config/env.js | 4 +- .../backend/controllers/authController.js | 24 +- .../backend/controllers/handlerController.js | 71 +--- .../backend/controllers/userController.js | 4 +- stoeberhunde/backend/package.json | 2 +- stoeberhunde/backend/seed.js | 2 +- stoeberhunde/backend/utils/logger.js | 2 +- stoeberhunde/docs/API.md | 18 +- stoeberhunde/docs/ARCHITECTURE.md | 6 +- stoeberhunde/frontend/Dockerfile | 2 +- stoeberhunde/frontend/package.json | 2 +- stoeberhunde/frontend/public/index.html | 6 +- stoeberhunde/frontend/public/offline.html | 2 +- .../src/components/admin/AdminPanel.js | 14 +- .../frontend/src/components/common/Header.js | 2 +- .../src/components/handler/HandlerLogin.js | 2 +- .../src/components/public/PublicUserList.js | 12 +- .../src/components/public/RulesDisplay.js | 4 +- .../frontend/src/components/users/UserForm.js | 8 +- .../frontend/src/components/users/UserList.js | 8 +- stoeberhunde/frontend/src/index.js | 2 +- stoeberhunde/frontend/src/services/users.js | 2 +- stoeberhunde/frontend/src/utils/constants.js | 36 +- stoeberhunde/nginx/extra8002.conf | 10 +- stoeberhunde/podman-compose.yml | 10 +- 55 files changed, 433 insertions(+), 475 deletions(-) diff --git a/drohnenfuehrer/CONTAINER.md b/drohnenfuehrer/CONTAINER.md index 82d581b..9c6297b 100644 --- a/drohnenfuehrer/CONTAINER.md +++ b/drohnenfuehrer/CONTAINER.md @@ -1,4 +1,4 @@ -# 🐳 Container-Betrieb - Tracking Leaders App +# 🐳 Container-Betrieb - Drohnenführer App ## Schnellstart mit Podman/Docker @@ -62,9 +62,9 @@ podman-compose logs -f frontend | Service | Container Name | Port | Beschreibung | |---------|---------------|------|--------------| -| Frontend | tracking-leaders-frontend | 8080 | React App mit Nginx | -| Backend | tracking-leaders-backend | 5000 | Node.js Express API | -| MongoDB | tracking-leaders-mongo | 27017 | MongoDB Datenbank | +| Frontend | drohnenfuehrer-frontend | 8080 | React App mit Nginx | +| Backend | drohnenfuehrer-backend | 5000 | Node.js Express API | +| MongoDB | drohnenfuehrer-mongo | 27017 | MongoDB Datenbank | ## Nützliche Befehle @@ -86,15 +86,15 @@ podman-compose down -v ### In Container einsteigen ```bash # Backend -podman exec -it tracking-leaders-backend sh +podman exec -it drohnenfuehrer-backend sh # MongoDB -podman exec -it tracking-leaders-mongo mongosh +podman exec -it drohnenfuehrer-mongo mongosh ``` ### Datenbank seeden (manuell) ```bash -podman exec -it tracking-leaders-backend node seed.js +podman exec -it drohnenfuehrer-backend node seed.js ``` ### Health Checks prüfen @@ -161,7 +161,7 @@ Für Production solltest du einen Reverse Proxy (z.B. Traefik, Nginx) vorschalte podman volume ls # Backup erstellen -podman run --rm -v tracking-leaders_mongo-data:/data -v $(pwd):/backup alpine tar czf /backup/mongo-backup-$(date +%Y%m%d).tar.gz -C /data . +podman run --rm -v drohnenfuehrer_mongo-data:/data -v $(pwd):/backup alpine tar czf /backup/mongo-backup-$(date +%Y%m%d).tar.gz -C /data . ``` ## Troubleshooting @@ -188,7 +188,7 @@ curl http://localhost:5000/health ### Datenbank leer ```bash # Seed-Skript ausführen -podman exec -it tracking-leaders-backend node seed.js +podman exec -it drohnenfuehrer-backend node seed.js ``` ### Port bereits belegt @@ -205,7 +205,7 @@ Beide Dockerfiles nutzen bereits Multi-Stage Builds für minimale Image-Größen ### Image-Größen prüfen ```bash -podman images | grep tracking-leaders +podman images | grep drohnenfuehrer ``` Erwartete Größen: @@ -245,7 +245,7 @@ podman stats # Alle Services for svc in backend frontend mongo; do echo "=== $svc ===" - podman inspect tracking-leaders-$svc | grep -A5 Health + podman inspect drohnenfuehrer-$svc | grep -A5 Health done ``` @@ -255,10 +255,10 @@ Wenn du von lokalem MongoDB zu Container wechselst: ```bash # 1. Export aus lokalem MongoDB -mongodump --db tracking-leaders --out ./dump +mongodump --db drohnenfuehrer --out ./dump # 2. Import in Container -podman exec -i tracking-leaders-mongo mongorestore --drop /dump +podman exec -i drohnenfuehrer-mongo mongorestore --drop /dump ``` ## Weitere Informationen diff --git a/drohnenfuehrer/backend/.env.example b/drohnenfuehrer/backend/.env.example index 01de079..21d31c2 100644 --- a/drohnenfuehrer/backend/.env.example +++ b/drohnenfuehrer/backend/.env.example @@ -3,7 +3,7 @@ PORT=5000 NODE_ENV=development # Database Configuration -MONGO_URI=mongodb://127.0.0.1:27017/tracking-leaders +MONGO_URI=mongodb://127.0.0.1:27017/drohnenfuehrer # JWT Configuration JWT_SECRET=your-super-secret-jwt-key-change-this-in-production @@ -17,5 +17,5 @@ CORS_ORIGIN=http://localhost:3000 # Geocoding Configuration (OpenStreetMap Nominatim) GEOCODE_URL=https://nominatim.openstreetmap.org/search -GEOCODE_USER_AGENT=tracking-leaders-app/1.0 (admin@localhost) +GEOCODE_USER_AGENT=drohnenfuehrer-app/1.0 (admin@localhost) GEOCODE_MIN_DELAY_MS=1100 diff --git a/drohnenfuehrer/backend/config/env.js b/drohnenfuehrer/backend/config/env.js index c5825a0..800e73d 100644 --- a/drohnenfuehrer/backend/config/env.js +++ b/drohnenfuehrer/backend/config/env.js @@ -2,13 +2,13 @@ require('dotenv').config(); const config = { port: process.env.PORT || 5000, - mongoUri: process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/tracking-leaders', + mongoUri: process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/drohnenfuehrer', jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production', jwtExpiresIn: process.env.JWT_EXPIRES_IN || '24h', nodeEnv: process.env.NODE_ENV || 'development', corsOrigin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5000'], geocodeUrl: process.env.GEOCODE_URL || 'https://nominatim.openstreetmap.org/search', - geocodeUserAgent: process.env.GEOCODE_USER_AGENT || 'tracking-leaders-app/1.0 (admin@localhost)', + geocodeUserAgent: process.env.GEOCODE_USER_AGENT || 'drohnenfuehrer-app/1.0 (admin@localhost)', geocodeMinDelayMs: parseInt(process.env.GEOCODE_MIN_DELAY_MS || '1100', 10) }; diff --git a/drohnenfuehrer/backend/controllers/authController.js b/drohnenfuehrer/backend/controllers/authController.js index d7b2ddc..d7e24ac 100644 --- a/drohnenfuehrer/backend/controllers/authController.js +++ b/drohnenfuehrer/backend/controllers/authController.js @@ -45,15 +45,11 @@ const login = async (req, res) => { ); // Set token in httpOnly cookie (XSS protection) - const secureCookie = config.nodeEnv === 'production' - ? (req.secure || req.headers['x-forwarded-proto'] === 'https') - : false; - res.cookie('token', token, { - httpOnly: true, - secure: secureCookie, - sameSite: 'lax', - path: '/', + httpOnly: true, // Not accessible via JavaScript (XSS protection) + secure: false, // Allow over HTTP in development (localhost) + sameSite: 'lax', // Relaxed CSRF protection (allows cross-port cookies on localhost) + path: '/', // Ensure cookie is sent for all app routes, including /drohnenfuehrer/api maxAge: 24 * 60 * 60 * 1000 // 24 hours in milliseconds }); @@ -82,14 +78,10 @@ const logout = async (req, res) => { const username = req.user?.username || 'unknown'; await auditAuth(req, true, username, null); - const secureCookie = config.nodeEnv === 'production' - ? (req.secure || req.headers['x-forwarded-proto'] === 'https') - : false; - // Clear the token cookie res.clearCookie('token', { httpOnly: true, - secure: secureCookie, + secure: false, sameSite: 'lax', path: '/' }); @@ -148,11 +140,13 @@ const forgotPassword = async (req, res) => { }); // In production, send email here - logger.info(`Password reset token generiert für Benutzer: ${username}`); + // For development, log the token + logger.info(`Password reset token for ${username}: ${token} (expires in 1 hour)`); res.json({ success: true, - message: 'Reset-Token wurde generiert. Bitte kontaktieren Sie den Administrator.' + message: 'Reset-Token wurde generiert. In der Entwicklungsumgebung finden Sie den Token in den Logs.', + ...(config.nodeEnv === 'development' && { token }) // Only in dev! }); } catch (error) { logger.error('Fehler bei Passwort-Reset-Anfrage:', error); diff --git a/drohnenfuehrer/backend/controllers/handlerController.js b/drohnenfuehrer/backend/controllers/handlerController.js index e619540..e4b88b8 100644 --- a/drohnenfuehrer/backend/controllers/handlerController.js +++ b/drohnenfuehrer/backend/controllers/handlerController.js @@ -1,6 +1,5 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); -const crypto = require('crypto'); const User = require('../models/User'); const config = require('../config/env'); const logger = require('../utils/logger'); @@ -50,42 +49,22 @@ const handlerLogin = async (req, res) => { } }; -// POST /api/handler/set-password — set first password using a one-time invite token -// Admin must generate the invite token via POST /api/users/:id/invite-token first +// POST /api/handler/set-password — set first password (handler provides token + new password) +// Admin first sets email, then the handler can set their own password const setHandlerPassword = async (req, res) => { try { - const { email, inviteToken, newPassword } = req.body; + const { email, newPassword } = req.body; - if (!email || !inviteToken || !newPassword || newPassword.length < 8) { - return res.status(400).json({ success: false, message: 'E-Mail, Einladungs-Token und Passwort (min. 8 Zeichen) erforderlich' }); + if (!email || !newPassword || newPassword.length < 6) { + return res.status(400).json({ success: false, message: 'E-Mail und Passwort (min. 6 Zeichen) erforderlich' }); } - const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }) - .select('+inviteToken +inviteTokenExpiry'); - - // Always return the same error to prevent user enumeration - const invalidMsg = 'Ungültiger oder abgelaufener Einladungs-Token'; - if (!user || !user.inviteToken || !user.inviteTokenExpiry) { - return res.status(401).json({ success: false, message: invalidMsg }); + const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }); + if (!user) { + return res.status(404).json({ success: false, message: 'Kein Drohnenführer mit dieser E-Mail gefunden' }); } - if (user.inviteTokenExpiry < new Date()) { - user.inviteToken = null; - user.inviteTokenExpiry = null; - await user.save(); - return res.status(401).json({ success: false, message: invalidMsg }); - } - - // Timing-safe comparison to prevent timing attacks - const incoming = Buffer.from(inviteToken.trim()); - const stored = Buffer.from(user.inviteToken); - if (incoming.length !== stored.length || !crypto.timingSafeEqual(incoming, stored)) { - return res.status(401).json({ success: false, message: invalidMsg }); - } - - user.passwordHash = await bcrypt.hash(newPassword, 12); - user.inviteToken = null; - user.inviteTokenExpiry = null; + user.passwordHash = await bcrypt.hash(newPassword, 12); await user.save(); res.json({ success: true, message: 'Passwort erfolgreich gesetzt' }); @@ -114,7 +93,7 @@ const updateHandlerSelf = async (req, res) => { ); if (!user) { - return res.status(404).json({ success: false, message: 'Hundeführer nicht gefunden' }); + return res.status(404).json({ success: false, message: 'Drohnenführer nicht gefunden' }); } res.json({ success: true, data: user }); @@ -135,32 +114,4 @@ const getHandlerSelf = async (req, res) => { } }; -// POST /api/users/:id/invite-token (admin only) — generate a one-time invite token for a handler -const generateInviteToken = async (req, res) => { - try { - const user = await User.findOne({ _id: req.params.id, deleted: false }) - .select('+inviteToken +inviteTokenExpiry'); - if (!user) { - return res.status(404).json({ success: false, message: 'Benutzer nicht gefunden' }); - } - if (!user.email) { - return res.status(400).json({ success: false, message: 'Kein E-Mail für diesen Benutzer hinterlegt' }); - } - - const token = crypto.randomBytes(32).toString('hex'); - user.inviteToken = token; - user.inviteTokenExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days - await user.save(); - - res.json({ - success: true, - message: 'Einladungs-Token generiert (gültig 7 Tage)', - data: { inviteToken: token, expiresAt: user.inviteTokenExpiry, email: user.email } - }); - } catch (error) { - logger.error('Fehler beim Generieren des Einladungs-Tokens:', error); - res.status(500).json({ success: false, message: 'Serverfehler' }); - } -}; - -module.exports = { handlerLogin, setHandlerPassword, generateInviteToken, updateHandlerSelf, getHandlerSelf }; +module.exports = { handlerLogin, setHandlerPassword, updateHandlerSelf, getHandlerSelf }; diff --git a/drohnenfuehrer/backend/controllers/userController.js b/drohnenfuehrer/backend/controllers/userController.js index 6583d13..5db9505 100644 --- a/drohnenfuehrer/backend/controllers/userController.js +++ b/drohnenfuehrer/backend/controllers/userController.js @@ -425,7 +425,7 @@ const exportUsers = async (req, res) => { res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', - `attachment; filename="nachsuchenfuehrer_export_${new Date().toISOString().slice(0, 10)}.csv"`); + `attachment; filename="drohnenfuehrer_export_${new Date().toISOString().slice(0, 10)}.csv"`); return res.send('\uFEFF' + csv); // BOM für korrekte UTF-8-Darstellung in Excel } @@ -442,7 +442,7 @@ const exportUsers = async (req, res) => { })); res.setHeader('Content-Disposition', - `attachment; filename="nachsuchenfuehrer_export_${new Date().toISOString().slice(0, 10)}.json"`); + `attachment; filename="drohnenfuehrer_export_${new Date().toISOString().slice(0, 10)}.json"`); res.json({ exportedAt: new Date().toISOString(), count: exportData.length, diff --git a/drohnenfuehrer/backend/package.json b/drohnenfuehrer/backend/package.json index 5b83d10..6de9572 100644 --- a/drohnenfuehrer/backend/package.json +++ b/drohnenfuehrer/backend/package.json @@ -1,7 +1,7 @@ { - "name": "tracking-leaders-backend", + "name": "drohnenfuehrer-backend", "version": "1.0.0", - "description": "Backend for tracking leaders app", + "description": "Backend for drohnenfuehrer app", "main": "server.js", "scripts": { "start": "node server.js", diff --git a/drohnenfuehrer/backend/seed.js b/drohnenfuehrer/backend/seed.js index 11400ef..324282d 100644 --- a/drohnenfuehrer/backend/seed.js +++ b/drohnenfuehrer/backend/seed.js @@ -17,7 +17,7 @@ const seedDatabase = async () => { const existingUserCount = await User.countDocuments(); if (existingUserCount === 0) { await User.insertMany(users); - logger.info(`✅ ${users.length} Nachsuchenführer wurden erstellt`); + logger.info(`✅ ${users.length} Drohnenführer wurden erstellt`); } else { logger.info(`ℹ️ Benutzer bereits vorhanden (${existingUserCount}), überspringe Seeding`); } diff --git a/drohnenfuehrer/backend/utils/logger.js b/drohnenfuehrer/backend/utils/logger.js index 1a12f31..c8ef53f 100644 --- a/drohnenfuehrer/backend/utils/logger.js +++ b/drohnenfuehrer/backend/utils/logger.js @@ -19,7 +19,7 @@ const logger = winston.createLogger({ winston.format.splat(), winston.format.json() ), - defaultMeta: { service: 'tracking-leaders-api' }, + defaultMeta: { service: 'drohnenfuehrer-api' }, transports: [ rotateTransport('error', 'error'), rotateTransport('info', 'combined') diff --git a/drohnenfuehrer/docs/API.md b/drohnenfuehrer/docs/API.md index d542a1a..b00f49e 100644 --- a/drohnenfuehrer/docs/API.md +++ b/drohnenfuehrer/docs/API.md @@ -16,7 +16,7 @@ Authorization: Bearer ## Öffentliche Endpunkte ### GET /api/public/users -Ruft alle verfügbaren Nachsuchenführer ab. +Ruft alle verfügbaren Drohnenführer ab. **Response:** ```json @@ -69,7 +69,7 @@ Admin-Login. ## Geschützte Endpunkte (Admin) ### GET /api/users -Ruft alle Nachsuchenführer ab (auch nicht verfügbare). +Ruft alle Drohnenführer ab (auch nicht verfügbare). **Headers:** - `Authorization: Bearer ` @@ -83,7 +83,7 @@ Ruft alle Nachsuchenführer ab (auch nicht verfügbare). ``` ### GET /api/users/:id -Ruft einen einzelnen Nachsuchenführer ab. +Ruft einen einzelnen Drohnenführer ab. **Headers:** - `Authorization: Bearer ` @@ -97,7 +97,7 @@ Ruft einen einzelnen Nachsuchenführer ab. ``` ### POST /api/users -Erstellt einen neuen Nachsuchenführer. +Erstellt einen neuen Drohnenführer. **Headers:** - `Authorization: Bearer ` @@ -126,7 +126,7 @@ Erstellt einen neuen Nachsuchenführer. ``` ### PUT /api/users/:id -Aktualisiert einen Nachsuchenführer. +Aktualisiert einen Drohnenführer. **Headers:** - `Authorization: Bearer ` @@ -149,7 +149,7 @@ Aktualisiert einen Nachsuchenführer. ``` ### DELETE /api/users/:id -Löscht einen Nachsuchenführer. +Löscht einen Drohnenführer. **Headers:** - `Authorization: Bearer ` @@ -163,7 +163,7 @@ Löscht einen Nachsuchenführer. ``` ### PUT /api/users/:id/availability -Aktualisiert die Verfügbarkeit eines Nachsuchenführers. +Aktualisiert die Verfügbarkeit eines Drohnenführers. **Headers:** - `Authorization: Bearer ` @@ -184,7 +184,7 @@ Aktualisiert die Verfügbarkeit eines Nachsuchenführers. ``` ### PUT /api/users/:id/gps -Aktualisiert die GPS-Koordinaten eines Nachsuchenführers. +Aktualisiert die GPS-Koordinaten eines Drohnenführers. **Headers:** - `Authorization: Bearer ` @@ -206,7 +206,7 @@ Aktualisiert die GPS-Koordinaten eines Nachsuchenführers. ``` ### GET /api/users/export -Exportiert alle Nachsuchenführer. +Exportiert alle Drohnenführer. **Headers:** - `Authorization: Bearer ` diff --git a/drohnenfuehrer/docs/ARCHITECTURE.md b/drohnenfuehrer/docs/ARCHITECTURE.md index 7f4bbcb..a339503 100644 --- a/drohnenfuehrer/docs/ARCHITECTURE.md +++ b/drohnenfuehrer/docs/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## Übersicht -Die Nachsuchenführer-App besteht aus einem Node.js/Express-Backend und einem React-Frontend. +Die Drohnenführer-App besteht aus einem Node.js/Express-Backend und einem React-Frontend. ## Backend-Architektur @@ -23,7 +23,7 @@ backend/ │ └── validator.js # Input-Validierung ├── models/ │ ├── Admin.js # Admin-Modell -│ └── User.js # Nachsuchenführer-Modell +│ └── User.js # Drohnenführer-Modell ├── routes/ │ ├── authRoutes.js # Authentifizierungs-Routes │ └── userRoutes.js # Benutzer-Routes @@ -35,7 +35,7 @@ backend/ ### Datenmodell -#### User (Nachsuchenführer) +#### User (Drohnenführer) - `name`: String (erforderlich) - `address`: String (erforderlich) - `phone`: String (erforderlich) diff --git a/drohnenfuehrer/frontend/Dockerfile b/drohnenfuehrer/frontend/Dockerfile index 346fe8e..5cc5fca 100644 --- a/drohnenfuehrer/frontend/Dockerfile +++ b/drohnenfuehrer/frontend/Dockerfile @@ -8,7 +8,7 @@ RUN npm install COPY . . -ARG PUBLIC_URL=/nachsuche/ +ARG PUBLIC_URL=/drohnenfuehrer/ ENV PUBLIC_URL=$PUBLIC_URL ARG REACT_APP_API_URL= ENV REACT_APP_API_URL=$REACT_APP_API_URL diff --git a/drohnenfuehrer/frontend/package.json b/drohnenfuehrer/frontend/package.json index 68baba2..af7f839 100644 --- a/drohnenfuehrer/frontend/package.json +++ b/drohnenfuehrer/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "tracking-leaders-frontend", + "name": "drohnenfuehrer-frontend", "version": "0.1.0", "private": true, "dependencies": { diff --git a/drohnenfuehrer/frontend/public/index.html b/drohnenfuehrer/frontend/public/index.html index 28fb6f6..c493cdd 100644 --- a/drohnenfuehrer/frontend/public/index.html +++ b/drohnenfuehrer/frontend/public/index.html @@ -8,14 +8,14 @@ - + - NSS Heidekreis + Drohnenführer Heidekreis diff --git a/drohnenfuehrer/frontend/public/offline.html b/drohnenfuehrer/frontend/public/offline.html index 13728bd..c069be4 100644 --- a/drohnenfuehrer/frontend/public/offline.html +++ b/drohnenfuehrer/frontend/public/offline.html @@ -3,7 +3,7 @@ - Offline - Nachsuchenführer + Offline - Drohnenführer
-

Logo Jagd Apps Heidekreis

-

Wählen Sie Ihre App

+

Jagd Apps Heidekreis

+

Jägerschaft Fallingbostel e.V.

-
- -

Nachsuche

-

Nachsuchenstation Heidekreis – Übersicht der Nachsuchenführer und Verfügbarkeiten

+

Nachsuchenstation Heidekreis – Verfügbare Nachsuchenführer und Kontaktdaten

- +
+ -
- -

Drohnenführer

-

Drohnenführer Heidekreis – Übersicht der zertifizierten Drohnenführer im Einsatz

+

Drohnenführer Heidekreis – Zertifizierte Drohnenführer für den jagdlichen Einsatz

- +
+ -
- -

Stöberhunde

-

Stöberhunde Heidekreis – Übersicht der Stöberhundleiter und Einsatzmöglichkeiten

+

Stöberhunde Heidekreis – Stöberhundleiter und Verfügbarkeit für die Drückjagd

- +
Jägerschaft Fallingbostel e.V.
diff --git a/stoeberhunde/CONTAINER.md b/stoeberhunde/CONTAINER.md index 82d581b..7612d90 100644 --- a/stoeberhunde/CONTAINER.md +++ b/stoeberhunde/CONTAINER.md @@ -1,4 +1,4 @@ -# 🐳 Container-Betrieb - Tracking Leaders App +# 🐳 Container-Betrieb - Stöberhunde App ## Schnellstart mit Podman/Docker @@ -62,9 +62,9 @@ podman-compose logs -f frontend | Service | Container Name | Port | Beschreibung | |---------|---------------|------|--------------| -| Frontend | tracking-leaders-frontend | 8080 | React App mit Nginx | -| Backend | tracking-leaders-backend | 5000 | Node.js Express API | -| MongoDB | tracking-leaders-mongo | 27017 | MongoDB Datenbank | +| Frontend | stoeberhunde-frontend | 8080 | React App mit Nginx | +| Backend | stoeberhunde-backend | 5000 | Node.js Express API | +| MongoDB | stoeberhunde-mongo | 27017 | MongoDB Datenbank | ## Nützliche Befehle @@ -86,15 +86,15 @@ podman-compose down -v ### In Container einsteigen ```bash # Backend -podman exec -it tracking-leaders-backend sh +podman exec -it stoeberhunde-backend sh # MongoDB -podman exec -it tracking-leaders-mongo mongosh +podman exec -it stoeberhunde-mongo mongosh ``` ### Datenbank seeden (manuell) ```bash -podman exec -it tracking-leaders-backend node seed.js +podman exec -it stoeberhunde-backend node seed.js ``` ### Health Checks prüfen @@ -161,7 +161,7 @@ Für Production solltest du einen Reverse Proxy (z.B. Traefik, Nginx) vorschalte podman volume ls # Backup erstellen -podman run --rm -v tracking-leaders_mongo-data:/data -v $(pwd):/backup alpine tar czf /backup/mongo-backup-$(date +%Y%m%d).tar.gz -C /data . +podman run --rm -v stoeberhunde_mongo-data:/data -v $(pwd):/backup alpine tar czf /backup/mongo-backup-$(date +%Y%m%d).tar.gz -C /data . ``` ## Troubleshooting @@ -188,7 +188,7 @@ curl http://localhost:5000/health ### Datenbank leer ```bash # Seed-Skript ausführen -podman exec -it tracking-leaders-backend node seed.js +podman exec -it stoeberhunde-backend node seed.js ``` ### Port bereits belegt @@ -205,7 +205,7 @@ Beide Dockerfiles nutzen bereits Multi-Stage Builds für minimale Image-Größen ### Image-Größen prüfen ```bash -podman images | grep tracking-leaders +podman images | grep stoeberhunde ``` Erwartete Größen: @@ -245,7 +245,7 @@ podman stats # Alle Services for svc in backend frontend mongo; do echo "=== $svc ===" - podman inspect tracking-leaders-$svc | grep -A5 Health + podman inspect stoeberhunde-$svc | grep -A5 Health done ``` @@ -255,10 +255,10 @@ Wenn du von lokalem MongoDB zu Container wechselst: ```bash # 1. Export aus lokalem MongoDB -mongodump --db tracking-leaders --out ./dump +mongodump --db stoeberhunde --out ./dump # 2. Import in Container -podman exec -i tracking-leaders-mongo mongorestore --drop /dump +podman exec -i stoeberhunde-mongo mongorestore --drop /dump ``` ## Weitere Informationen diff --git a/stoeberhunde/backend/.env.example b/stoeberhunde/backend/.env.example index 01de079..10be23e 100644 --- a/stoeberhunde/backend/.env.example +++ b/stoeberhunde/backend/.env.example @@ -3,7 +3,7 @@ PORT=5000 NODE_ENV=development # Database Configuration -MONGO_URI=mongodb://127.0.0.1:27017/tracking-leaders +MONGO_URI=mongodb://127.0.0.1:27017/stoeberhunde # JWT Configuration JWT_SECRET=your-super-secret-jwt-key-change-this-in-production @@ -17,5 +17,5 @@ CORS_ORIGIN=http://localhost:3000 # Geocoding Configuration (OpenStreetMap Nominatim) GEOCODE_URL=https://nominatim.openstreetmap.org/search -GEOCODE_USER_AGENT=tracking-leaders-app/1.0 (admin@localhost) +GEOCODE_USER_AGENT=stoeberhunde-app/1.0 (admin@localhost) GEOCODE_MIN_DELAY_MS=1100 diff --git a/stoeberhunde/backend/config/env.js b/stoeberhunde/backend/config/env.js index c5825a0..18f65b8 100644 --- a/stoeberhunde/backend/config/env.js +++ b/stoeberhunde/backend/config/env.js @@ -2,13 +2,13 @@ require('dotenv').config(); const config = { port: process.env.PORT || 5000, - mongoUri: process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/tracking-leaders', + mongoUri: process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/stoeberhunde', jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production', jwtExpiresIn: process.env.JWT_EXPIRES_IN || '24h', nodeEnv: process.env.NODE_ENV || 'development', corsOrigin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5000'], geocodeUrl: process.env.GEOCODE_URL || 'https://nominatim.openstreetmap.org/search', - geocodeUserAgent: process.env.GEOCODE_USER_AGENT || 'tracking-leaders-app/1.0 (admin@localhost)', + geocodeUserAgent: process.env.GEOCODE_USER_AGENT || 'stoeberhunde-app/1.0 (admin@localhost)', geocodeMinDelayMs: parseInt(process.env.GEOCODE_MIN_DELAY_MS || '1100', 10) }; diff --git a/stoeberhunde/backend/controllers/authController.js b/stoeberhunde/backend/controllers/authController.js index d7b2ddc..640b942 100644 --- a/stoeberhunde/backend/controllers/authController.js +++ b/stoeberhunde/backend/controllers/authController.js @@ -45,15 +45,11 @@ const login = async (req, res) => { ); // Set token in httpOnly cookie (XSS protection) - const secureCookie = config.nodeEnv === 'production' - ? (req.secure || req.headers['x-forwarded-proto'] === 'https') - : false; - res.cookie('token', token, { - httpOnly: true, - secure: secureCookie, - sameSite: 'lax', - path: '/', + httpOnly: true, // Not accessible via JavaScript (XSS protection) + secure: false, // Allow over HTTP in development (localhost) + sameSite: 'lax', // Relaxed CSRF protection (allows cross-port cookies on localhost) + path: '/', // Ensure cookie is sent for all app routes, including /stoeberhunde/api maxAge: 24 * 60 * 60 * 1000 // 24 hours in milliseconds }); @@ -82,14 +78,10 @@ const logout = async (req, res) => { const username = req.user?.username || 'unknown'; await auditAuth(req, true, username, null); - const secureCookie = config.nodeEnv === 'production' - ? (req.secure || req.headers['x-forwarded-proto'] === 'https') - : false; - // Clear the token cookie res.clearCookie('token', { httpOnly: true, - secure: secureCookie, + secure: false, sameSite: 'lax', path: '/' }); @@ -148,11 +140,13 @@ const forgotPassword = async (req, res) => { }); // In production, send email here - logger.info(`Password reset token generiert für Benutzer: ${username}`); + // For development, log the token + logger.info(`Password reset token for ${username}: ${token} (expires in 1 hour)`); res.json({ success: true, - message: 'Reset-Token wurde generiert. Bitte kontaktieren Sie den Administrator.' + message: 'Reset-Token wurde generiert. In der Entwicklungsumgebung finden Sie den Token in den Logs.', + ...(config.nodeEnv === 'development' && { token }) // Only in dev! }); } catch (error) { logger.error('Fehler bei Passwort-Reset-Anfrage:', error); diff --git a/stoeberhunde/backend/controllers/handlerController.js b/stoeberhunde/backend/controllers/handlerController.js index e619540..18962d3 100644 --- a/stoeberhunde/backend/controllers/handlerController.js +++ b/stoeberhunde/backend/controllers/handlerController.js @@ -1,6 +1,5 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); -const crypto = require('crypto'); const User = require('../models/User'); const config = require('../config/env'); const logger = require('../utils/logger'); @@ -50,42 +49,22 @@ const handlerLogin = async (req, res) => { } }; -// POST /api/handler/set-password — set first password using a one-time invite token -// Admin must generate the invite token via POST /api/users/:id/invite-token first +// POST /api/handler/set-password — set first password (handler provides token + new password) +// Admin first sets email, then the handler can set their own password const setHandlerPassword = async (req, res) => { try { - const { email, inviteToken, newPassword } = req.body; + const { email, newPassword } = req.body; - if (!email || !inviteToken || !newPassword || newPassword.length < 8) { - return res.status(400).json({ success: false, message: 'E-Mail, Einladungs-Token und Passwort (min. 8 Zeichen) erforderlich' }); + if (!email || !newPassword || newPassword.length < 6) { + return res.status(400).json({ success: false, message: 'E-Mail und Passwort (min. 6 Zeichen) erforderlich' }); } - const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }) - .select('+inviteToken +inviteTokenExpiry'); - - // Always return the same error to prevent user enumeration - const invalidMsg = 'Ungültiger oder abgelaufener Einladungs-Token'; - if (!user || !user.inviteToken || !user.inviteTokenExpiry) { - return res.status(401).json({ success: false, message: invalidMsg }); + const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }); + if (!user) { + return res.status(404).json({ success: false, message: 'Kein Stöberhundeführer mit dieser E-Mail gefunden' }); } - if (user.inviteTokenExpiry < new Date()) { - user.inviteToken = null; - user.inviteTokenExpiry = null; - await user.save(); - return res.status(401).json({ success: false, message: invalidMsg }); - } - - // Timing-safe comparison to prevent timing attacks - const incoming = Buffer.from(inviteToken.trim()); - const stored = Buffer.from(user.inviteToken); - if (incoming.length !== stored.length || !crypto.timingSafeEqual(incoming, stored)) { - return res.status(401).json({ success: false, message: invalidMsg }); - } - - user.passwordHash = await bcrypt.hash(newPassword, 12); - user.inviteToken = null; - user.inviteTokenExpiry = null; + user.passwordHash = await bcrypt.hash(newPassword, 12); await user.save(); res.json({ success: true, message: 'Passwort erfolgreich gesetzt' }); @@ -114,7 +93,7 @@ const updateHandlerSelf = async (req, res) => { ); if (!user) { - return res.status(404).json({ success: false, message: 'Hundeführer nicht gefunden' }); + return res.status(404).json({ success: false, message: 'Stöberhundeführer nicht gefunden' }); } res.json({ success: true, data: user }); @@ -135,32 +114,4 @@ const getHandlerSelf = async (req, res) => { } }; -// POST /api/users/:id/invite-token (admin only) — generate a one-time invite token for a handler -const generateInviteToken = async (req, res) => { - try { - const user = await User.findOne({ _id: req.params.id, deleted: false }) - .select('+inviteToken +inviteTokenExpiry'); - if (!user) { - return res.status(404).json({ success: false, message: 'Benutzer nicht gefunden' }); - } - if (!user.email) { - return res.status(400).json({ success: false, message: 'Kein E-Mail für diesen Benutzer hinterlegt' }); - } - - const token = crypto.randomBytes(32).toString('hex'); - user.inviteToken = token; - user.inviteTokenExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days - await user.save(); - - res.json({ - success: true, - message: 'Einladungs-Token generiert (gültig 7 Tage)', - data: { inviteToken: token, expiresAt: user.inviteTokenExpiry, email: user.email } - }); - } catch (error) { - logger.error('Fehler beim Generieren des Einladungs-Tokens:', error); - res.status(500).json({ success: false, message: 'Serverfehler' }); - } -}; - -module.exports = { handlerLogin, setHandlerPassword, generateInviteToken, updateHandlerSelf, getHandlerSelf }; +module.exports = { handlerLogin, setHandlerPassword, updateHandlerSelf, getHandlerSelf }; diff --git a/stoeberhunde/backend/controllers/userController.js b/stoeberhunde/backend/controllers/userController.js index 6583d13..cc988b7 100644 --- a/stoeberhunde/backend/controllers/userController.js +++ b/stoeberhunde/backend/controllers/userController.js @@ -425,7 +425,7 @@ const exportUsers = async (req, res) => { res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', - `attachment; filename="nachsuchenfuehrer_export_${new Date().toISOString().slice(0, 10)}.csv"`); + `attachment; filename="stoeberhundefuehrer_export_${new Date().toISOString().slice(0, 10)}.csv"`); return res.send('\uFEFF' + csv); // BOM für korrekte UTF-8-Darstellung in Excel } @@ -442,7 +442,7 @@ const exportUsers = async (req, res) => { })); res.setHeader('Content-Disposition', - `attachment; filename="nachsuchenfuehrer_export_${new Date().toISOString().slice(0, 10)}.json"`); + `attachment; filename="stoeberhundefuehrer_export_${new Date().toISOString().slice(0, 10)}.json"`); res.json({ exportedAt: new Date().toISOString(), count: exportData.length, diff --git a/stoeberhunde/backend/package.json b/stoeberhunde/backend/package.json index 5b83d10..7305be0 100644 --- a/stoeberhunde/backend/package.json +++ b/stoeberhunde/backend/package.json @@ -1,5 +1,5 @@ { - "name": "tracking-leaders-backend", + "name": "stoeberhunde-backend", "version": "1.0.0", "description": "Backend for tracking leaders app", "main": "server.js", diff --git a/stoeberhunde/backend/seed.js b/stoeberhunde/backend/seed.js index 1ab3107..0631e47 100644 --- a/stoeberhunde/backend/seed.js +++ b/stoeberhunde/backend/seed.js @@ -17,7 +17,7 @@ const seedDatabase = async () => { const existingUserCount = await User.countDocuments(); if (existingUserCount === 0) { await User.insertMany(users); - logger.info(`✅ ${users.length} Nachsuchenführer wurden erstellt`); + logger.info(`✅ ${users.length} Stöberhundeführer wurden erstellt`); } else { logger.info(`ℹ️ Benutzer bereits vorhanden (${existingUserCount}), überspringe Seeding`); } diff --git a/stoeberhunde/backend/utils/logger.js b/stoeberhunde/backend/utils/logger.js index 1a12f31..ce9a013 100644 --- a/stoeberhunde/backend/utils/logger.js +++ b/stoeberhunde/backend/utils/logger.js @@ -19,7 +19,7 @@ const logger = winston.createLogger({ winston.format.splat(), winston.format.json() ), - defaultMeta: { service: 'tracking-leaders-api' }, + defaultMeta: { service: 'stoeberhunde-api' }, transports: [ rotateTransport('error', 'error'), rotateTransport('info', 'combined') diff --git a/stoeberhunde/docs/API.md b/stoeberhunde/docs/API.md index d542a1a..e2cb041 100644 --- a/stoeberhunde/docs/API.md +++ b/stoeberhunde/docs/API.md @@ -16,7 +16,7 @@ Authorization: Bearer ## Öffentliche Endpunkte ### GET /api/public/users -Ruft alle verfügbaren Nachsuchenführer ab. +Ruft alle verfügbaren Stöberhundeführer ab. **Response:** ```json @@ -69,7 +69,7 @@ Admin-Login. ## Geschützte Endpunkte (Admin) ### GET /api/users -Ruft alle Nachsuchenführer ab (auch nicht verfügbare). +Ruft alle Stöberhundeführer ab (auch nicht verfügbare). **Headers:** - `Authorization: Bearer ` @@ -83,7 +83,7 @@ Ruft alle Nachsuchenführer ab (auch nicht verfügbare). ``` ### GET /api/users/:id -Ruft einen einzelnen Nachsuchenführer ab. +Ruft einen einzelnen Stöberhundeführer ab. **Headers:** - `Authorization: Bearer ` @@ -97,7 +97,7 @@ Ruft einen einzelnen Nachsuchenführer ab. ``` ### POST /api/users -Erstellt einen neuen Nachsuchenführer. +Erstellt einen neuen Stöberhundeführer. **Headers:** - `Authorization: Bearer ` @@ -126,7 +126,7 @@ Erstellt einen neuen Nachsuchenführer. ``` ### PUT /api/users/:id -Aktualisiert einen Nachsuchenführer. +Aktualisiert einen Stöberhundeführer. **Headers:** - `Authorization: Bearer ` @@ -149,7 +149,7 @@ Aktualisiert einen Nachsuchenführer. ``` ### DELETE /api/users/:id -Löscht einen Nachsuchenführer. +Löscht einen Stöberhundeführer. **Headers:** - `Authorization: Bearer ` @@ -163,7 +163,7 @@ Löscht einen Nachsuchenführer. ``` ### PUT /api/users/:id/availability -Aktualisiert die Verfügbarkeit eines Nachsuchenführers. +Aktualisiert die Verfügbarkeit eines Stöberhundeführers. **Headers:** - `Authorization: Bearer ` @@ -184,7 +184,7 @@ Aktualisiert die Verfügbarkeit eines Nachsuchenführers. ``` ### PUT /api/users/:id/gps -Aktualisiert die GPS-Koordinaten eines Nachsuchenführers. +Aktualisiert die GPS-Koordinaten eines Stöberhundeführers. **Headers:** - `Authorization: Bearer ` @@ -206,7 +206,7 @@ Aktualisiert die GPS-Koordinaten eines Nachsuchenführers. ``` ### GET /api/users/export -Exportiert alle Nachsuchenführer. +Exportiert alle Stöberhundeführer. **Headers:** - `Authorization: Bearer ` diff --git a/stoeberhunde/docs/ARCHITECTURE.md b/stoeberhunde/docs/ARCHITECTURE.md index 7f4bbcb..bbd0e91 100644 --- a/stoeberhunde/docs/ARCHITECTURE.md +++ b/stoeberhunde/docs/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## Übersicht -Die Nachsuchenführer-App besteht aus einem Node.js/Express-Backend und einem React-Frontend. +Die Stöberhundeführer-App besteht aus einem Node.js/Express-Backend und einem React-Frontend. ## Backend-Architektur @@ -23,7 +23,7 @@ backend/ │ └── validator.js # Input-Validierung ├── models/ │ ├── Admin.js # Admin-Modell -│ └── User.js # Nachsuchenführer-Modell +│ └── User.js # Stöberhundeführer-Modell ├── routes/ │ ├── authRoutes.js # Authentifizierungs-Routes │ └── userRoutes.js # Benutzer-Routes @@ -35,7 +35,7 @@ backend/ ### Datenmodell -#### User (Nachsuchenführer) +#### User (Stöberhundeführer) - `name`: String (erforderlich) - `address`: String (erforderlich) - `phone`: String (erforderlich) diff --git a/stoeberhunde/frontend/Dockerfile b/stoeberhunde/frontend/Dockerfile index 346fe8e..46f5b5d 100644 --- a/stoeberhunde/frontend/Dockerfile +++ b/stoeberhunde/frontend/Dockerfile @@ -8,7 +8,7 @@ RUN npm install COPY . . -ARG PUBLIC_URL=/nachsuche/ +ARG PUBLIC_URL=/stoeberhunde/ ENV PUBLIC_URL=$PUBLIC_URL ARG REACT_APP_API_URL= ENV REACT_APP_API_URL=$REACT_APP_API_URL diff --git a/stoeberhunde/frontend/package.json b/stoeberhunde/frontend/package.json index 68baba2..50addbe 100644 --- a/stoeberhunde/frontend/package.json +++ b/stoeberhunde/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "tracking-leaders-frontend", + "name": "stoeberhunde-frontend", "version": "0.1.0", "private": true, "dependencies": { diff --git a/stoeberhunde/frontend/public/index.html b/stoeberhunde/frontend/public/index.html index 28fb6f6..3ce9f17 100644 --- a/stoeberhunde/frontend/public/index.html +++ b/stoeberhunde/frontend/public/index.html @@ -8,14 +8,14 @@ - + - NSS Heidekreis + Stöberhunde Heidekreis diff --git a/stoeberhunde/frontend/public/offline.html b/stoeberhunde/frontend/public/offline.html index 13728bd..6e5c44b 100644 --- a/stoeberhunde/frontend/public/offline.html +++ b/stoeberhunde/frontend/public/offline.html @@ -3,7 +3,7 @@ - Offline - Nachsuchenführer + Offline - Stöberhundeführer