secure-express-setup
Version:
Military-grade one-command security setup for Express.js applications
66 lines (60 loc) • 2.02 kB
JavaScript
// lib/fileValidation.js
/**
* File validation helper that dynamically imports 'file-type' (ESM-only)
* Exports a factory function (setupFileValidation) that returns { validateFile, middleware }
*/
function setupFileValidation() {
const allowedMimeTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'text/plain'
];
// lazy detector that works with ESM-only file-type v18+
async function detectFileType(buffer) {
if (!buffer) return null;
const ft = await import('file-type');
if (ft && typeof ft.fileTypeFromBuffer === 'function') {
return await ft.fileTypeFromBuffer(buffer);
}
if (ft && typeof ft.fromBuffer === 'function') {
return await ft.fromBuffer(buffer);
}
return null;
}
async function validateFile(buffer, allowedTypes = allowedMimeTypes) {
try {
const type = await detectFileType(buffer);
if (!type) throw new Error('Unable to determine file type');
if (!allowedTypes.includes(type.mime)) {
throw new Error(`File type ${type.mime} not allowed`);
}
return { valid: true, mime: type.mime, ext: type.ext };
} catch (err) {
return { valid: false, error: err.message };
}
}
return {
validateFile,
middleware: (allowedTypes) => {
return async (req, res, next) => {
try {
if (!req.file && !req.files) return next();
const files = req.files || [req.file];
for (const file of files) {
const result = await validateFile(file.buffer, allowedTypes);
if (!result.valid) {
return res.status(400).json({ error: 'Invalid file type', details: result.error });
}
}
next();
} catch (err) {
return res.status(500).json({ error: 'File validation error', details: err.message });
}
};
}
};
}
module.exports = setupFileValidation;