Beim Suchen eines verify-integrity-Endpoints stiess der Pentester
auf einen 500er: GET /api/audit-logs/verify matcht GET /:id (das
echte Verify ist POST /verify), parseInt("verify") = NaN → Prisma
findUnique({ where:{ id: NaN }}) wirft. Gleiche 400-statt-500-
Klasse wie R64.1/R104.1.
Fix: Number.isNaN-Guard in getAuditLogById, getAuditLogsByCustomer
und updateRetentionPolicy → sauberer 400 „Ungültige ID".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
241 lines
7.6 KiB
TypeScript
241 lines
7.6 KiB
TypeScript
import { Response } from 'express';
|
||
import { AuthRequest } from '../types/index.js';
|
||
import * as auditService from '../services/audit.service.js';
|
||
import { logChange } from '../services/audit.service.js';
|
||
import { AuditAction, AuditSensitivity } from '@prisma/client';
|
||
|
||
/**
|
||
* Audit-Logs mit Filtern abrufen
|
||
*/
|
||
export async function getAuditLogs(req: AuthRequest, res: Response) {
|
||
try {
|
||
const {
|
||
userId,
|
||
customerId,
|
||
dataSubjectId,
|
||
action,
|
||
sensitivity,
|
||
resourceType,
|
||
resourceId,
|
||
startDate,
|
||
endDate,
|
||
success,
|
||
search,
|
||
page,
|
||
limit,
|
||
} = req.query;
|
||
|
||
const result = await auditService.searchAuditLogs({
|
||
userId: userId ? parseInt(userId as string) : undefined,
|
||
customerId: customerId ? parseInt(customerId as string) : undefined,
|
||
dataSubjectId: dataSubjectId ? parseInt(dataSubjectId as string) : undefined,
|
||
action: action as AuditAction | undefined,
|
||
sensitivity: sensitivity as AuditSensitivity | undefined,
|
||
resourceType: resourceType as string | undefined,
|
||
resourceId: resourceId as string | undefined,
|
||
startDate: startDate ? new Date(startDate as string) : undefined,
|
||
endDate: endDate ? new Date(endDate as string) : undefined,
|
||
success: success !== undefined ? success === 'true' : undefined,
|
||
search: search as string | undefined,
|
||
page: page ? parseInt(page as string) : 1,
|
||
limit: limit ? parseInt(limit as string) : 50,
|
||
});
|
||
|
||
res.json({ success: true, ...result });
|
||
} catch (error) {
|
||
console.error('Fehler beim Abrufen der Audit-Logs:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Audit-Logs' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Einzelnes Audit-Log abrufen
|
||
*/
|
||
export async function getAuditLogById(req: AuthRequest, res: Response) {
|
||
try {
|
||
const id = parseInt(req.params.id);
|
||
// Nicht-numerische :id (z.B. GET /audit-logs/verify statt POST /verify)
|
||
// landete sonst als NaN in Prisma findUnique → 500. Sauberer 400.
|
||
// (Pentest R120 – gleiche „400 statt 500"-Klasse wie R64.1/R104.1.)
|
||
if (Number.isNaN(id)) {
|
||
return res.status(400).json({ success: false, error: 'Ungültige Audit-Log-ID' });
|
||
}
|
||
const log = await auditService.getAuditLogById(id);
|
||
|
||
if (!log) {
|
||
return res.status(404).json({ success: false, error: 'Audit-Log nicht gefunden' });
|
||
}
|
||
|
||
res.json({ success: true, data: log });
|
||
} catch (error) {
|
||
console.error('Fehler beim Abrufen des Audit-Logs:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Audit-Logs' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Audit-Logs für einen Kunden abrufen (DSGVO)
|
||
*/
|
||
export async function getAuditLogsByCustomer(req: AuthRequest, res: Response) {
|
||
try {
|
||
const customerId = parseInt(req.params.customerId);
|
||
if (Number.isNaN(customerId)) {
|
||
return res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' });
|
||
}
|
||
const logs = await auditService.getAuditLogsByDataSubject(customerId);
|
||
|
||
res.json({ success: true, data: logs });
|
||
} catch (error) {
|
||
console.error('Fehler beim Abrufen der Kunden-Audit-Logs:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Audit-Logs' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Audit-Logs exportieren
|
||
*/
|
||
export async function exportAuditLogs(req: AuthRequest, res: Response) {
|
||
try {
|
||
const format = (req.query.format as 'json' | 'csv') || 'json';
|
||
const {
|
||
action,
|
||
sensitivity,
|
||
resourceType,
|
||
startDate,
|
||
endDate,
|
||
} = req.query;
|
||
|
||
const content = await auditService.exportAuditLogs(
|
||
{
|
||
action: action as AuditAction | undefined,
|
||
sensitivity: sensitivity as AuditSensitivity | undefined,
|
||
resourceType: resourceType as string | undefined,
|
||
startDate: startDate ? new Date(startDate as string) : undefined,
|
||
endDate: endDate ? new Date(endDate as string) : undefined,
|
||
},
|
||
format
|
||
);
|
||
|
||
if (format === 'csv') {
|
||
const filename = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`;
|
||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||
// BOM für Excel UTF-8 Erkennung
|
||
res.send('\uFEFF' + content);
|
||
} else {
|
||
res.json({ success: true, data: JSON.parse(content) })
|
||
}
|
||
} catch (error) {
|
||
console.error('Fehler beim Exportieren der Audit-Logs:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Exportieren' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hash-Ketten-Integrität prüfen
|
||
*/
|
||
export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||
try {
|
||
const { fromId, toId } = req.query;
|
||
|
||
const result = await auditService.verifyIntegrity(
|
||
fromId ? parseInt(fromId as string) : undefined,
|
||
toId ? parseInt(toId as string) : undefined
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
valid: result.valid,
|
||
checkedCount: result.checkedCount,
|
||
invalidEntries: result.invalidEntries,
|
||
message: result.valid
|
||
? 'Alle Einträge sind valide'
|
||
: `${result.invalidEntries.length} manipulierte Einträge gefunden`,
|
||
},
|
||
});
|
||
} catch (error) {
|
||
console.error('Fehler bei der Integritätsprüfung:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler bei der Integritätsprüfung' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hash-Kette reparieren (alle Hashes neu berechnen)
|
||
*/
|
||
export async function rehashAll(req: AuthRequest, res: Response) {
|
||
try {
|
||
const result = await auditService.rehashAll();
|
||
res.json({
|
||
success: true,
|
||
data: result,
|
||
message: `${result.rehashedCount} Einträge neu gehasht. Kette ist jetzt intakt.`,
|
||
});
|
||
} catch (error) {
|
||
console.error('Fehler beim Re-Hashing:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Re-Hashing' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Retention-Policies abrufen
|
||
*/
|
||
export async function getRetentionPolicies(req: AuthRequest, res: Response) {
|
||
try {
|
||
const policies = await auditService.getRetentionPolicies();
|
||
res.json({ success: true, data: policies });
|
||
} catch (error) {
|
||
console.error('Fehler beim Abrufen der Retention-Policies:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Policies' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Retention-Policy aktualisieren
|
||
*/
|
||
export async function updateRetentionPolicy(req: AuthRequest, res: Response) {
|
||
try {
|
||
const id = parseInt(req.params.id);
|
||
if (Number.isNaN(id)) {
|
||
return res.status(400).json({ success: false, error: 'Ungültige Policy-ID' });
|
||
}
|
||
const { retentionDays, description, legalBasis, isActive } = req.body;
|
||
|
||
const policy = await auditService.updateRetentionPolicy(id, {
|
||
retentionDays,
|
||
description,
|
||
legalBasis,
|
||
isActive,
|
||
});
|
||
|
||
await logChange({
|
||
req, action: 'UPDATE', resourceType: 'RetentionPolicy',
|
||
resourceId: id.toString(),
|
||
label: `Aufbewahrungsrichtlinie aktualisiert`,
|
||
});
|
||
|
||
res.json({ success: true, data: policy });
|
||
} catch (error) {
|
||
console.error('Fehler beim Aktualisieren der Retention-Policy:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Retention-Cleanup manuell ausführen
|
||
*/
|
||
export async function runRetentionCleanup(req: AuthRequest, res: Response) {
|
||
try {
|
||
const result = await auditService.runRetentionCleanup();
|
||
|
||
res.json({
|
||
success: true,
|
||
data: result,
|
||
message: `${result.deletedCount} alte Audit-Logs wurden gelöscht`,
|
||
});
|
||
} catch (error) {
|
||
console.error('Fehler beim Retention-Cleanup:', error);
|
||
res.status(500).json({ success: false, error: 'Fehler beim Cleanup' });
|
||
}
|
||
}
|