fix: respect allowed_file_types setting for upload validation (#203)

The "Allowed File Types" admin setting was stored in the database but
never actually read during upload validation. Both frontend and backend
used hardcoded MIME type lists, causing video uploads (e.g. MP4) to be
rejected even when explicitly added to the setting.

Changes:
- Add getAllowedMimeTypes() to uploadSettings service that reads the
  general_allowed_file_types DB setting and converts extensions to MIME types
- Backend admin upload route now resolves allowed types from settings
  before multer processes files (via resolveAllowedTypes middleware)
- Backend gallery upload route uses dynamic allowed types from settings
- Expose allowed_file_types in public settings API for gallery clients
- Frontend PhotoUpload and UserPhotoUpload components now derive allowed
  MIME types from settings instead of hardcoded image-only lists
- Add shared fileTypes.ts utility for extension-to-MIME conversion

Closes #203
This commit is contained in:
Paul Nothaft
2026-03-01 14:36:34 +01:00
parent 33483cf32d
commit fe07a148f1
8 changed files with 221 additions and 36 deletions
+48
View File
@@ -0,0 +1,48 @@
/**
* Maps file extensions to MIME types for upload validation.
*/
const EXTENSION_TO_MIME: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
gif: 'image/gif',
mp4: 'video/mp4',
m4v: 'video/mp4',
webm: 'video/webm',
mov: 'video/quicktime',
avi: 'video/x-msvideo',
};
const DEFAULT_ALLOWED = 'jpg,jpeg,png,webp';
/**
* Convert a comma-separated extension string (e.g. "jpg,png,mp4") to an
* array of unique MIME types.
*/
export function extensionsToMimeTypes(extString?: string | null): string[] {
const input = extString?.trim() || DEFAULT_ALLOWED;
const mimeSet = new Set<string>();
input.split(',').forEach(ext => {
const cleaned = ext.trim().toLowerCase().replace(/^\./, '');
const mime = EXTENSION_TO_MIME[cleaned];
if (mime) {
mimeSet.add(mime);
}
});
if (mimeSet.size === 0) {
return extensionsToMimeTypes(DEFAULT_ALLOWED);
}
return Array.from(mimeSet);
}
/**
* Convert a comma-separated extension string to an HTML `accept` attribute
* value, e.g. "image/jpeg,image/png,video/mp4".
*/
export function extensionsToAcceptString(extString?: string | null): string {
return extensionsToMimeTypes(extString).join(',');
}