Update v1.2.0-beta - Dynamic context menu & permissions
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:
2025-10-25 23:55:51 +02:00
parent 58b57fbb84
commit 2df1b28962
33 changed files with 6275 additions and 1462 deletions

View 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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
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
};