Files
workflow-app/backend/routes/ad.js
Kühn 1aec65dc95 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
2026-09-10 16:57:20 +02:00

124 lines
5.1 KiB
JavaScript

/**
* AD/LDAP routes module.
*/
const express = require('express');
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
const { auditLog } = require('../auditLog');
const { isLDAPConfigured } = require('../ldapSync');
const { browseOUTree, createADUser, checkADUserExists, deleteADUser } = require('../ldapOperations');
const { searchADGroups, addUserToGroups, browseADGroups } = require('../ldapOperations');
const { validate, createADUserSchema, deleteADUserSchema } = require('../middleware/validation');
const router = express.Router();
// LDAP Status Endpoint (public)
router.get('/status', (req, res) => {
res.json({ configured: isLDAPConfigured() });
});
// All other AD routes require auth
router.use(authMiddleware);
// Browse OU tree
router.get('/ou-tree', (req, res) => {
const { base } = req.query;
browseOUTree(base || undefined).then(ous => {
res.json(ous);
}).catch(err => {
console.error('[ERROR] GET /ad/ou-tree -', err.message);
res.status(500).json({ error: 'Interner Serverfehler.' });
});
});
// Search AD groups (all security groups)
router.get('/groups', (req, res) => {
const { q } = req.query;
if (!q || q.trim().length < 2) {
return res.json([]);
}
searchADGroups(q.trim()).then(groups => {
res.json(groups);
}).catch(err => {
console.error('[ERROR] GET /ad/groups -', err.message);
res.status(500).json({ error: 'Interner Serverfehler.' });
});
});
// Browse all AD groups (for tree display)
router.get('/groups-tree', (req, res) => {
browseADGroups().then(groups => {
res.json(groups);
}).catch(err => {
console.error('[ERROR] GET /ad/groups-tree -', err.message);
res.status(500).json({ error: 'Interner Serverfehler.' });
});
});
// Get AD create config
router.get('/create-config', (req, res) => {
res.json({
configured: isLDAPConfigured(),
createOU: process.env.LDAP_CREATE_OU || '',
upnSuffix: process.env.LDAP_UPN_SUFFIX || process.env.LDAP_BIND_USER?.split('@')[1] || '',
});
});
// Create AD user (admin only)
router.post('/create-user', adminMiddleware, validate(createADUserSchema), async (req, res) => {
const { ou, vorname, nachname, email, username, password, department, telefon, titel, displayName, physicalDeliveryOfficeName, company, description, wWWHomePage, streetAddress, postOfficeBox, l, st, postalCode, c, groups } = req.validatedBody;
try {
let sAMAccountName = username;
if (vorname && nachname) {
sAMAccountName = nachname.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue')
.replace(/Ä/g, 'Ae').replace(/Ö/g, 'Oe').replace(/Ü/g, 'Ue').replace(/ß/g, 'ss')
+ vorname.charAt(0).replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue')
.replace(/Ä/g, 'Ae').replace(/Ö/g, 'Oe').replace(/Ü/g, 'Ue').replace(/ß/g, 'ss');
sAMAccountName = sAMAccountName.replace(/[^a-zA-Z0-9]/g, '').substring(0, 20);
}
const existing = await checkADUserExists(username, sAMAccountName);
if (existing) {
return res.status(409).json({ error: 'Benutzername "' + username + '" existiert bereits im Active Directory.', dn: existing.distinguishedName });
}
const result = await createADUser({ ou, vorname, nachname, email, username, password, department, telefon, titel, displayName, physicalDeliveryOfficeName, company, description, wWWHomePage, streetAddress, postOfficeBox, l, st, postalCode, c });
if (result.warning && result.dn) {
auditLog(req.user?.id, 'ad.create-user', 'ad_user', null, `AD user created with warning: ${username} - ${result.warning}`, req);
} else {
auditLog(req.user?.id, 'ad.create-user', 'ad_user', null, `Created AD user: ${username}`, req);
}
// Add user to groups if specified
let groupResults = [];
if (groups && Array.isArray(groups) && groups.length > 0 && result.dn) {
try {
groupResults = await addUserToGroups(result.dn, groups);
const addedCount = groupResults.filter(r => r.status === 'added').length;
auditLog(req.user?.id, 'ad.create-user', 'ad_user', null, `Added ${username} to ${addedCount} group(s)`, req);
} catch (groupErr) {
console.error('[WARN] Gruppenzuweisung fehlgeschlagen:', groupErr.message);
groupResults = groups.map(dn => ({ dn, status: 'error', error: groupErr.message }));
}
}
res.status(201).json({ ...result, groupResults });
} catch (err) {
auditLog(req.user?.id, 'ad.create-user-failed', 'ad_user', null, `Failed to create AD user: ${username} - ${err.message}`, req);
res.status(500).json({ error: 'Interner Serverfehler.' });
}
});
// Delete AD user (admin only)
router.delete('/delete-user', adminMiddleware, validate(deleteADUserSchema), async (req, res) => {
const { dn } = req.validatedBody;
try {
await deleteADUser(dn);
auditLog(req.user?.id, 'ad.delete-user', 'ad_user', null, `Deleted AD user: ${dn}`, req);
res.json({ success: true, message: 'Benutzer erfolgreich gelöscht.' });
} catch (err) {
res.status(500).json({ error: 'Fehler beim Löschen des AD-Benutzers: ' + err.message });
}
});
module.exports = router;