opencrm/backend/src/lib/prisma.ts

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;