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:
119
backend/routes/templates.js
Normal file
119
backend/routes/templates.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Templates routes module.
|
||||
*
|
||||
* Punkt 8: Uses transactions for template updates (delete+insert steps).
|
||||
* Punkt 6: Uses better-sqlite3 synchronous API.
|
||||
*/
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { auditLog } = require('../auditLog');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
const { validate, createTemplateSchema, updateTemplateSchema } = require('../middleware/validation');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authMiddleware);
|
||||
|
||||
// List templates
|
||||
router.get('/', async (req, res) => {
|
||||
const templates = await db.prepare('SELECT * FROM templates ORDER BY id DESC').all();
|
||||
if (templates.length === 0) return res.json([]);
|
||||
|
||||
const templateIds = templates.map(t => t.id).filter(id => Number.isInteger(id));
|
||||
if (templateIds.length === 0) return res.json(templates.map(t => ({ ...t, steps: [] })));
|
||||
const placeholders = templateIds.map(() => '?').join(',');
|
||||
const steps = await db.prepare(`SELECT * FROM template_steps WHERE template_id IN (${placeholders}) ORDER BY step_order ASC`).all(...templateIds);
|
||||
|
||||
const result = templates.map(t => ({
|
||||
...t,
|
||||
steps: steps.filter(s => s.template_id === t.id)
|
||||
}));
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
// Create template (admin only) - Punkt 8: Transaction
|
||||
router.post('/', adminMiddleware, validate(createTemplateSchema), async (req, res) => {
|
||||
const { name, description, is_assignable, allows_file_upload, ad_create, steps } = req.validatedBody;
|
||||
|
||||
const assignable = is_assignable ? 1 : 0;
|
||||
const fileUpload = allows_file_upload ? 1 : 0;
|
||||
const adCreate = ad_create ? 1 : 0;
|
||||
|
||||
const insertTemplate = db.prepare('INSERT INTO templates (name, description, is_assignable, allows_file_upload, ad_create) VALUES (?, ?, ?, ?, ?)');
|
||||
const insertStep = db.prepare('INSERT INTO template_steps (template_id, page_num, label, type, step_order, email_domain, email_source_fields, dropdown_options, ad_field, hidden, ad_prefix) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
|
||||
// Punkt 8: Transaction for template + steps
|
||||
const createTemplate = db.transaction(async () => {
|
||||
const info = await insertTemplate.run(name, description, assignable, fileUpload, adCreate);
|
||||
const templateId = info.lastInsertRowid;
|
||||
|
||||
for (const [idx, step] of steps.entries()) {
|
||||
await insertStep.run(
|
||||
templateId, step.page_num || 1, step.label, step.type, idx + 1,
|
||||
step.email_domain || null, step.email_source_fields || null,
|
||||
step.dropdown_options || null, step.ad_field || null,
|
||||
step.hidden ? 1 : 0, step.ad_prefix || null
|
||||
);
|
||||
}
|
||||
return templateId;
|
||||
});
|
||||
|
||||
try {
|
||||
const templateId = await createTemplate();
|
||||
auditLog(req.user?.id, 'create_template', 'template', templateId, `Created template: ${name}`);
|
||||
res.status(201).json({ id: templateId, name, description, is_assignable: !!assignable, allows_file_upload: !!fileUpload, ad_create: !!adCreate, steps });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Interner Serverfehler.' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update template (admin only) - Punkt 8: Transaction
|
||||
router.put('/:id', adminMiddleware, validate(updateTemplateSchema), async (req, res) => {
|
||||
const templateId = parseInt(req.params.id);
|
||||
const { name, description, is_assignable, allows_file_upload, ad_create, steps } = req.validatedBody;
|
||||
|
||||
const assignable = is_assignable ? 1 : 0;
|
||||
const fileUpload = allows_file_upload ? 1 : 0;
|
||||
const adCreate = ad_create ? 1 : 0;
|
||||
|
||||
const updateTemplate = db.prepare('UPDATE templates SET name = ?, description = ?, is_assignable = ?, allows_file_upload = ?, ad_create = ? WHERE id = ?');
|
||||
const deleteSteps = db.prepare('DELETE FROM template_steps WHERE template_id = ?');
|
||||
const insertStep = db.prepare('INSERT INTO template_steps (template_id, page_num, label, type, step_order, email_domain, email_source_fields, dropdown_options, ad_field, hidden, ad_prefix) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
|
||||
// Punkt 8: Transaction for update + delete old steps + insert new steps
|
||||
const updateTemplateTransaction = db.transaction(async () => {
|
||||
const info = await updateTemplate.run(name, description, assignable, fileUpload, adCreate, templateId);
|
||||
if (info.changes === 0) throw new Error('NOT_FOUND');
|
||||
|
||||
await deleteSteps.run(templateId);
|
||||
|
||||
for (const [idx, step] of steps.entries()) {
|
||||
await insertStep.run(
|
||||
templateId, step.page_num || 1, step.label, step.type, idx + 1,
|
||||
step.email_domain || null, step.email_source_fields || null,
|
||||
step.dropdown_options || null, step.ad_field || null,
|
||||
step.hidden ? 1 : 0, step.ad_prefix || null
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await updateTemplateTransaction();
|
||||
auditLog(req.user?.id, 'update_template', 'template', templateId, `Updated template: ${name}`);
|
||||
res.json({ id: templateId, name, description, is_assignable: !!assignable, allows_file_upload: !!fileUpload, ad_create: !!adCreate, steps });
|
||||
} catch (err) {
|
||||
if (err.message === 'NOT_FOUND') return res.status(404).json({ error: 'Vorlage nicht gefunden.' });
|
||||
res.status(500).json({ error: 'Interner Serverfehler.' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete template (admin only)
|
||||
router.delete('/:id', adminMiddleware, async (req, res) => {
|
||||
const templateId = parseInt(req.params.id);
|
||||
const info = await db.prepare('DELETE FROM templates WHERE id = ?').run(templateId);
|
||||
if (info.changes === 0) return res.status(404).json({ error: 'Vorlage nicht gefunden.' });
|
||||
auditLog(req.user?.id, 'delete_template', 'template', templateId, null);
|
||||
res.json({ message: 'Vorlage gelöscht.' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user