diff --git a/backend/src/index.ts b/backend/src/index.ts index 1a049b70..2208ae88 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -422,9 +422,36 @@ if (process.env.NODE_ENV === 'production') { // body-parser wirft 413 (PayloadTooLargeError) bzw. 400 (SyntaxError) mit einem // `status`-Feld. Ohne Respektierung werden legitime Client-Fehler als 500 // kaschiert und landen als "Interner Serverfehler" beim User. -app.use((err: Error & { status?: number; type?: string }, req: express.Request, res: express.Response, next: express.NextFunction) => { - console.error(err.stack); - const status = typeof err.status === 'number' && err.status >= 400 && err.status < 600 ? err.status : 500; +// +// Multer-Errors werden hier sauber abgefangen statt als 500 zu enden: +// - MulterError (z.B. LIMIT_FILE_SIZE) → 413 +// - LIMIT_UNEXPECTED_FILE / -PART_COUNT etc. → 400 +// - fileFilter-Errors (unzulässiger Typ) → 415 +// Pentest 2026-05-30, INFO: WebP/GIF-Uploads (und alle anderen vom +// fileFilter abgelehnten Typen) lieferten vorher 500 mit Stack-Trace. +app.use((err: any, req: express.Request, res: express.Response, _next: express.NextFunction) => { + console.error(err?.stack || err); + + // Multer-spezifische Fehler (importiert as namespace) + if (err?.name === 'MulterError') { + const code = err.code; + if (code === 'LIMIT_FILE_SIZE') { + return res.status(413).json({ success: false, error: 'Datei ist zu groß' }); + } + return res.status(400).json({ + success: false, + error: 'Upload-Fehler: ' + (err.message || code || 'unbekannt'), + }); + } + + // fileFilter hat cb(new Error(...), false) gerufen – kein MulterError, + // sondern unsere eigene Reject-Nachricht ("Nur PDF, JPG, ... erlaubt"). + const msg = typeof err?.message === 'string' ? err.message : ''; + if (/sind erlaubt|nicht erlaubt/i.test(msg)) { + return res.status(415).json({ success: false, error: msg }); + } + + const status = typeof err?.status === 'number' && err.status >= 400 && err.status < 600 ? err.status : 500; let message = 'Interner Serverfehler'; if (status === 413) message = 'Anfrage zu groß'; else if (status === 400 && (err.type === 'entity.parse.failed' || err instanceof SyntaxError)) { diff --git a/backend/src/routes/upload.routes.ts b/backend/src/routes/upload.routes.ts index 8c297e3f..2cde7a44 100644 --- a/backend/src/routes/upload.routes.ts +++ b/backend/src/routes/upload.routes.ts @@ -38,12 +38,18 @@ const fileFilter = ( file: Express.Multer.File, cb: multer.FileFilterCallback ) => { - // Nur PDFs und Bilder erlauben - const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/jpg']; + // PDFs + gängige Web-Bildformate. WebP + GIF nachgezogen 2026-05-30 + // (Pentest INFO: WebP/GIF lieferten 500 statt sauberem 4xx, weil + // erlaubter MIME-Type fehlte und der fileFilter dann throwte). + const allowedTypes = [ + 'application/pdf', + 'image/jpeg', 'image/jpg', 'image/png', + 'image/gif', 'image/webp', + ]; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { - cb(new Error('Nur PDF, JPG und PNG Dateien sind erlaubt')); + cb(new Error('Nur PDF, JPG, PNG, GIF und WebP-Dateien sind erlaubt')); } };