import { Response } from 'express'; import prisma from '../lib/prisma.js'; import { AuthRequest, ApiResponse } from '../types/index.js'; import * as appSettingService from '../services/appSetting.service.js'; import { sendAlertEmail, sendDigest } from '../services/securityAlert.service.js'; import type { SecurityEventType, SecuritySeverity } from '@prisma/client'; /** * GET /api/monitoring/events * Liste der Security-Events mit Filter + Pagination. */ export async function listEvents(req: AuthRequest, res: Response): Promise { try { const page = parseInt((req.query.page as string) || '1'); const limit = Math.min(parseInt((req.query.limit as string) || '50'), 200); const type = req.query.type as SecurityEventType | undefined; const severity = req.query.severity as SecuritySeverity | undefined; const search = req.query.search as string | undefined; const since = req.query.since as string | undefined; const ip = req.query.ip as string | undefined; const where: any = {}; if (type) where.type = type; if (severity) where.severity = severity; if (ip) where.ipAddress = ip; if (since) where.createdAt = { gte: new Date(since) }; if (search) { where.OR = [ { message: { contains: search } }, { userEmail: { contains: search } }, { endpoint: { contains: search } }, ]; } const [events, total, byType, bySeverity] = await Promise.all([ prisma.securityEvent.findMany({ where, orderBy: { createdAt: 'desc' }, take: limit, skip: (page - 1) * limit, }), prisma.securityEvent.count({ where }), prisma.securityEvent.groupBy({ by: ['type'], where: since ? { createdAt: { gte: new Date(since) } } : {}, _count: true, }), prisma.securityEvent.groupBy({ by: ['severity'], where: since ? { createdAt: { gte: new Date(since) } } : {}, _count: true, }), ]); res.json({ success: true, data: events, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }, stats: { byType: Object.fromEntries(byType.map((r: any) => [r.type, r._count])), bySeverity: Object.fromEntries(bySeverity.map((r: any) => [r.severity, r._count])), }, } as any); } catch (error) { console.error('listEvents error:', error); res.status(500).json({ success: false, error: 'Fehler beim Laden der Security-Events' } as ApiResponse); } } /** * GET /api/monitoring/settings */ export async function getMonitoringSettings(_req: AuthRequest, res: Response): Promise { try { const alertEmail = await appSettingService.getSetting('monitoringAlertEmail'); const digestEnabled = await appSettingService.getSettingBool('monitoringDigestEnabled'); const lastDigest = await appSettingService.getSetting('monitoringLastDigestAt'); res.json({ success: true, data: { alertEmail: alertEmail || '', digestEnabled, lastDigestAt: lastDigest || null, }, } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden' } as ApiResponse); } } /** * PUT /api/monitoring/settings */ export async function updateMonitoringSettings(req: AuthRequest, res: Response): Promise { try { const { alertEmail, digestEnabled } = req.body || {}; if (typeof alertEmail === 'string') { // Email-Validierung minimal: muss @ enthalten oder leer sein if (alertEmail !== '' && !alertEmail.includes('@')) { res.status(400).json({ success: false, error: 'Ungültige E-Mail-Adresse' } as ApiResponse); return; } await appSettingService.setSetting('monitoringAlertEmail', alertEmail); } if (typeof digestEnabled === 'boolean') { await appSettingService.setSetting('monitoringDigestEnabled', digestEnabled ? 'true' : 'false'); } res.json({ success: true, message: 'Einstellungen gespeichert' } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Speichern' } as ApiResponse); } } /** * POST /api/monitoring/test-alert * Versendet eine Test-Alert-Mail an die konfigurierte Adresse. */ export async function testAlert(_req: AuthRequest, res: Response): Promise { try { const alertEmail = await appSettingService.getSetting('monitoringAlertEmail'); if (!alertEmail) { res.status(400).json({ success: false, error: 'Keine Alert-E-Mail konfiguriert', } as ApiResponse); return; } const result = await sendAlertEmail(alertEmail, { subject: '[OpenCRM] Test-Alert', events: [{ type: 'SUSPICIOUS' as any, severity: 'INFO' as any, message: 'Dies ist eine Test-Mail vom Monitoring-System. Alles in Ordnung.', createdAt: new Date(), } as any], isDigest: false, }); if (result.success) { res.json({ success: true, message: `Test-Alert an ${alertEmail} versendet` } as ApiResponse); } else { res.status(500).json({ success: false, error: result.error || 'Versand fehlgeschlagen' } as ApiResponse); } } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Test-Alert fehlgeschlagen', } as ApiResponse); } } /** * DELETE /api/monitoring/events * Löscht alle SecurityEvents (oder optional nur älter als ?olderThanDays). * Alert-versendete CRITICAL-Events werden vorher noch geloggt, damit der * Audit-Trail erhalten bleibt. */ export async function clearEvents(req: AuthRequest, res: Response): Promise { try { const olderThanDays = req.query.olderThanDays ? parseInt(req.query.olderThanDays as string) : undefined; const where: any = {}; if (olderThanDays && olderThanDays > 0) { const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000); where.createdAt = { lt: cutoff }; } const result = await prisma.securityEvent.deleteMany({ where }); // Audit-Spur: Wer hat geleert const user = (req as any).user; await prisma.securityEvent.create({ data: { type: 'PERMISSION_CHANGED', severity: 'INFO', message: `Security-Log geleert: ${result.count} Einträge gelöscht${olderThanDays ? ` (älter als ${olderThanDays} Tage)` : ''}`, userId: user?.userId || null, userEmail: user?.email || null, ipAddress: req.ip || 'unknown', endpoint: 'DELETE /api/monitoring/events', }, }); res.json({ success: true, message: `${result.count} Events gelöscht`, data: { deletedCount: result.count }, } as any); } catch (error) { console.error('clearEvents error:', error); res.status(500).json({ success: false, error: 'Löschen fehlgeschlagen' } as ApiResponse); } } /** * POST /api/monitoring/run-digest (manueller Trigger für den Hourly-Digest) */ export async function runDigestNow(_req: AuthRequest, res: Response): Promise { try { const result = await sendDigest({ force: true }); res.json({ success: true, data: result } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Digest fehlgeschlagen', } as ApiResponse); } }