gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery

This commit is contained in:
2026-03-21 11:59:53 +01:00
parent 09e87c951b
commit c3edb8ad2e
1491 changed files with 265550 additions and 1292 deletions
+213
View File
@@ -0,0 +1,213 @@
import { Response, NextFunction } from 'express';
import { AuditAction } from '@prisma/client';
import { AuthRequest } from '../types/index.js';
import { createAuditLog } from '../services/audit.service.js';
import { getAuditContext, AuditContext } from './auditContext.js';
// Resource-Typ-Mapping basierend auf Route-Patterns
const RESOURCE_MAPPING: Record<string, { type: string; extractId?: (req: AuthRequest) => string | undefined }> = {
'/api/customers': { type: 'Customer', extractId: (req) => req.params.id || req.params.customerId },
'/api/customers/*/bank-cards': { type: 'BankCard', extractId: (req) => req.params.bankCardId },
'/api/customers/*/documents': { type: 'IdentityDocument', extractId: (req) => req.params.documentId },
'/api/customers/*/addresses': { type: 'Address', extractId: (req) => req.params.addressId },
'/api/customers/*/meters': { type: 'Meter', extractId: (req) => req.params.meterId },
'/api/customers/*/consents': { type: 'CustomerConsent', extractId: (req) => req.params.type },
'/api/contracts': { type: 'Contract', extractId: (req) => req.params.id },
'/api/contracts/*/history': { type: 'ContractHistoryEntry', extractId: (req) => req.params.entryId },
'/api/contracts/*/tasks': { type: 'ContractTask', extractId: (req) => req.params.taskId },
'/api/users': { type: 'User', extractId: (req) => req.params.id },
'/api/providers': { type: 'Provider', extractId: (req) => req.params.id },
'/api/tariffs': { type: 'Tariff', extractId: (req) => req.params.id },
'/api/platforms': { type: 'SalesPlatform', extractId: (req) => req.params.id },
'/api/contract-categories': { type: 'ContractCategory', extractId: (req) => req.params.id },
'/api/cancellation-periods': { type: 'CancellationPeriod', extractId: (req) => req.params.id },
'/api/contract-durations': { type: 'ContractDuration', extractId: (req) => req.params.id },
'/api/settings': { type: 'AppSetting', extractId: (req) => req.params.key },
'/api/email-providers': { type: 'EmailProviderConfig', extractId: (req) => req.params.id },
'/api/auth': { type: 'Authentication' },
'/api/audit-logs': { type: 'AuditLog', extractId: (req) => req.params.id },
'/api/gdpr': { type: 'GDPR' },
};
// Routen die nicht geloggt werden sollen
const EXCLUDED_ROUTES = [
'/api/health',
'/api/uploads',
];
/**
* Bestimmt die Aktion basierend auf HTTP-Methode und Erfolg
*/
function determineAction(method: string, path: string, success: boolean): AuditAction {
// Spezielle Auth-Aktionen
if (path.includes('/auth/login')) {
return success ? 'LOGIN' : 'LOGIN_FAILED';
}
if (path.includes('/auth/logout')) {
return 'LOGOUT';
}
// Standard CRUD-Aktionen
switch (method.toUpperCase()) {
case 'GET':
return 'READ';
case 'POST':
return 'CREATE';
case 'PUT':
case 'PATCH':
return 'UPDATE';
case 'DELETE':
return 'DELETE';
default:
return 'READ';
}
}
/**
* Findet den passenden Resource-Typ für einen Pfad
*/
function findResourceMapping(path: string): { type: string; extractId?: (req: AuthRequest) => string | undefined } | null {
// Exakte Matches zuerst prüfen
for (const [pattern, mapping] of Object.entries(RESOURCE_MAPPING)) {
// Konvertiere Pattern zu Regex
const regexPattern = pattern
.replace(/\*/g, '[^/]+')
.replace(/\//g, '\\/');
const regex = new RegExp(`^${regexPattern}(?:/|$)`);
if (regex.test(path)) {
return mapping;
}
}
return null;
}
/**
* Extrahiert die betroffene Kunden-ID für DSGVO-Tracking
*/
function extractDataSubjectId(req: AuthRequest): number | undefined {
// Aus Route-Parameter
const customerId = req.params.customerId || req.params.id;
if (customerId && req.path.includes('/customers')) {
return parseInt(customerId);
}
// Aus Request-Body (bei Create)
if (req.body?.customerId) {
return parseInt(req.body.customerId);
}
// Bei Kundenportal-Zugriff
if (req.user?.customerId) {
return req.user.customerId;
}
return undefined;
}
/**
* Extrahiert die IP-Adresse des Clients
*/
function getClientIp(req: AuthRequest): string {
const forwarded = req.headers['x-forwarded-for'];
if (typeof forwarded === 'string') {
return forwarded.split(',')[0].trim();
}
return req.socket.remoteAddress || 'unknown';
}
/**
* Audit Middleware - loggt alle API-Aufrufe asynchron
*/
export function auditMiddleware(req: AuthRequest, res: Response, next: NextFunction): void {
const startTime = Date.now();
// Ausgeschlossene Routen überspringen
if (EXCLUDED_ROUTES.some((route) => req.path.startsWith(route))) {
next();
return;
}
// Resource-Mapping finden
const mapping = findResourceMapping(req.path);
if (!mapping) {
// Unbekannte Route - trotzdem loggen mit generischem Typ
next();
return;
}
// Original res.json überschreiben um Response zu capturen
const originalJson = res.json.bind(res);
let responseBody: unknown = null;
let responseSuccess = true;
res.json = function (body: unknown) {
responseBody = body;
if (typeof body === 'object' && body !== null && 'success' in body) {
responseSuccess = (body as { success: boolean }).success;
}
return originalJson(body);
};
// Response-Ende abfangen für Logging
res.on('finish', () => {
// Async Logging - blockiert nicht die Response
setImmediate(async () => {
try {
const durationMs = Date.now() - startTime;
const action = determineAction(req.method, req.path, responseSuccess);
const resourceId = mapping.extractId?.(req);
const dataSubjectId = extractDataSubjectId(req);
// Audit-Kontext abrufen (enthält Before/After-Werte von Prisma Middleware)
const auditContext = getAuditContext();
// Label für bessere Lesbarkeit generieren
let resourceLabel: string | undefined;
if (responseBody && typeof responseBody === 'object' && 'data' in responseBody) {
const data = (responseBody as { data: Record<string, unknown> }).data;
if (data) {
// Versuche verschiedene Label-Felder
resourceLabel =
(data.contractNumber as string) ||
(data.customerNumber as string) ||
(data.name as string) ||
(data.email as string) ||
(data.firstName && data.lastName
? `${data.firstName} ${data.lastName}`
: undefined);
}
}
await createAuditLog({
userId: req.user?.userId,
userEmail: req.user?.email || 'anonymous',
userRole: req.user?.permissions?.join(', '),
customerId: req.user?.customerId,
isCustomerPortal: req.user?.isCustomerPortal,
action,
resourceType: mapping.type,
resourceId,
resourceLabel,
endpoint: req.path,
httpMethod: req.method,
ipAddress: getClientIp(req),
userAgent: req.headers['user-agent'],
changesBefore: auditContext?.before,
changesAfter: auditContext?.after,
dataSubjectId,
success: responseSuccess,
errorMessage: !responseSuccess && responseBody && typeof responseBody === 'object' && 'error' in responseBody
? (responseBody as { error: string }).error
: undefined,
durationMs,
});
} catch (error) {
// Audit-Logging darf niemals die Anwendung beeinträchtigen
console.error('[AuditMiddleware] Fehler beim Logging:', error);
}
});
});
next();
}