fix(users): DSGVO-/Entwickler-Zugriff über User-Update durchreichen

`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) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 18:27:02 +02:00
parent 4201a90fd0
commit 2d3ca28691
2 changed files with 20 additions and 2 deletions
+14 -2
View File
@@ -72,8 +72,19 @@ export async function updateUser(req: Request, res: Response): Promise<void> {
// 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<void> {
const changes: Record<string, { von: unknown; nach: unknown }> = {};
const fieldLabels: Record<string, string> = {
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;
+6
View File
@@ -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;