Files
workflow-app/backend/routes/upload.js
Kühn 6be1791c62 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
2026-08-24 09:45:28 +02:00

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;