chore: Enhance image upload validation with magic byte checks
This commit is contained in:
@@ -6,6 +6,20 @@ import {requireAuth, requireEmailVerified} from '../middleware/auth.js';
|
|||||||
import * as S3Service from '../services/S3Service.js';
|
import * as S3Service from '../services/S3Service.js';
|
||||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||||
|
|
||||||
|
const MAGIC_BYTES: Record<string, Buffer[]> = {
|
||||||
|
'image/jpeg': [Buffer.from([0xff, 0xd8, 0xff])],
|
||||||
|
'image/jpg': [Buffer.from([0xff, 0xd8, 0xff])],
|
||||||
|
'image/png': [Buffer.from([0x89, 0x50, 0x4e, 0x47])],
|
||||||
|
'image/gif': [Buffer.from('GIF87a'), Buffer.from('GIF89a')],
|
||||||
|
'image/webp': [Buffer.from('RIFF')],
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateMagicBytes(buffer: Buffer, mimetype: string): boolean {
|
||||||
|
const signatures = MAGIC_BYTES[mimetype];
|
||||||
|
if (!signatures) return false;
|
||||||
|
return signatures.some(sig => buffer.subarray(0, sig.length).equals(sig));
|
||||||
|
}
|
||||||
|
|
||||||
// Configure multer for file uploads (memory storage)
|
// Configure multer for file uploads (memory storage)
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: multer.memoryStorage(),
|
storage: multer.memoryStorage(),
|
||||||
@@ -13,12 +27,12 @@ const upload = multer({
|
|||||||
fileSize: 10 * 1024 * 1024, // 10MB max file size
|
fileSize: 10 * 1024 * 1024, // 10MB max file size
|
||||||
},
|
},
|
||||||
fileFilter: (_req, file, cb) => {
|
fileFilter: (_req, file, cb) => {
|
||||||
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
|
||||||
if (allowedMimeTypes.includes(file.mimetype)) {
|
if (allowedMimeTypes.includes(file.mimetype)) {
|
||||||
cb(null, true);
|
cb(null, true);
|
||||||
} else {
|
} else {
|
||||||
cb(new Error('Only image files are allowed (JPEG, PNG, GIF, WebP, SVG)'));
|
cb(new Error('Only image files are allowed (JPEG, PNG, GIF, WebP)'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -48,6 +62,12 @@ export class Uploads {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateMagicBytes(req.file.buffer, req.file.mimetype)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'File contents do not match the declared image type',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Upload file to S3/Minio
|
// Upload file to S3/Minio
|
||||||
const result = await S3Service.uploadFile({
|
const result = await S3Service.uploadFile({
|
||||||
file: req.file.buffer,
|
file: req.file.buffer,
|
||||||
|
|||||||
Reference in New Issue
Block a user