- 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
65 lines
2.4 KiB
JavaScript
65 lines
2.4 KiB
JavaScript
/**
|
|
* File upload routes module.
|
|
*/
|
|
const express = require('express');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const multer = require('multer');
|
|
const { authMiddleware } = require('../middleware/auth');
|
|
const { uploadLimiter } = require('../middleware/rateLimit');
|
|
const { auditLog } = require('../auditLog');
|
|
|
|
const router = express.Router();
|
|
|
|
// File upload setup
|
|
const uploadDir = path.join(__dirname, '..', 'data', 'uploads');
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
|
|
// VULN-08/09: Secure file upload
|
|
const ALLOWED_MIMES = ['application/pdf', 'image/png', 'image/jpeg', 'image/gif', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
|
|
const ALLOWED_EXTS = ['.pdf', '.png', '.jpg', '.jpeg', '.gif', '.txt', '.doc', '.docx'];
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => cb(null, uploadDir),
|
|
filename: (req, file, cb) => {
|
|
const safeName = path.basename(file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
const ext = path.extname(safeName).toLowerCase();
|
|
const safeExt = ALLOWED_EXTS.includes(ext) ? ext : '.bin';
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
cb(null, uniqueSuffix + '-' + safeName.replace(/\.[^.]+$/, '') + safeExt);
|
|
},
|
|
});
|
|
|
|
const upload = multer({
|
|
storage,
|
|
limits: { fileSize: 10 * 1024 * 1024 },
|
|
fileFilter: (req, file, cb) => {
|
|
if (ALLOWED_MIMES.includes(file.mimetype)) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Dateityp nicht erlaubt. Erlaubt: PDF, PNG, JPG, GIF, TXT, DOC, DOCX.'));
|
|
}
|
|
},
|
|
});
|
|
|
|
// P12: Serve uploads as attachments (prevent XSS) - requires authentication
|
|
router.use('/uploads', authMiddleware, express.static(uploadDir, {
|
|
setHeaders: (res) => {
|
|
res.setHeader('Content-Disposition', 'attachment');
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
},
|
|
}));
|
|
|
|
// Upload endpoint - Punkt 23: Rate limited per user
|
|
router.post('/', authMiddleware, uploadLimiter, upload.single('file'), (req, res) => {
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: 'Keine Datei hochgeladen.' });
|
|
}
|
|
const fileUrl = '/uploads/' + req.file.filename;
|
|
auditLog(req.user?.id, 'file_upload', null, null, `Uploaded: ${req.file.filename} (${req.file.size} bytes)`);
|
|
res.json({ filename: req.file.filename, originalname: req.file.originalname, url: fileUrl, size: req.file.size });
|
|
});
|
|
|
|
module.exports = router; |