Security & UX Release v7
Security: - H1: Stored-XSS-Fix — Upload-Pfad-Whitelist (Server + Frontend-Guard safeFileUrl) - H2: Transaktionen repariert — txDb-Contract in db.js (PG + SQLite), Rollback funktioniert - H3-Vorbereitung: SESSION_SECRET wird in Compose durchgereicht (Fix M3) - registerLimiter exportiert (Crash-Bug: Route.post ohne Callback) - LDAP-Sync: PG-Transaktionsabbruch bei UNIQUE-Verstoß behoben (Precheck-Selects) - LDAP-Filter: nur echte Benutzerkonten (keine Computer/Service-Accounts, Bit 512) - Rollen app-seitig: Sync ändert nie role/status, neue User immer user+inaktiv - DB-Cleanup: 82 Computer-/Service-Accounts aus lokaler User-Tabelle entfernt UX: - Dashboard: Vorlagen als Table-Liste + Column-Chart (Top 5 in %), 2 gleich große Spalten - Table-Listen (Dashboard/Vorlageneditor/Aufgaben) scrollbar bis Seitenende - Pagination 10/Seite im Dashboard, Sidebar-Label Dashboard
This commit is contained in:
@@ -49,7 +49,7 @@ router.post('/', adminMiddleware, validate(createUserSchema), async (req, res) =
|
||||
try {
|
||||
const hash = bcrypt.hashSync(password, 12); // Punkt 10: 12 rounds per OWASP
|
||||
const info = await db.prepare('INSERT INTO users (email, password, name, role, status, source) VALUES (?, ?, ?, ?, ?, \'local\')').run(email, hash, name, role, status);
|
||||
auditLog(req.user?.id, 'create_user', 'user', info.lastInsertRowid, `Created user: ${email}`);
|
||||
auditLog(req.user?.id, 'create_user', 'user', info.lastInsertRowid, `Created user: ${email}`, req);
|
||||
res.status(201).json({ id: info.lastInsertRowid, email, name, role, status, source: 'local' });
|
||||
} catch (err) {
|
||||
if (err.message && err.message.includes('UNIQUE constraint')) {
|
||||
@@ -62,7 +62,6 @@ router.post('/', adminMiddleware, validate(createUserSchema), async (req, res) =
|
||||
// Update user
|
||||
router.put('/:id', validate(updateUserSchema), async (req, res) => {
|
||||
const userId = parseInt(req.params.id);
|
||||
const { email, name, password, role, status, current_password } = req.validatedBody;
|
||||
|
||||
// VULN-04: Authorization check - only admin or self (with restrictions)
|
||||
const isSelf = req.user.id === userId;
|
||||
@@ -70,12 +69,18 @@ router.put('/:id', validate(updateUserSchema), async (req, res) => {
|
||||
if (!isAdmin && !isSelf) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung, diesen Nutzer zu bearbeiten.' });
|
||||
}
|
||||
// Non-admins may NOT change role or status (privilege escalation prevention)
|
||||
|
||||
// K1: Strip role/status FIRST for non-admins (BEFORE reading values) to prevent
|
||||
// privilege escalation via mass assignment. Earlier code destructured first
|
||||
// and deleted afterwards, which left the local copies intact.
|
||||
if (!isAdmin) {
|
||||
delete req.validatedBody.role;
|
||||
delete req.validatedBody.status;
|
||||
}
|
||||
|
||||
// Now safe to read — non-admins can never see role/status here
|
||||
const { email, name, password, role, status, current_password } = req.validatedBody;
|
||||
|
||||
// P7: Non-admins changing their own password must verify the current password
|
||||
if (!isAdmin && isSelf && password && password.trim()) {
|
||||
if (!current_password) {
|
||||
@@ -108,7 +113,7 @@ router.put('/:id', validate(updateUserSchema), async (req, res) => {
|
||||
return res.status(400).json({ error: 'Status muss "aktiv" oder "inaktiv" sein.' });
|
||||
}
|
||||
await db.prepare('UPDATE users SET role = ?, status = ? WHERE id = ?').run(finalRole, finalStatus, userId);
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated AD user role/status`);
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated AD user role/status`, req);
|
||||
return res.json({ id: userId, email: user.email, name: user.name, role: finalRole, status: finalStatus, source: user.source, username: user.username });
|
||||
}
|
||||
|
||||
@@ -139,7 +144,7 @@ router.put('/:id', validate(updateUserSchema), async (req, res) => {
|
||||
}
|
||||
// VULN-13: Invalidate all sessions for this user after password change
|
||||
await invalidateUserSessions(userId);
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated local user with new password (sessions invalidated)`);
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated local user with new password (sessions invalidated)`, req);
|
||||
res.json({ id: userId, email, name: finalName, role: finalRole, status: finalStatus, source: user.source, username: user.username });
|
||||
} else {
|
||||
try {
|
||||
@@ -150,7 +155,7 @@ router.put('/:id', validate(updateUserSchema), async (req, res) => {
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated local user`);
|
||||
auditLog(req.user?.id, 'update_user', 'user', userId, `Updated local user`, req);
|
||||
res.json({ id: userId, email, name: finalName, role: finalRole, status: finalStatus, source: user.source, username: user.username });
|
||||
}
|
||||
});
|
||||
@@ -165,7 +170,7 @@ router.delete('/:id', adminMiddleware, async (req, res) => {
|
||||
}
|
||||
const info = await db.prepare('DELETE FROM users WHERE id = ?').run(userId);
|
||||
if (info.changes === 0) return res.status(404).json({ error: 'Nutzer nicht gefunden.' });
|
||||
auditLog(req.user?.id, 'delete_user', 'user', userId, `Deleted user: ${user.email}`);
|
||||
auditLog(req.user?.id, 'delete_user', 'user', userId, `Deleted user: ${user.email}`, req);
|
||||
res.json({ message: 'Nutzer geloescht.' });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user