135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import { PrismaClient, Prisma } from '@prisma/client';
|
|
import { setBeforeValues, setAfterValues } from '../middleware/auditContext.js';
|
|
|
|
// Modelle die für Before/After-Tracking relevant sind
|
|
const AUDITED_MODELS = [
|
|
'Customer',
|
|
'Contract',
|
|
'Address',
|
|
'BankCard',
|
|
'IdentityDocument',
|
|
'User',
|
|
'Meter',
|
|
'MeterReading',
|
|
'StressfreiEmail',
|
|
'Provider',
|
|
'Tariff',
|
|
'ContractCategory',
|
|
'AppSetting',
|
|
'CustomerConsent',
|
|
];
|
|
|
|
// Sensible Felder die aus dem Audit-Log gefiltert werden
|
|
const SENSITIVE_FIELDS = [
|
|
'password',
|
|
'passwordHash',
|
|
'portalPasswordHash',
|
|
'portalPasswordEncrypted',
|
|
'emailPasswordEncrypted',
|
|
'internetPasswordEncrypted',
|
|
'sipPasswordEncrypted',
|
|
'pin',
|
|
'puk',
|
|
'apiKey',
|
|
];
|
|
|
|
/**
|
|
* Filtert sensible Felder aus einem Objekt
|
|
*/
|
|
function filterSensitiveFields(obj: Record<string, unknown>): Record<string, unknown> {
|
|
const filtered: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
if (SENSITIVE_FIELDS.includes(key)) {
|
|
filtered[key] = '[REDACTED]';
|
|
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
filtered[key] = filterSensitiveFields(value as Record<string, unknown>);
|
|
} else {
|
|
filtered[key] = value;
|
|
}
|
|
}
|
|
return filtered;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Model für Audit-Tracking relevant ist
|
|
*/
|
|
function isAuditedModel(model: string | undefined): boolean {
|
|
return model !== undefined && AUDITED_MODELS.includes(model);
|
|
}
|
|
|
|
/**
|
|
* Erstellt einen Prisma Client mit Audit-Middleware
|
|
*/
|
|
function createPrismaClient(): PrismaClient {
|
|
const prisma = new PrismaClient();
|
|
|
|
// Middleware für Before/After-Tracking
|
|
prisma.$use(async (params: Prisma.MiddlewareParams, next: (params: Prisma.MiddlewareParams) => Promise<unknown>) => {
|
|
const { model, action, args } = params;
|
|
|
|
// Nur relevante Modelle und Aktionen tracken
|
|
if (!isAuditedModel(model)) {
|
|
return next(params);
|
|
}
|
|
|
|
// Update-Operationen: Vorherigen Stand abrufen
|
|
if (action === 'update' || action === 'updateMany') {
|
|
try {
|
|
const modelDelegate = (prisma as unknown as Record<string, { findUnique: (args: unknown) => Promise<unknown> }>)[
|
|
model!.charAt(0).toLowerCase() + model!.slice(1)
|
|
];
|
|
|
|
if (modelDelegate && args?.where) {
|
|
const before = await modelDelegate.findUnique({ where: args.where });
|
|
if (before) {
|
|
setBeforeValues(filterSensitiveFields(before as Record<string, unknown>));
|
|
}
|
|
}
|
|
} catch {
|
|
// Fehler beim Abrufen des vorherigen Stands ignorieren
|
|
}
|
|
}
|
|
|
|
// Delete-Operationen: Datensatz vor dem Löschen abrufen
|
|
if (action === 'delete' || action === 'deleteMany') {
|
|
try {
|
|
const modelDelegate = (prisma as unknown as Record<string, { findUnique: (args: unknown) => Promise<unknown> }>)[
|
|
model!.charAt(0).toLowerCase() + model!.slice(1)
|
|
];
|
|
|
|
if (modelDelegate && args?.where) {
|
|
const before = await modelDelegate.findUnique({ where: args.where });
|
|
if (before) {
|
|
setBeforeValues(filterSensitiveFields(before as Record<string, unknown>));
|
|
}
|
|
}
|
|
} catch {
|
|
// Fehler beim Abrufen ignorieren
|
|
}
|
|
}
|
|
|
|
// Operation ausführen
|
|
const result = await next(params);
|
|
|
|
// Nach Update/Create: Neuen Stand speichern
|
|
if ((action === 'update' || action === 'create') && result) {
|
|
setAfterValues(filterSensitiveFields(result as Record<string, unknown>));
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
return prisma;
|
|
}
|
|
|
|
// Singleton-Instanz
|
|
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
|
|
|
|
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
globalForPrisma.prisma = prisma;
|
|
}
|
|
|
|
export default prisma;
|