/** * Security-Monitor: zentrale `emit()`-Funktion für sicherheitsrelevante * Events. Schreibt in die `SecurityEvent`-Tabelle (nicht im AuditLog, * weil hier andere Anforderungen gelten: schnelles Filtern, Threshold- * Detection, Realtime-Alerting statt forensischer Hash-Chain). * * Hooks für die wichtigsten Klassen: * - LOGIN_FAILED → Login mit falschem Passwort * - LOGIN_SUCCESS → erfolgreicher Login (informativ) * - RATE_LIMIT_HIT → express-rate-limit hat zugeschlagen * - ACCESS_DENIED → 403 von canAccess* (versuchter IDOR) * - SSRF_BLOCKED → ssrfGuard hat geblockt * - PASSWORD_RESET_REQUEST → Reset angefordert * - PASSWORD_RESET_CONFIRM → Reset abgeschlossen * - LOGOUT → expliziter Logout * - TOKEN_REJECTED → JWT verify-Failure * - PERMISSION_CHANGED → Rolle/Permission-Update * * Sofort-Alert für CRITICAL+HIGH-Events (wenn `monitoringAlertEmail` * konfiguriert), sonst Sammlung im stündlichen Digest. */ import prisma from '../lib/prisma.js'; import type { SecurityEventType, SecuritySeverity } from '@prisma/client'; export interface SecurityEventInput { type: SecurityEventType; severity: SecuritySeverity; message: string; ipAddress?: string | null; userId?: number | null; customerId?: number | null; userEmail?: string | null; endpoint?: string | null; details?: Record; } /** * Schreibt ein SecurityEvent. Fehler beim Schreiben werden geschluckt, * damit ein kaputtes Monitoring nicht den Login-Flow stoppt. */ export async function emit(event: SecurityEventInput): Promise { try { await prisma.securityEvent.create({ data: { type: event.type, severity: event.severity, message: event.message, ipAddress: event.ipAddress || null, userId: event.userId || null, customerId: event.customerId || null, userEmail: event.userEmail || null, endpoint: event.endpoint || null, details: event.details ? (event.details as any) : undefined, }, }); } catch (err) { console.error('[securityMonitor] emit failed:', err); } } /** * Helper: aus einem Express-Request die wichtigsten Kontextfelder extrahieren. * Funktioniert sowohl mit AuthRequest (eingeloggt) als auch mit anonymen * Requests (Login-Versuch etc.). */ export function contextFromRequest(req: any): { ipAddress: string; userId?: number; customerId?: number; userEmail?: string; endpoint: string; } { const user = req?.user; return { ipAddress: req?.ip || req?.socket?.remoteAddress || 'unknown', userId: user?.userId, customerId: user?.customerId, userEmail: user?.email, endpoint: `${req?.method || ''} ${req?.path || req?.originalUrl || ''}`.trim(), }; }