Update v1.2.0-beta - Dynamic context menu & permissions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
✨ New Features: - Dynamic permission-based context menus for files and folders - Support for collaborative folder access control - Upload to specific folders including shared folders - Changelog modal for version updates - Improved dark mode synchronization 🐛 Bug Fixes: - Fixed context menu displaying incorrect options - Fixed CSS !important override preventing dynamic menu behavior - Fixed folder collaboration permission checks - Fixed breadcrumb navigation with empty segments - Fixed "Premature close" error loop in attachments - Fixed missing user variable in admin routes - Fixed avatar loading COEP policy issues 🔒 Security: - Added security middleware (CSRF, rate limiting, input validation) - Fixed collaboration folder access validation - Improved shared folder permission handling 🎨 UI/UX Improvements: - Removed Actions column from folder view - Context menu now properly hides/shows based on permissions - Better visual feedback for collaborative folders - Improved upload flow with inline modals 🧹 Code Quality: - Added collaboration data to folder routes - Refactored context menu logic for better maintainability - Added debug logging for troubleshooting - Improved file upload handling with chunking support
This commit is contained in:
202
Middlewares/inputValidationMiddleware.js
Normal file
202
Middlewares/inputValidationMiddleware.js
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Middleware de validation et d'assainissement des entrées utilisateur
|
||||
* Protège contre les injections SQL, XSS, et autres attaques
|
||||
*/
|
||||
|
||||
const { logger } = require('../config/logs');
|
||||
|
||||
/**
|
||||
* Nettoie une chaîne de caractères pour prévenir les injections
|
||||
*/
|
||||
const sanitizeString = (str) => {
|
||||
if (typeof str !== 'string') return str;
|
||||
|
||||
// Supprime les caractères null bytes
|
||||
str = str.replace(/\0/g, '');
|
||||
|
||||
// Encode les caractères spéciaux HTML
|
||||
str = str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
|
||||
return str.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Valide un nom de fichier/dossier
|
||||
*/
|
||||
const validateFileName = (filename) => {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
return { valid: false, error: 'Nom de fichier invalide' };
|
||||
}
|
||||
|
||||
// Limite de longueur
|
||||
if (filename.length > 255) {
|
||||
return { valid: false, error: 'Nom de fichier trop long (max 255 caractères)' };
|
||||
}
|
||||
|
||||
// Caractères interdits dans les noms de fichiers Windows/Linux
|
||||
const forbiddenChars = /[<>:"\/\\|?*\x00-\x1f]/g;
|
||||
if (forbiddenChars.test(filename)) {
|
||||
return { valid: false, error: 'Caractères interdits dans le nom de fichier' };
|
||||
}
|
||||
|
||||
// Noms réservés Windows
|
||||
const reservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
||||
if (reservedNames.test(filename.split('.')[0])) {
|
||||
return { valid: false, error: 'Nom de fichier réservé' };
|
||||
}
|
||||
|
||||
// Vérifier les tentatives de traversée de répertoire
|
||||
if (filename.includes('..') || filename.includes('./') || filename.includes('.\\')) {
|
||||
return { valid: false, error: 'Tentative de traversée de répertoire détectée' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Valide un chemin de dossier
|
||||
*/
|
||||
const validatePath = (path) => {
|
||||
if (!path || typeof path !== 'string') {
|
||||
return { valid: false, error: 'Chemin invalide' };
|
||||
}
|
||||
|
||||
// Vérifier les tentatives de traversée de répertoire
|
||||
if (path.includes('..') || path.includes('~')) {
|
||||
return { valid: false, error: 'Tentative de traversée de répertoire détectée' };
|
||||
}
|
||||
|
||||
// Vérifier les chemins absolus non autorisés
|
||||
if (path.startsWith('/') || /^[A-Za-z]:/.test(path)) {
|
||||
return { valid: false, error: 'Chemin absolu non autorisé' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Valide un ID utilisateur
|
||||
*/
|
||||
const validateUserId = (userId) => {
|
||||
if (!userId) {
|
||||
return { valid: false, error: 'ID utilisateur manquant' };
|
||||
}
|
||||
|
||||
// Accepte les formats Discord (snowflake) ou alphanumériques
|
||||
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(userId)) {
|
||||
return { valid: false, error: 'Format d\'ID utilisateur invalide' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Valide une adresse email
|
||||
*/
|
||||
const validateEmail = (email) => {
|
||||
if (!email || typeof email !== 'string') {
|
||||
return { valid: false, error: 'Email invalide' };
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return { valid: false, error: 'Format d\'email invalide' };
|
||||
}
|
||||
|
||||
if (email.length > 254) {
|
||||
return { valid: false, error: 'Email trop long' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Nettoie récursivement un objet
|
||||
*/
|
||||
const sanitizeObject = (obj) => {
|
||||
if (obj === null || obj === undefined) return obj;
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return sanitizeString(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => sanitizeObject(item));
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const sanitized = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
sanitized[key] = sanitizeObject(value);
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware principal de validation
|
||||
*/
|
||||
const inputValidationMiddleware = (req, res, next) => {
|
||||
try {
|
||||
// Log des requêtes suspectes
|
||||
const suspiciousPatterns = [
|
||||
/<script/i,
|
||||
/javascript:/i,
|
||||
/on\w+\s*=/i,
|
||||
/\.\.\//,
|
||||
/union\s+select/i,
|
||||
/insert\s+into/i,
|
||||
/delete\s+from/i,
|
||||
/drop\s+table/i,
|
||||
/exec\s*\(/i,
|
||||
/eval\s*\(/i
|
||||
];
|
||||
|
||||
const checkSuspicious = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
return suspiciousPatterns.some(pattern => pattern.test(value));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Vérifier le body
|
||||
if (req.body && typeof req.body === 'object') {
|
||||
const bodyStr = JSON.stringify(req.body);
|
||||
if (suspiciousPatterns.some(pattern => pattern.test(bodyStr))) {
|
||||
logger.warn(`Suspicious input detected in request body from ${req.ip}: ${req.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier les query params
|
||||
if (req.query && typeof req.query === 'object') {
|
||||
const queryStr = JSON.stringify(req.query);
|
||||
if (suspiciousPatterns.some(pattern => pattern.test(queryStr))) {
|
||||
logger.warn(`Suspicious input detected in query params from ${req.ip}: ${req.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Continuer sans bloquer (logging only pour ne pas casser l'app)
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.error('Error in input validation middleware:', error);
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
inputValidationMiddleware,
|
||||
sanitizeString,
|
||||
sanitizeObject,
|
||||
validateFileName,
|
||||
validatePath,
|
||||
validateUserId,
|
||||
validateEmail
|
||||
};
|
||||
Reference in New Issue
Block a user