From 2d3ca28691ae3ae529c6edc6e7235486f1ab85f8 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 7 May 2026 18:27:02 +0200 Subject: [PATCH] =?UTF-8?q?fix(users):=20DSGVO-/Entwickler-Zugriff=20?= =?UTF-8?q?=C3=BCber=20User-Update=20durchreichen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pickUserUpdate`-Whitelist enthielt `hasGdprAccess` und `hasDeveloperAccess` nicht – sie wurden vom Mass-Assignment-Schutz aus dem Request entfernt, bevor sie den Service erreichen konnten. Damit lief `setUserGdprAccess` / `setUserDeveloperAccess` nie und die zwei versteckten Rollen blieben unzuweisbar (UI-Checkbox hatte keine Wirkung). Fix: Beide Felder zur Whitelist hinzugefügt – sie sind keine User-Spalten, der Service mappt sie auf die DSGVO-/Developer-Rollen. Bonus: Audit-Log-Diff vergleicht jetzt den Pre-State korrekt (User-Rollen in `before` mitgeladen + Field-Labels), sonst hätte der jetzt durchkommende Flag immer einen False-Positive-Change "- → Ja" produziert. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/src/controllers/user.controller.ts | 16 ++++++++++++++-- backend/src/utils/sanitize.ts | 6 ++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index 03fafaca..6ab0a816 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -72,8 +72,19 @@ export async function updateUser(req: Request, res: Response): Promise { // Whitelist: nur erlaubte Felder aus req.body übernehmen (Mass-Assignment-Schutz) const data = pickUserUpdate(req.body); - // Vorherigen Stand laden für Audit - const before = await prisma.user.findUnique({ where: { id: userId } }); + // Vorherigen Stand laden für Audit – inkl. Rollen, damit hasGdprAccess / + // hasDeveloperAccess (versteckte Rollen) korrekt verglichen werden. + const beforeUser = await prisma.user.findUnique({ + where: { id: userId }, + include: { roles: { include: { role: true } } }, + }); + const before = beforeUser + ? { + ...beforeUser, + hasGdprAccess: beforeUser.roles.some((ur) => ur.role.name === 'DSGVO'), + hasDeveloperAccess: beforeUser.roles.some((ur) => ur.role.name === 'Developer'), + } + : null; const user = await userService.updateUser(userId, data as any); if (user) { @@ -82,6 +93,7 @@ export async function updateUser(req: Request, res: Response): Promise { const changes: Record = {}; const fieldLabels: Record = { email: 'E-Mail', firstName: 'Vorname', lastName: 'Nachname', isActive: 'Aktiv', + hasGdprAccess: 'DSGVO-Zugriff', hasDeveloperAccess: 'Entwicklerzugriff', }; for (const [key, newVal] of Object.entries(data)) { if (['id', 'createdAt', 'updatedAt'].includes(key)) continue; diff --git a/backend/src/utils/sanitize.ts b/backend/src/utils/sanitize.ts index 7f737679..77310b0b 100644 --- a/backend/src/utils/sanitize.ts +++ b/backend/src/utils/sanitize.ts @@ -110,6 +110,12 @@ const USER_UPDATABLE_FIELDS = [ 'signalNumber', 'roleIds', 'password', // nur Admin, wird im Service gehashed + // hasGdprAccess + hasDeveloperAccess sind keine User-Spalten – der Service + // mappt sie auf die versteckten Rollen DSGVO/Developer (siehe + // setUserGdprAccess / setUserDeveloperAccess). Müssen aber auf der Whitelist + // stehen, damit pick() sie nicht aus dem Request entfernt. + 'hasGdprAccess', + 'hasDeveloperAccess', // Nicht: id, customerId, tokenInvalidatedAt, passwordResetToken, passwordResetExpiresAt ] as const;