import { Response } from 'express'; import { AuthRequest } from '../types/index.js'; import * as gdprService from '../services/gdpr.service.js'; import * as consentService from '../services/consent.service.js'; import * as consentPublicService from '../services/consent-public.service.js'; import * as appSettingService from '../services/appSetting.service.js'; import { canAccessCustomer } from '../utils/accessControl.js'; import { createAuditLog, logChange } from '../services/audit.service.js'; import { ConsentType, DeletionRequestStatus } from '@prisma/client'; import prisma from '../lib/prisma.js'; import path from 'path'; import fs from 'fs'; import { sendEmail, SmtpCredentials } from '../services/smtpService.js'; import { getSystemEmailCredentials } from '../services/emailProvider/emailProviderService.js'; import * as authorizationService from '../services/authorization.service.js'; import { stripHtml } from '../utils/sanitize.js'; /** * Kundendaten exportieren (DSGVO Art. 15) */ export async function exportCustomerData(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const format = (req.query.format as string) || 'json'; const data = await gdprService.exportCustomerData(customerId); // Audit-Log für Export await createAuditLog({ userId: req.user?.userId, userEmail: req.user?.email || 'unknown', action: 'EXPORT', resourceType: 'GDPR', resourceId: customerId.toString(), resourceLabel: `Datenexport für ${data.dataSubject.name}`, endpoint: req.path, httpMethod: req.method, ipAddress: req.socket.remoteAddress || 'unknown', dataSubjectId: customerId, legalBasis: 'DSGVO Art. 15', }); if (format === 'json') { res.setHeader('Content-Type', 'application/json'); res.setHeader( 'Content-Disposition', `attachment; filename="datenexport_${data.dataSubject.customerNumber}_${new Date().toISOString().split('T')[0]}.json"` ); res.json(data); } else { // Für PDF würde hier PDFKit verwendet werden res.json({ success: true, data }); } } catch (error) { console.error('Fehler beim Datenexport:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Datenexport', }); } } /** * Löschanfrage erstellen */ export async function createDeletionRequest(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.id); const { requestSource } = req.body; const request = await gdprService.createDeletionRequest({ customerId, requestSource: requestSource || 'portal', requestedBy: req.user?.email || 'unknown', }); await logChange({ req, action: 'CREATE', resourceType: 'DeletionRequest', resourceId: request.id.toString(), label: `Löschanfrage erstellt für Kunde #${customerId}`, customerId, }); res.status(201).json({ success: true, data: request }); } catch (error) { console.error('Fehler beim Erstellen der Löschanfrage:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen', }); } } /** * Alle Löschanfragen abrufen */ export async function getDeletionRequests(req: AuthRequest, res: Response) { try { const { status, page, limit } = req.query; const result = await gdprService.getDeletionRequests({ status: status as DeletionRequestStatus | undefined, page: page ? parseInt(page as string) : 1, limit: limit ? parseInt(limit as string) : 20, }); res.json({ success: true, ...result }); } catch (error) { console.error('Fehler beim Abrufen der Löschanfragen:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Einzelne Löschanfrage abrufen */ export async function getDeletionRequest(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const request = await gdprService.getDeletionRequest(id); if (!request) { return res.status(404).json({ success: false, error: 'Löschanfrage nicht gefunden' }); } res.json({ success: true, data: request }); } catch (error) { console.error('Fehler beim Abrufen der Löschanfrage:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Löschanfrage bearbeiten */ export async function processDeletionRequest(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const { action, retentionReason } = req.body; if (!['complete', 'partial', 'reject'].includes(action)) { return res.status(400).json({ success: false, error: 'Ungültige Aktion. Erlaubt: complete, partial, reject', }); } const result = await gdprService.processDeletionRequest(id, { processedBy: req.user?.email || 'unknown', action, retentionReason, }); // Audit-Log für Löschung await createAuditLog({ userId: req.user?.userId, userEmail: req.user?.email || 'unknown', action: 'ANONYMIZE', resourceType: 'GDPR', resourceId: id.toString(), resourceLabel: `Löschanfrage ${action}`, endpoint: req.path, httpMethod: req.method, ipAddress: req.socket.remoteAddress || 'unknown', dataSubjectId: result.customerId, legalBasis: 'DSGVO Art. 17', }); res.json({ success: true, data: result }); } catch (error) { console.error('Fehler beim Bearbeiten der Löschanfrage:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Bearbeiten', }); } } /** * Löschnachweis-PDF herunterladen */ export async function getDeletionProof(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const request = await gdprService.getDeletionRequest(id); if (!request) { return res.status(404).json({ success: false, error: 'Löschanfrage nicht gefunden' }); } if (!request.proofDocument) { return res.status(404).json({ success: false, error: 'Kein Löschnachweis vorhanden' }); } // Path-Traversal-Schutz: proofDocument aus der DB darf nur unter uploads/ liegen const uploadsDir = path.resolve(process.cwd(), 'uploads'); const filepath = path.resolve(uploadsDir, request.proofDocument); if (!filepath.startsWith(uploadsDir + path.sep)) { return res.status(400).json({ success: false, error: 'Ungültiger Dateipfad' }); } if (!fs.existsSync(filepath)) { return res.status(404).json({ success: false, error: 'Datei nicht gefunden' }); } res.download(filepath); } catch (error) { console.error('Fehler beim Download des Löschnachweises:', error); res.status(500).json({ success: false, error: 'Fehler beim Download' }); } } /** * DSGVO-Dashboard Statistiken */ export async function getDashboardStats(req: AuthRequest, res: Response) { try { const stats = await gdprService.getGDPRDashboardStats(); res.json({ success: true, data: stats }); } catch (error) { console.error('Fehler beim Abrufen der Dashboard-Statistiken:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } // ==================== CONSENT ENDPOINTS ==================== /** * Einwilligungen eines Kunden abrufen */ export async function getCustomerConsents(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); if (!(await canAccessCustomer(req, res, customerId))) return; const consents = await consentService.getCustomerConsents(customerId); // Labels hinzufügen const consentsWithLabels = consents.map((c) => ({ ...c, label: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.label, description: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.description, })); res.json({ success: true, data: consentsWithLabels }); } catch (error) { console.error('Fehler beim Abrufen der Einwilligungen:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Consent-Status prüfen (hat der Kunde vollständig zugestimmt?) */ export async function checkConsentStatus(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); if (!(await canAccessCustomer(req, res, customerId))) return; const result = await consentService.hasFullConsent(customerId); res.json({ success: true, data: result }); } catch (error) { console.error('Fehler beim Consent-Check:', error); res.status(500).json({ success: false, error: 'Fehler beim Consent-Check' }); } } /** * Einwilligung aktualisieren (nur Kundenportal-Benutzer!) */ export async function updateCustomerConsent(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const consentType = req.params.consentType as ConsentType; // BEWUSST nur `status` aus dem Body übernehmen. `source`, `documentPath` // und `version` darf der Portal-User NICHT setzen – Pentest 2026-05-20 // (MEDIUM): "ADMIN_OVERRIDE" als source bzw. "