From 82cce365f77f04827d1159862e1b7eee073c08cc Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 22 Apr 2026 16:16:35 +0200 Subject: [PATCH] fix: validate image-only uploads and respect allowed_file_types setting for journey photos Add fileFilter to the journey photo multer config (shared by entry photo upload and gallery upload routes): - Rejects any non-image MIME type (including SVG which carries XSS risk) - Checks the extension against the admin-configured allowed_file_types setting (same getAllowedExtensions() used by the trip file upload route) - Returns HTTP 400 with a descriptive message on rejection Also fix the global error handler to return err.message for 4xx responses instead of the generic 'Internal server error', so fileFilter rejections produce a readable error on the client. --- server/src/app.ts | 6 ++++-- server/src/routes/journey.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/server/src/app.ts b/server/src/app.ts index bae6a820..9bca8fcb 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -372,8 +372,10 @@ export function createApp(): express.Application { } else { console.error('Unhandled error:', err); } - const status = err.statusCode || 500; - res.status(status).json({ error: 'Internal server error' }); + const status = err.statusCode || err.status || 500; + // Expose the message for client errors (4xx); keep 'Internal server error' for 5xx. + const message = status < 500 ? err.message : 'Internal server error'; + res.status(status).json({ error: message }); }); return app; diff --git a/server/src/routes/journey.ts b/server/src/routes/journey.ts index 46b57fbe..1336bd50 100644 --- a/server/src/routes/journey.ts +++ b/server/src/routes/journey.ts @@ -9,6 +9,7 @@ import * as svc from '../services/journeyService'; import { db } from '../db/database'; import { createOrUpdateJourneyShareLink, getJourneyShareLink, deleteJourneyShareLink, getPublicJourney } from '../services/journeyShareService'; import { uploadToImmich } from '../services/memories/immichService'; +import { getAllowedExtensions } from '../services/fileService'; const router = express.Router(); @@ -25,9 +26,26 @@ const storage = multer.diskStorage({ }, }); +const imageFilter: multer.Options['fileFilter'] = (_req, file, cb) => { + if (!file.mimetype.startsWith('image/') || file.mimetype.includes('svg')) { + const err: Error & { statusCode?: number } = new Error('Only image files are allowed'); + err.statusCode = 400; + return cb(err); + } + const ext = path.extname(file.originalname).toLowerCase().replace('.', ''); + const allowed = getAllowedExtensions().split(',').map(e => e.trim().toLowerCase()); + if (!allowed.includes('*') && !allowed.includes(ext)) { + const err: Error & { statusCode?: number } = new Error(`File type .${ext} is not allowed`); + err.statusCode = 400; + return cb(err); + } + cb(null, true); +}; + const upload = multer({ storage, limits: { fileSize: 20 * 1024 * 1024 }, + fileFilter: imageFilter, }); // ── Static prefix routes (MUST come before /:id) ─────────────────────────