complete new audit system

This commit is contained in:
2026-03-21 18:23:54 +01:00
parent 4f359df161
commit 219e1930f7
159 changed files with 2841 additions and 736 deletions
+1 -3
View File
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
// Default settings
const DEFAULT_SETTINGS: Record<string, string> = {
+83 -9
View File
@@ -3,6 +3,42 @@ import crypto from 'crypto';
import { encrypt, decrypt } from '../utils/encryption.js';
import prisma from '../lib/prisma.js';
/**
* Vereinfachte Audit-Log-Funktion für gezielte Änderungsprotokolle.
* Wird direkt in Controllern aufgerufen mit aussagekräftigen Details.
*/
export async function logChange(opts: {
req: any; // Express Request (für userId, email, IP)
action: AuditAction;
resourceType: string;
resourceId?: string;
label: string; // Menschenlesbares Label z.B. "Vollmacht für Stefan Hacker widerrufen"
details?: Record<string, unknown>; // Zusätzliche Details z.B. { vorher: 'erteilt', nachher: 'widerrufen' }
customerId?: number;
}) {
try {
const user = opts.req?.user;
await createAuditLog({
userId: user?.userId,
userEmail: user?.email || 'system',
userRole: user?.isCustomerPortal ? 'Kundenportal' : 'Mitarbeiter',
customerId: user?.customerId,
isCustomerPortal: user?.isCustomerPortal,
action: opts.action,
resourceType: opts.resourceType,
resourceId: opts.resourceId,
resourceLabel: opts.label,
endpoint: opts.req?.path || '',
httpMethod: opts.req?.method || '',
ipAddress: opts.req?.socket?.remoteAddress || opts.req?.headers?.['x-forwarded-for'] || 'unknown',
dataSubjectId: opts.customerId,
changesAfter: opts.details,
});
} catch (error) {
console.error('[logChange] Fehler:', error);
}
}
export interface CreateAuditLogData {
userId?: number;
userEmail: string;
@@ -101,16 +137,11 @@ function determineSensitivity(resourceType: string): AuditSensitivity {
}
/**
* Prüft ob Änderungen verschlüsselt werden sollen
* Prüft ob Änderungen verschlüsselt werden sollen.
* Deaktiviert - sensible Felder werden bereits von der Prisma-Middleware als [REDACTED] gefiltert.
*/
function shouldEncryptChanges(resourceType: string): boolean {
const encryptedTypes = [
'BankCard',
'IdentityDocument',
'User',
'Customer', // Enthält Portal-Passwörter
];
return encryptedTypes.includes(resourceType);
function shouldEncryptChanges(_resourceType: string): boolean {
return false;
}
/**
@@ -381,6 +412,49 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
};
}
/**
* Hash-Kette komplett neu berechnen (Reparatur)
*/
export async function rehashAll(): Promise<{ rehashedCount: number }> {
const logs = await prisma.auditLog.findMany({
orderBy: { id: 'asc' },
select: {
id: true,
userEmail: true,
action: true,
resourceType: true,
resourceId: true,
endpoint: true,
createdAt: true,
},
});
let previousHash: string | null = null;
let count = 0;
for (const log of logs) {
const hash = generateHash({
userEmail: log.userEmail,
action: log.action,
resourceType: log.resourceType,
resourceId: log.resourceId,
endpoint: log.endpoint,
createdAt: log.createdAt,
previousHash,
});
await prisma.auditLog.update({
where: { id: log.id },
data: { hash, previousHash },
});
previousHash = hash;
count++;
}
return { rehashedCount: count };
}
/**
* Exportiert Audit-Logs als JSON oder CSV
*/
+1 -3
View File
@@ -1,11 +1,9 @@
import { PrismaClient } from '@prisma/client';
import prisma from '../lib/prisma.js';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { JwtPayload } from '../types/index.js';
import { encrypt, decrypt } from '../utils/encryption.js';
const prisma = new PrismaClient();
// Mitarbeiter-Login
export async function login(email: string, password: string) {
const user = await prisma.user.findUnique({
+1 -3
View File
@@ -4,15 +4,13 @@
* Ermöglicht Backup und Restore der Datenbank und Uploads über die Web-Oberfläche.
*/
import { PrismaClient } from '@prisma/client';
import prisma from '../lib/prisma.js';
import * as fs from 'fs';
import * as path from 'path';
import archiver from 'archiver';
import AdmZip from 'adm-zip';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
// Verzeichnisse
const BACKUPS_DIR = path.join(__dirname, '../../prisma/backups');
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
+2 -3
View File
@@ -1,13 +1,12 @@
// ==================== CACHED EMAIL SERVICE ====================
// Service für E-Mail-Caching und Vertragszuordnung
import { PrismaClient, CachedEmail, Prisma, EmailFolder } from '@prisma/client';
import { CachedEmail, Prisma, EmailFolder } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { decrypt } from '../utils/encryption.js';
import { fetchEmails, ImapCredentials, FetchedEmail, moveToTrash, restoreFromTrash, permanentDelete } from './imapService.js';
import { getImapSmtpSettings } from './emailProvider/emailProviderService.js';
const prisma = new PrismaClient();
// ==================== TYPES ====================
export interface CachedEmailWithRelations extends CachedEmail {
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getAllCancellationPeriods(includeInactive = false) {
const where = includeInactive ? {} : { isActive: true };
+2 -2
View File
@@ -253,8 +253,8 @@ export const CONSENT_TYPE_LABELS: Record<ConsentType, { label: string; descripti
description: 'Grundlegende Verarbeitung personenbezogener Daten zur Vertragserfüllung',
},
MARKETING_EMAIL: {
label: 'E-Mail-Marketing',
description: 'Zusendung von Werbung und Angeboten per E-Mail',
label: 'Elektronisches Marketing',
description: 'Zusendung von Werbung und Angeboten über elektronische Kommunikationswege (E-Mail, Messenger etc.)',
},
MARKETING_PHONE: {
label: 'Telefonmarketing',
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getAllContractDurations(includeInactive = false) {
const where = includeInactive ? {} : { isActive: true };
+2 -3
View File
@@ -1,9 +1,8 @@
import { PrismaClient, ContractType, ContractStatus } from '@prisma/client';
import { ContractType, ContractStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { generateContractNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
import { encrypt, decrypt } from '../utils/encryption.js';
const prisma = new PrismaClient();
export interface ContractFilters {
customerId?: number;
customerIds?: number[]; // Für Kundenportal: eigene ID + vertretene Kunden
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getAllContractCategories(includeInactive = false) {
return prisma.contractCategory.findMany({
@@ -1,9 +1,7 @@
import { PrismaClient, ContractStatus
} from '@prisma/client';
import { ContractStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import * as appSettingService from './appSetting.service.js';
const prisma = new PrismaClient();
// Typen für das Cockpit
export type UrgencyLevel = 'critical' | 'warning' | 'ok' | 'none';
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export interface CreateHistoryEntryData {
title: string;
+2 -3
View File
@@ -1,6 +1,5 @@
import { PrismaClient, ContractTaskStatus } from '@prisma/client';
const prisma = new PrismaClient();
import { ContractTaskStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
export interface ContractTaskFilters {
contractId: number;
+2 -3
View File
@@ -1,10 +1,9 @@
import { PrismaClient, CustomerType, ContractStatus } from '@prisma/client';
import { CustomerType, ContractStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { generateCustomerNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
import fs from 'fs';
import path from 'path';
const prisma = new PrismaClient();
// Helper zum Löschen von Dateien
function deleteFileIfExists(filePath: string | null) {
if (!filePath) return;
@@ -1,6 +1,6 @@
// ==================== EMAIL PROVIDER SERVICE ====================
import { PrismaClient } from '@prisma/client';
import prisma from '../../lib/prisma.js';
import { decrypt } from '../../utils/encryption.js';
import {
IEmailProvider,
@@ -12,8 +12,6 @@ import {
} from './types.js';
import { PleskEmailProvider } from './pleskProvider.js';
const prisma = new PrismaClient();
// Factory-Funktion um den richtigen Provider zu erstellen
function createProvider(config: EmailProviderConfig): IEmailProvider {
switch (config.type) {
+2 -3
View File
@@ -1,9 +1,8 @@
import { PrismaClient, InvoiceType } from '@prisma/client';
import { InvoiceType } from '@prisma/client';
import prisma from '../lib/prisma.js';
import fs from 'fs';
import path from 'path';
const prisma = new PrismaClient();
export interface CreateInvoiceData {
invoiceDate: Date;
invoiceType: InvoiceType;
+1 -3
View File
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getAllPlatforms(includeInactive = false) {
const where = includeInactive ? {} : { isActive: true };
+1 -3
View File
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getAllProviders(includeInactive = false) {
const where = includeInactive ? {} : { isActive: true };
@@ -1,4 +1,4 @@
import { PrismaClient } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { encrypt, decrypt } from '../utils/encryption.js';
import {
provisionEmail,
@@ -10,8 +10,6 @@ import {
} from './emailProvider/emailProviderService.js';
import { generateSecurePassword } from '../utils/passwordGenerator.js';
const prisma = new PrismaClient();
export async function getEmailsByCustomerId(customerId: number, includeInactive = false) {
const where: Record<string, unknown> = { customerId };
if (!includeInactive) {
+1 -3
View File
@@ -1,6 +1,4 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import prisma from '../lib/prisma.js';
export async function getTariffsByProvider(providerId: number, includeInactive = false) {
const where: { providerId: number; isActive?: boolean } = { providerId };
+1 -3
View File
@@ -1,9 +1,7 @@
import { PrismaClient } from '@prisma/client';
import prisma from '../lib/prisma.js';
import bcrypt from 'bcryptjs';
import { paginate, buildPaginationResponse } from '../utils/helpers.js';
const prisma = new PrismaClient();
export interface UserFilters {
search?: string;
isActive?: boolean;