DEV1.0: Initial commit - Workflow Portal with security fixes
- Backend: Express.js + PostgreSQL/SQLite with LDAP/AD integration - Frontend: React 18 + Vite + TailwindCSS/DaisyUI - Security fixes applied (2026-07 + 2026-08): - LDAP injection prevention, CSRF protection, HttpOnly cookies - Session hashing (SHA-256), account lockout, rate limiting - Input validation (zod), file upload security, CSP/HSTS headers - V3: express-rate-limit updated (ip-address SSRF fix) - V4: postcss updated (nanoid DoS fix) - V5: Rate-limit on /health endpoint - V6: Session rotation on login (session fixation prevention) - V9: Task values array limit (DoS prevention) - V10: Frontend XSS audit completed - Docker: Multi-stage build, non-root user, PostgreSQL + backup service
This commit is contained in:
83
backend/routes/stats.js
Normal file
83
backend/routes/stats.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Stats and audit-log routes module.
|
||||
*
|
||||
* Punkt 7: Single aggregated query for stats instead of 9 nested callbacks.
|
||||
*/
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
const { validateQuery, paginationSchema } = require('../middleware/validation');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authMiddleware);
|
||||
router.use(adminMiddleware);
|
||||
|
||||
// Punkt 7: Single aggregated stats query
|
||||
router.get('/stats', async (req, res) => {
|
||||
try {
|
||||
const rawStats = await db.prepare(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM users WHERE status = 'aktiv') as activeUsers,
|
||||
(SELECT COUNT(*) FROM users) as totalUsers,
|
||||
(SELECT COUNT(*) FROM tasks WHERE status = 'offen') as openTasks,
|
||||
(SELECT COUNT(*) FROM tasks WHERE status = 'erledigt') as completedTasks,
|
||||
(SELECT COUNT(*) FROM tasks) as totalTasks,
|
||||
(SELECT COUNT(*) FROM templates) as totalTemplates,
|
||||
(SELECT COUNT(*) FROM templates WHERE is_assignable = 1) as assignableTemplates,
|
||||
(SELECT COUNT(*) FROM users WHERE source = 'ad') as adUsers,
|
||||
(SELECT COUNT(*) FROM users WHERE source = 'local') as localUsers
|
||||
`).get();
|
||||
|
||||
// PostgreSQL lowercases aliases; normalize keys and coerce counts to numbers.
|
||||
const normalizeKey = (key) => key.toLowerCase();
|
||||
const keyMap = {
|
||||
activeusers: 'activeUsers',
|
||||
totalusers: 'totalUsers',
|
||||
opentasks: 'openTasks',
|
||||
completedtasks: 'completedTasks',
|
||||
totaltasks: 'totalTasks',
|
||||
totaltemplates: 'totalTemplates',
|
||||
assignabletemplates: 'assignableTemplates',
|
||||
adusers: 'adUsers',
|
||||
localusers: 'localUsers'
|
||||
};
|
||||
const stats = {};
|
||||
for (const [key, value] of Object.entries(rawStats)) {
|
||||
const normalized = normalizeKey(key);
|
||||
const newKey = keyMap[normalized] || normalized;
|
||||
stats[newKey] = typeof value === 'string' ? Number(value) : value;
|
||||
}
|
||||
|
||||
const topTemplates = await db.prepare(
|
||||
'SELECT t.id, t.name, COUNT(tk.id) as task_count FROM templates t LEFT JOIN tasks tk ON t.id = tk.template_id GROUP BY t.id ORDER BY task_count DESC LIMIT 5'
|
||||
).all();
|
||||
|
||||
const recentActivity = await db.prepare(
|
||||
'SELECT al.*, u.name as user_name, u.email as user_email FROM audit_log al LEFT JOIN users u ON al.user_id = u.id ORDER BY al.created_at DESC LIMIT 10'
|
||||
).all();
|
||||
|
||||
res.json({ ...stats, topTemplates, recentActivity });
|
||||
} catch (err) {
|
||||
console.error('[ERROR] GET /stats -', err.message);
|
||||
res.status(500).json({ error: 'Interner Serverfehler.' });
|
||||
}
|
||||
});
|
||||
|
||||
// Audit log with pagination (Punkt 16: bounded limits)
|
||||
router.get('/audit-log', validateQuery(paginationSchema), async (req, res) => {
|
||||
const { page, limit } = req.validatedQuery;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
try {
|
||||
const rows = await db.prepare('SELECT al.*, u.name as user_name, u.email as user_email FROM audit_log al LEFT JOIN users u ON al.user_id = u.id ORDER BY al.created_at DESC LIMIT ? OFFSET ?').all(limit, offset);
|
||||
const countRow = await db.prepare('SELECT COUNT(*) as total FROM audit_log').get();
|
||||
const total = countRow?.total || 0;
|
||||
res.json({ entries: rows, total, page, limit, totalPages: Math.ceil(total / limit) });
|
||||
} catch (err) {
|
||||
console.error('[ERROR] GET /audit-log -', err.message);
|
||||
res.status(500).json({ error: 'Interner Serverfehler.' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user