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.
This commit is contained in:
Matthias Lotz 2025-11-30 11:40:59 +01:00
parent 489e2166bb
commit d76b4b2c9c
11 changed files with 261 additions and 62 deletions

View File

@ -10,10 +10,10 @@ Implementierung eines Telegram Bots zur automatischen Benachrichtigung der Werks
## Phasen-Aufteilung ## 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) **Ziel:** Telegram Bot erstellen und isoliert testen (ohne App-Integration)
**Status:** 🟡 In Planung **Status:** 🟢 Abgeschlossen
**Deliverables:** **Deliverables:**
- [x] Telegram Bot via BotFather erstellt - [x] Telegram Bot via BotFather erstellt
@ -32,52 +32,62 @@ Implementierung eines Telegram Bots zur automatischen Benachrichtigung der Werks
### Phase 2: Backend-Service Integration ### Phase 2: Backend-Service Integration
**Ziel:** TelegramNotificationService in Backend integrieren **Ziel:** TelegramNotificationService in Backend integrieren
**Status:** 🟢 Abgeschlossen
**Dependencies:** Phase 1 abgeschlossen **Dependencies:** Phase 1 abgeschlossen
**Deliverables:** **Deliverables:**
- [ ] `backend/src/services/TelegramNotificationService.js` - [x] `backend/src/services/TelegramNotificationService.js`
- [ ] ENV-Variablen in `docker/dev/backend/config/.env` - [x] ENV-Variablen in `docker/dev/backend/config/.env`
- [ ] Unit-Tests für Service - [x] Unit-Tests für Service
- [ ] Docker Dev Environment funktioniert - [x] Docker Dev Environment funktioniert
--- ---
### Phase 3: Upload-Benachrichtigungen ### Phase 3: Upload-Benachrichtigungen
**Ziel:** Automatische Benachrichtigungen bei neuem Upload **Ziel:** Automatische Benachrichtigungen bei neuem Upload
**Status:** 🟢 Abgeschlossen
**Dependencies:** Phase 2 abgeschlossen **Dependencies:** Phase 2 abgeschlossen
**Deliverables:** **Deliverables:**
- [ ] Integration in `routes/batchUpload.js` - [x] Integration in `routes/batchUpload.js`
- [ ] `sendUploadNotification()` Methode - [x] `sendUploadNotification()` Methode
- [ ] Formatierung mit Icons/Emojis - [x] Formatierung mit Icons/Emojis
- [ ] Integration-Tests - [x] Integration-Tests
--- ---
### Phase 4: User-Änderungs-Benachrichtigungen ### Phase 4: User-Änderungs-Benachrichtigungen
**Ziel:** Benachrichtigungen bei Consent-Änderungen & Löschungen **Ziel:** Benachrichtigungen bei Consent-Änderungen & Löschungen
**Status:** 🟢 Abgeschlossen
**Dependencies:** Phase 3 abgeschlossen **Dependencies:** Phase 3 abgeschlossen
**Deliverables:** **Deliverables:**
- [ ] Integration in `routes/management.js` (PUT/DELETE) - [x] Integration in `routes/management.js` (PUT/DELETE)
- [ ] `sendConsentChangeNotification()` Methode - [x] `sendConsentChangeNotification()` Methode
- [ ] `sendGroupDeletedNotification()` Methode - [x] `sendGroupDeletedNotification()` Methode
- [ ] Integration-Tests - [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 **Ziel:** Cron-Job für bevorstehende Löschungen
**Status:** 🟡 Dokumentation ausstehend
**Dependencies:** Phase 4 abgeschlossen **Dependencies:** Phase 4 abgeschlossen
**Deliverables:** **Deliverables:**
- [ ] Cron-Job Setup (node-cron) - [x] Cron-Job Setup (node-cron)
- [ ] `sendDeletionWarning()` Methode - [x] `sendDeletionWarning()` Methode
- [ ] Admin-Route für manuellen Trigger - [x] Admin-Route für manuellen Trigger (`POST /api/admin/telegram/warning`)
- [ ] Dokumentation - [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 ## Status-Tracking
**Letzte Aktualisierung:** 2025-11-29 **Letzte Aktualisierung:** 2025-11-30
| Phase | Status | Datum | | Phase | Status | Datum |
|-------|--------|-------| |-------|--------|-------|
| Phase 1 | 🟡 In Planung | 2025-11-29 | | Phase 1 | 🟢 Abgeschlossen | 2025-11-29 |
| Phase 2 | ⚪ Ausstehend | - | | Phase 2 | 🟢 Abgeschlossen | 2025-11-29 |
| Phase 3 | ⚪ Ausstehend | - | | Phase 3 | 🟢 Abgeschlossen | 2025-11-29 |
| Phase 4 | ⚪ Ausstehend | - | | Phase 4 | 🟢 Abgeschlossen | 2025-11-30 |
| Phase 5 | ⚪ Ausstehend | - | | Phase 5 | 🟡 Dokumentation | 2025-11-30 |
| Phase 6 | ⚪ Ausstehend | - | | Phase 6 | ⚪ Ausstehend | - |
**Legende:** **Legende:**

View File

@ -5,6 +5,7 @@ A self-hosted image uploader with multi-image upload capabilities and automatic
## Features ## Features
**Multi-Image Upload**: Upload multiple images at once with batch processing **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 **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 **Automatic Cleanup**: 🆕 Unapproved groups are automatically deleted after 7 days
**Deletion Log**: 🆕 Complete audit trail of automatically deleted content **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) ### 🆕 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): - **🌐 Public/Internal Host Separation** (Nov 25):
- Subdomain-based feature separation for production deployment - Subdomain-based feature separation for production deployment
- Public host (`deinprojekt.hobbyhimmel.de`): Upload + UUID Management only - 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 | | `API_URL` | `http://localhost:5001` | Backend API endpoint |
| `CLIENT_URL` | `http://localhost` | Frontend application URL | | `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 ### Volume Configuration
- **Upload Limits**: 100MB maximum file size for batch uploads - **Upload Limits**: 100MB maximum file size for batch uploads

View File

@ -1070,22 +1070,38 @@
}, },
"/api/manage/{token}/reorder": { "/api/manage/{token}/reorder": {
"put": { "put": {
"description": "", "tags": [
"Management Portal"
],
"summary": "Reorder images in group",
"description": "Reorder images within the managed group (token-based access)",
"parameters": [ "parameters": [
{ {
"name": "token", "name": "token",
"in": "path", "in": "path",
"required": true, "required": true,
"type": "string" "type": "string",
"description": "Management token (UUID v4)",
"example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}, },
{ {
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"imageIds": { "imageIds": {
"example": "any" "type": "array",
"example": [
1,
3,
2,
4
],
"items": {
"type": "number"
}
} }
} }
} }
@ -1093,13 +1109,29 @@
], ],
"responses": { "responses": {
"200": { "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": { "400": {
"description": "Bad Request" "description": "Invalid token format or imageIds"
}, },
"404": { "404": {
"description": "Not Found" "description": "Token not found or group deleted"
}, },
"429": { "429": {
"description": "Too Many Requests" "description": "Too Many Requests"
@ -1163,25 +1195,46 @@
}, },
"/api/admin/groups/{groupId}/consents": { "/api/admin/groups/{groupId}/consents": {
"post": { "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": [ "parameters": [
{ {
"name": "groupId", "name": "groupId",
"in": "path", "in": "path",
"required": true, "required": true,
"type": "string" "type": "string",
"description": "Group ID",
"example": "abc123def456"
}, },
{ {
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"workshopConsent": { "workshopConsent": {
"example": "any" "type": "boolean",
"example": true
}, },
"socialMediaConsents": { "socialMediaConsents": {
"example": "any" "type": "array",
"items": {
"type": "object",
"properties": {
"platformId": {
"type": "number",
"example": 2
},
"consented": {
"type": "boolean",
"example": false
}
}
}
} }
} }
} }
@ -1189,10 +1242,26 @@
], ],
"responses": { "responses": {
"200": { "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": { "400": {
"description": "Bad Request" "description": "Invalid request data"
}, },
"403": { "403": {
"description": "Forbidden" "description": "Forbidden"

View File

@ -58,16 +58,37 @@ router.get('/social-media/platforms', async (req, res) => {
// Group Consents // 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) => { 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 { try {
const { groupId } = req.params; const { groupId } = req.params;
const { workshopConsent, socialMediaConsents } = req.body; const { workshopConsent, socialMediaConsents } = req.body;

View File

@ -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) => { 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 { try {
const { token } = req.params; const { token } = req.params;
const { imageIds } = req.body; const { imageIds } = req.body;

View File

@ -88,8 +88,10 @@ class Server {
// Starte Scheduler für automatisches Cleanup // Starte Scheduler für automatisches Cleanup
SchedulerService.start(); SchedulerService.start();
// Teste Telegram-Service (nur in Development) // Teste Telegram-Service (optional, nur in Development wenn aktiviert)
if (process.env.NODE_ENV === 'development' && telegramService.isAvailable()) { if (process.env.NODE_ENV === 'development'
&& process.env.TELEGRAM_SEND_TEST_ON_START === 'true'
&& telegramService.isAvailable()) {
telegramService.sendTestMessage() telegramService.sendTestMessage()
.catch(err => console.error('[Telegram] Test message failed:', err.message)); .catch(err => console.error('[Telegram] Test message failed:', err.message));
} }

View File

@ -10,6 +10,22 @@ NODE_ENV=development
# Port for the backend server # Port for the backend server
PORT=5000 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<TOKEN>/getUpdates
# Example: -1001234567890
TELEGRAM_CHAT_ID=your-chat-id-here
# Database settings (if needed in future) # Database settings (if needed in future)
# DB_HOST=localhost # DB_HOST=localhost
# DB_PORT=3306 # DB_PORT=3306

13
docker/dev/.env.example Normal file
View File

@ -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<TOKEN>/getUpdates
# Example: -1001234567890
TELEGRAM_CHAT_ID=your-chat-id-here

View File

@ -46,9 +46,10 @@ services:
- ENABLE_HOST_RESTRICTION=true - ENABLE_HOST_RESTRICTION=true
- TRUST_PROXY_HOPS=0 - TRUST_PROXY_HOPS=0
- PUBLIC_UPLOAD_RATE_LIMIT=20 - PUBLIC_UPLOAD_RATE_LIMIT=20
- TELEGRAM_ENABLED=true - TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false}
- TELEGRAM_BOT_TOKEN=8496813818:AAHShJP_cqOuWIW66ROUCxa-1C83CCCZmqU - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=-1003064825033 - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
- TELEGRAM_SEND_TEST_ON_START=${TELEGRAM_SEND_TEST_ON_START:-false}
networks: networks:
- dev-internal - dev-internal
command: [ "npm", "run", "server" ] command: [ "npm", "run", "server" ]

17
docker/prod/.env.example Normal file
View File

@ -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<TOKEN>/getUpdates
# Example: -1001234567890
TELEGRAM_CHAT_ID=your-chat-id-here

View File

@ -39,6 +39,8 @@ changed = False
cookie_pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)') 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]+)') 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): def ensure_entry(text, *, pattern, value, anchor_line, expected_line, label):
match = pattern.search(text) 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) print('ERROR: Failed to ensure ADMIN_SESSION_SECRET uses environment variable in docker-compose.yml', file=sys.stderr)
sys.exit(4) 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: if changed:
path.write_text(new_text) path.write_text(new_text)
print('UPDATED') print('UPDATED')