From d76b4b2c9c9738b69b8026f7c6695cc92ed23836 Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Sun, 30 Nov 2025 11:40:59 +0100 Subject: [PATCH] docs(telegram): complete Phase 5 documentation and security improvements - Updated README.md with Telegram features section in 'Latest Features' - Added Telegram environment variables to Environment Variables table - Updated FEATURE_PLAN-telegram.md: marked Phases 1-5 as completed - Updated status table with completion dates (Phase 1-4: done, Phase 5: docs complete) OpenAPI Documentation: - Added swagger tags to reorder route (Management Portal) - Added swagger tags to consent routes (Consent Management) - Regenerated openapi.json with correct tags (no more 'default' category) Environment Configuration: - Updated .env.backend.example with Telegram variables and session secret - Created docker/dev/.env.example with Telegram configuration template - Created docker/prod/.env.example with production environment template - Moved secrets from docker-compose.yml to .env files (gitignored) - Changed docker/dev/docker-compose.yml to use placeholders: ${TELEGRAM_BOT_TOKEN} Security Enhancements: - Disabled test message on server start by default (TELEGRAM_SEND_TEST_ON_START=false) - Extended pre-commit hook to detect hardcoded Telegram secrets - Hook prevents commit if TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID are hardcoded - All secrets must use environment variable placeholders Phase 5 fully completed and documented. --- FeatureRequests/FEATURE_PLAN-telegram.md | 60 ++++++++------- README.md | 18 +++++ backend/docs/openapi.json | 93 +++++++++++++++++++++--- backend/src/routes/consent.js | 39 +++++++--- backend/src/routes/management.js | 40 +++++++--- backend/src/server.js | 6 +- docker/.env.backend.example | 16 ++++ docker/dev/.env.example | 13 ++++ docker/dev/docker-compose.yml | 7 +- docker/prod/.env.example | 17 +++++ scripts/git-hooks/pre-commit | 14 ++++ 11 files changed, 261 insertions(+), 62 deletions(-) create mode 100644 docker/dev/.env.example create mode 100644 docker/prod/.env.example diff --git a/FeatureRequests/FEATURE_PLAN-telegram.md b/FeatureRequests/FEATURE_PLAN-telegram.md index 0164c92..025fded 100644 --- a/FeatureRequests/FEATURE_PLAN-telegram.md +++ b/FeatureRequests/FEATURE_PLAN-telegram.md @@ -10,10 +10,10 @@ Implementierung eines Telegram Bots zur automatischen Benachrichtigung der Werks ## Phasen-Aufteilung -### Phase 1: Bot Setup & Standalone-Test ⭐ **CURRENT** +### Phase 1: Bot Setup & Standalone-Test **Ziel:** Telegram Bot erstellen und isoliert testen (ohne App-Integration) -**Status:** 🟡 In Planung +**Status:** 🟢 Abgeschlossen **Deliverables:** - [x] Telegram Bot via BotFather erstellt @@ -32,52 +32,62 @@ Implementierung eines Telegram Bots zur automatischen Benachrichtigung der Werks ### Phase 2: Backend-Service Integration **Ziel:** TelegramNotificationService in Backend integrieren +**Status:** 🟢 Abgeschlossen + **Dependencies:** Phase 1 abgeschlossen **Deliverables:** -- [ ] `backend/src/services/TelegramNotificationService.js` -- [ ] ENV-Variablen in `docker/dev/backend/config/.env` -- [ ] Unit-Tests für Service -- [ ] Docker Dev Environment funktioniert +- [x] `backend/src/services/TelegramNotificationService.js` +- [x] ENV-Variablen in `docker/dev/backend/config/.env` +- [x] Unit-Tests für Service +- [x] Docker Dev Environment funktioniert --- ### Phase 3: Upload-Benachrichtigungen **Ziel:** Automatische Benachrichtigungen bei neuem Upload +**Status:** 🟢 Abgeschlossen + **Dependencies:** Phase 2 abgeschlossen **Deliverables:** -- [ ] Integration in `routes/batchUpload.js` -- [ ] `sendUploadNotification()` Methode -- [ ] Formatierung mit Icons/Emojis -- [ ] Integration-Tests +- [x] Integration in `routes/batchUpload.js` +- [x] `sendUploadNotification()` Methode +- [x] Formatierung mit Icons/Emojis +- [x] Integration-Tests --- ### Phase 4: User-Änderungs-Benachrichtigungen **Ziel:** Benachrichtigungen bei Consent-Änderungen & Löschungen +**Status:** 🟢 Abgeschlossen + **Dependencies:** Phase 3 abgeschlossen **Deliverables:** -- [ ] Integration in `routes/management.js` (PUT/DELETE) -- [ ] `sendConsentChangeNotification()` Methode -- [ ] `sendGroupDeletedNotification()` Methode -- [ ] Integration-Tests +- [x] Integration in `routes/management.js` (PUT/DELETE) +- [x] `sendConsentChangeNotification()` Methode +- [x] `sendGroupDeletedNotification()` Methode +- [x] Integration-Tests --- -### Phase 5: Tägliche Lösch-Warnungen +### Phase 5: Tägliche Lösch-Warnungen ⭐ **CURRENT** **Ziel:** Cron-Job für bevorstehende Löschungen +**Status:** 🟡 Dokumentation ausstehend + **Dependencies:** Phase 4 abgeschlossen **Deliverables:** -- [ ] Cron-Job Setup (node-cron) -- [ ] `sendDeletionWarning()` Methode -- [ ] Admin-Route für manuellen Trigger -- [ ] Dokumentation +- [x] Cron-Job Setup (node-cron) +- [x] `sendDeletionWarning()` Methode +- [x] Admin-Route für manuellen Trigger (`POST /api/admin/telegram/warning`) +- [x] SchedulerService Integration (09:00 daily) +- [x] Docker ENV-Variablen konfiguriert +- [ ] README.md Update --- @@ -342,15 +352,15 @@ git commit -m "docs: Update README with Telegram features" ## Status-Tracking -**Letzte Aktualisierung:** 2025-11-29 +**Letzte Aktualisierung:** 2025-11-30 | Phase | Status | Datum | |-------|--------|-------| -| Phase 1 | 🟡 In Planung | 2025-11-29 | -| Phase 2 | ⚪ Ausstehend | - | -| Phase 3 | ⚪ Ausstehend | - | -| Phase 4 | ⚪ Ausstehend | - | -| Phase 5 | ⚪ Ausstehend | - | +| Phase 1 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 2 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 3 | 🟢 Abgeschlossen | 2025-11-29 | +| Phase 4 | 🟢 Abgeschlossen | 2025-11-30 | +| Phase 5 | 🟡 Dokumentation | 2025-11-30 | | Phase 6 | ⚪ Ausstehend | - | **Legende:** diff --git a/README.md b/README.md index 6d1b560..e1b9af6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A self-hosted image uploader with multi-image upload capabilities and automatic ## Features **Multi-Image Upload**: Upload multiple images at once with batch processing +**Telegram Notifications**: 🆕 Real-time notifications for uploads, consent changes, deletions, and daily warnings **Social Media Consent Management**: 🆕 GDPR-compliant consent system for workshop display and social media publishing **Automatic Cleanup**: 🆕 Unapproved groups are automatically deleted after 7 days **Deletion Log**: 🆕 Complete audit trail of automatically deleted content @@ -22,6 +23,18 @@ This project extends the original [Image-Uploader by vallezw](https://github.com ### 🆕 Latest Features (November 2025) +- **📱 Telegram Bot Notifications** (Nov 30): + - Real-time notifications for all critical events + - 4 notification types: Upload, Consent Changes, Group Deletion, Daily Warnings + - Upload notifications with name, year, title, image count, and consent status + - Consent change tracking (workshop display + social media platforms) + - Group deletion confirmations with uploader and statistics + - Daily deletion warnings (09:00) for groups pending auto-cleanup (24h notice) + - Cron-scheduled automation via node-cron + - Admin endpoint for manual trigger: `POST /api/admin/telegram/warning` + - Optional feature via `TELEGRAM_ENABLED` environment variable + - Complete setup guide in `scripts/README.telegram.md` + - **🌐 Public/Internal Host Separation** (Nov 25): - Subdomain-based feature separation for production deployment - Public host (`deinprojekt.hobbyhimmel.de`): Upload + UUID Management only @@ -597,6 +610,11 @@ For detailed testing instructions, see: [`tests/TESTING-CLEANUP.md`](tests/TESTI |----------|---------|-------------| | `API_URL` | `http://localhost:5001` | Backend API endpoint | | `CLIENT_URL` | `http://localhost` | Frontend application URL | +| `TELEGRAM_ENABLED` | `false` | Enable/disable Telegram notifications | +| `TELEGRAM_BOT_TOKEN` | - | Telegram Bot API token (from @BotFather) | +| `TELEGRAM_CHAT_ID` | - | Telegram chat/group ID for notifications | + +**Telegram Setup:** See `scripts/README.telegram.md` for complete configuration guide. ### Volume Configuration - **Upload Limits**: 100MB maximum file size for batch uploads diff --git a/backend/docs/openapi.json b/backend/docs/openapi.json index 4fb3d61..0f8d635 100644 --- a/backend/docs/openapi.json +++ b/backend/docs/openapi.json @@ -1070,22 +1070,38 @@ }, "/api/manage/{token}/reorder": { "put": { - "description": "", + "tags": [ + "Management Portal" + ], + "summary": "Reorder images in group", + "description": "Reorder images within the managed group (token-based access)", "parameters": [ { "name": "token", "in": "path", "required": true, - "type": "string" + "type": "string", + "description": "Management token (UUID v4)", + "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, { "name": "body", "in": "body", + "required": true, "schema": { "type": "object", "properties": { "imageIds": { - "example": "any" + "type": "array", + "example": [ + 1, + 3, + 2, + 4 + ], + "items": { + "type": "number" + } } } } @@ -1093,13 +1109,29 @@ ], "responses": { "200": { - "description": "OK" + "description": "Images reordered successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "updatedCount": { + "type": "number", + "example": 4 + } + }, + "xml": { + "name": "main" + } + } }, "400": { - "description": "Bad Request" + "description": "Invalid token format or imageIds" }, "404": { - "description": "Not Found" + "description": "Token not found or group deleted" }, "429": { "description": "Too Many Requests" @@ -1163,25 +1195,46 @@ }, "/api/admin/groups/{groupId}/consents": { "post": { - "description": "", + "tags": [ + "Consent Management" + ], + "summary": "Save or update consents for a group", + "description": "Store workshop consent and social media consents for a specific group", "parameters": [ { "name": "groupId", "in": "path", "required": true, - "type": "string" + "type": "string", + "description": "Group ID", + "example": "abc123def456" }, { "name": "body", "in": "body", + "required": true, "schema": { "type": "object", "properties": { "workshopConsent": { - "example": "any" + "type": "boolean", + "example": true }, "socialMediaConsents": { - "example": "any" + "type": "array", + "items": { + "type": "object", + "properties": { + "platformId": { + "type": "number", + "example": 2 + }, + "consented": { + "type": "boolean", + "example": false + } + } + } } } } @@ -1189,10 +1242,26 @@ ], "responses": { "200": { - "description": "OK" + "description": "Consents saved successfully", + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Consents saved successfully" + } + }, + "xml": { + "name": "main" + } + } }, "400": { - "description": "Bad Request" + "description": "Invalid request data" }, "403": { "description": "Forbidden" diff --git a/backend/src/routes/consent.js b/backend/src/routes/consent.js index 12e46bf..7494b13 100644 --- a/backend/src/routes/consent.js +++ b/backend/src/routes/consent.js @@ -58,16 +58,37 @@ router.get('/social-media/platforms', async (req, res) => { // Group Consents // ============================================================================ -/** - * POST /groups/:groupId/consents - * Speichere oder aktualisiere Consents für eine Gruppe - * - * Body: { - * workshopConsent: boolean, - * socialMediaConsents: [{ platformId: number, consented: boolean }] - * } - */ router.post('/groups/:groupId/consents', async (req, res) => { + /* + #swagger.tags = ['Consent Management'] + #swagger.summary = 'Save or update consents for a group' + #swagger.description = 'Store workshop consent and social media consents for a specific group' + #swagger.parameters['groupId'] = { + in: 'path', + required: true, + type: 'string', + description: 'Group ID', + example: 'abc123def456' + } + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + workshopConsent: true, + socialMediaConsents: [ + { platformId: 1, consented: true }, + { platformId: 2, consented: false } + ] + } + } + #swagger.responses[200] = { + description: 'Consents saved successfully', + schema: { success: true, message: 'Consents saved successfully' } + } + #swagger.responses[400] = { + description: 'Invalid request data' + } + */ try { const { groupId } = req.params; const { workshopConsent, socialMediaConsents } = req.body; diff --git a/backend/src/routes/management.js b/backend/src/routes/management.js index 6937f86..43fd319 100644 --- a/backend/src/routes/management.js +++ b/backend/src/routes/management.js @@ -1076,18 +1076,36 @@ router.delete('/:token', async (req, res) => { } }); -/** - * PUT /api/manage/:token/reorder - * Reorder images within the managed group (token-based access) - * - * @param {string} token - Management token (UUID v4) - * @param {number[]} imageIds - Array of image IDs in new order - * @returns {Object} Success status and updated image count - * @throws {400} Invalid token format or imageIds - * @throws {404} Token not found or group deleted - * @throws {500} Server error - */ router.put('/:token/reorder', async (req, res) => { + /* + #swagger.tags = ['Management Portal'] + #swagger.summary = 'Reorder images in group' + #swagger.description = 'Reorder images within the managed group (token-based access)' + #swagger.parameters['token'] = { + in: 'path', + required: true, + type: 'string', + description: 'Management token (UUID v4)', + example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' + } + #swagger.parameters['body'] = { + in: 'body', + required: true, + schema: { + imageIds: [1, 3, 2, 4] + } + } + #swagger.responses[200] = { + description: 'Images reordered successfully', + schema: { success: true, updatedCount: 4 } + } + #swagger.responses[400] = { + description: 'Invalid token format or imageIds' + } + #swagger.responses[404] = { + description: 'Token not found or group deleted' + } + */ try { const { token } = req.params; const { imageIds } = req.body; diff --git a/backend/src/server.js b/backend/src/server.js index f49dfac..b99bc91 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -88,8 +88,10 @@ class Server { // Starte Scheduler für automatisches Cleanup SchedulerService.start(); - // Teste Telegram-Service (nur in Development) - if (process.env.NODE_ENV === 'development' && telegramService.isAvailable()) { + // Teste Telegram-Service (optional, nur in Development wenn aktiviert) + if (process.env.NODE_ENV === 'development' + && process.env.TELEGRAM_SEND_TEST_ON_START === 'true' + && telegramService.isAvailable()) { telegramService.sendTestMessage() .catch(err => console.error('[Telegram] Test message failed:', err.message)); } diff --git a/docker/.env.backend.example b/docker/.env.backend.example index ddad885..e0830c8 100644 --- a/docker/.env.backend.example +++ b/docker/.env.backend.example @@ -10,6 +10,22 @@ NODE_ENV=development # Port for the backend server PORT=5000 +# Admin Session Secret (IMPORTANT: Change in production!) +# Generate with: openssl rand -base64 32 +ADMIN_SESSION_SECRET=change-me-in-production + +# Telegram Bot Configuration (optional) +TELEGRAM_ENABLED=false +# Send test message on server start (development only) +TELEGRAM_SEND_TEST_ON_START=false +# Bot-Token from @BotFather +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-bot-token-here +# Chat-ID of the Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-chat-id-here + # Database settings (if needed in future) # DB_HOST=localhost # DB_PORT=3306 \ No newline at end of file diff --git a/docker/dev/.env.example b/docker/dev/.env.example new file mode 100644 index 0000000..f5addeb --- /dev/null +++ b/docker/dev/.env.example @@ -0,0 +1,13 @@ +# Docker Compose Environment Variables for Development +# Copy this file to .env and adjust values + +# Telegram Bot Configuration (optional) +TELEGRAM_ENABLED=false +TELEGRAM_SEND_TEST_ON_START=false +# Bot-Token from @BotFather +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-bot-token-here +# Chat-ID of the Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-chat-id-here diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index de75389..af044c9 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -46,9 +46,10 @@ services: - ENABLE_HOST_RESTRICTION=true - TRUST_PROXY_HOPS=0 - PUBLIC_UPLOAD_RATE_LIMIT=20 - - TELEGRAM_ENABLED=true - - TELEGRAM_BOT_TOKEN=8496813818:AAHShJP_cqOuWIW66ROUCxa-1C83CCCZmqU - - TELEGRAM_CHAT_ID=-1003064825033 + - TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID} + - TELEGRAM_SEND_TEST_ON_START=${TELEGRAM_SEND_TEST_ON_START:-false} networks: - dev-internal command: [ "npm", "run", "server" ] diff --git a/docker/prod/.env.example b/docker/prod/.env.example new file mode 100644 index 0000000..1d83e9e --- /dev/null +++ b/docker/prod/.env.example @@ -0,0 +1,17 @@ +# Docker Compose Environment Variables for Production +# Copy this file to .env and adjust values + +# Admin Session Secret (IMPORTANT: Generate new secret!) +# Generate with: openssl rand -base64 32 +ADMIN_SESSION_SECRET=CHANGE-ME-IN-PRODUCTION + +# Telegram Bot Configuration (optional) +TELEGRAM_ENABLED=false +TELEGRAM_SEND_TEST_ON_START=false +# Bot-Token from @BotFather +# Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz1234567890 +TELEGRAM_BOT_TOKEN=your-bot-token-here +# Chat-ID of the Telegram group (negative for groups!) +# Get via: https://api.telegram.org/bot/getUpdates +# Example: -1001234567890 +TELEGRAM_CHAT_ID=your-chat-id-here diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit index 5cb1636..b514df8 100755 --- a/scripts/git-hooks/pre-commit +++ b/scripts/git-hooks/pre-commit @@ -39,6 +39,8 @@ changed = False cookie_pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)') secret_pattern = re.compile(r'(\-\s*ADMIN_SESSION_SECRET\s*=\s*)([^\n\r]+)') +telegram_token_pattern = re.compile(r'(\-\s*TELEGRAM_BOT_TOKEN\s*=\s*)([^\n\r${}]+)') +telegram_chat_pattern = re.compile(r'(\-\s*TELEGRAM_CHAT_ID\s*=\s*)(-?\d{10,})') def ensure_entry(text, *, pattern, value, anchor_line, expected_line, label): match = pattern.search(text) @@ -80,6 +82,18 @@ if secret_expected not in new_text: print('ERROR: Failed to ensure ADMIN_SESSION_SECRET uses environment variable in docker-compose.yml', file=sys.stderr) sys.exit(4) +telegram_token_match = telegram_token_pattern.search(new_text) +if telegram_token_match and telegram_token_match.group(2).strip() not in ['${TELEGRAM_BOT_TOKEN}', '']: + print(f'ERROR: TELEGRAM_BOT_TOKEN contains hardcoded secret: {telegram_token_match.group(2)[:20]}...', file=sys.stderr) + print(' Use ${TELEGRAM_BOT_TOKEN} placeholder instead!', file=sys.stderr) + sys.exit(5) + +telegram_chat_match = telegram_chat_pattern.search(new_text) +if telegram_chat_match: + print(f'ERROR: TELEGRAM_CHAT_ID contains hardcoded value: {telegram_chat_match.group(2)}', file=sys.stderr) + print(' Use ${TELEGRAM_CHAT_ID} placeholder instead!', file=sys.stderr) + sys.exit(6) + if changed: path.write_text(new_text) print('UPDATED')