/** * 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, '/'); 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 = [ /