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
195 lines
6.0 KiB
JavaScript
195 lines
6.0 KiB
JavaScript
/**
|
|
* Middleware de rate limiting renforcé
|
|
* Protège contre les abus et les attaques par force brute
|
|
*/
|
|
|
|
const rateLimit = require('express-rate-limit');
|
|
const { logger } = require('../config/logs');
|
|
|
|
/**
|
|
* Rate limiter général pour toutes les requêtes
|
|
*/
|
|
const generalLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 1000, // Limite de 1000 requêtes par fenêtre
|
|
message: {
|
|
error: 'Trop de requêtes depuis cette adresse IP, veuillez réessayer plus tard.',
|
|
retryAfter: '15 minutes'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
// Skip pour localhost
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`Rate limit exceeded for ${req.ip} on ${req.path}`);
|
|
res.status(429).json({
|
|
error: 'Trop de requêtes',
|
|
message: 'Vous avez dépassé la limite de requêtes autorisées. Veuillez réessayer dans 15 minutes.',
|
|
retryAfter: 900
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter strict pour les endpoints sensibles (authentification)
|
|
*/
|
|
const authLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 5, // Seulement 5 tentatives de connexion
|
|
skipSuccessfulRequests: true, // Ne pas compter les tentatives réussies
|
|
message: {
|
|
error: 'Trop de tentatives de connexion, votre compte est temporairement verrouillé.',
|
|
retryAfter: '15 minutes'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`Auth rate limit exceeded for ${req.ip} on ${req.path}`);
|
|
res.status(429).json({
|
|
error: 'Compte temporairement verrouillé',
|
|
message: 'Trop de tentatives de connexion. Veuillez réessayer dans 15 minutes.',
|
|
retryAfter: 900
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter pour les uploads
|
|
*/
|
|
const uploadLimiter = rateLimit({
|
|
windowMs: 60 * 60 * 1000, // 1 heure
|
|
max: 100, // 100 uploads par heure
|
|
message: {
|
|
error: 'Limite d\'upload atteinte',
|
|
retryAfter: '1 heure'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`Upload rate limit exceeded for ${req.ip}`);
|
|
res.status(429).json({
|
|
error: 'Limite d\'upload dépassée',
|
|
message: 'Vous avez atteint la limite d\'uploads autorisés. Veuillez réessayer dans 1 heure.',
|
|
retryAfter: 3600
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter pour les API
|
|
*/
|
|
const apiLimiter = rateLimit({
|
|
windowMs: 1 * 60 * 1000, // 1 minute
|
|
max: 60, // 60 requêtes par minute
|
|
message: {
|
|
error: 'Limite d\'API atteinte',
|
|
retryAfter: '1 minute'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`API rate limit exceeded for ${req.ip} on ${req.path}`);
|
|
res.status(429).json({
|
|
error: 'Limite d\'API dépassée',
|
|
message: 'Vous avez dépassé la limite de requêtes API autorisées. Veuillez réessayer dans 1 minute.',
|
|
retryAfter: 60
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter pour la création de dossiers/fichiers
|
|
*/
|
|
const createLimiter = rateLimit({
|
|
windowMs: 10 * 60 * 1000, // 10 minutes
|
|
max: 50, // 50 créations par 10 minutes
|
|
message: {
|
|
error: 'Limite de création atteinte',
|
|
retryAfter: '10 minutes'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`Create rate limit exceeded for ${req.ip} on ${req.path}`);
|
|
res.status(429).json({
|
|
error: 'Limite de création dépassée',
|
|
message: 'Vous avez dépassé la limite de créations autorisées. Veuillez réessayer dans 10 minutes.',
|
|
retryAfter: 600
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter pour les suppressions
|
|
*/
|
|
const deleteLimiter = rateLimit({
|
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
max: 30, // 30 suppressions par 5 minutes
|
|
message: {
|
|
error: 'Limite de suppression atteinte',
|
|
retryAfter: '5 minutes'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`Delete rate limit exceeded for ${req.ip} on ${req.path}`);
|
|
res.status(429).json({
|
|
error: 'Limite de suppression dépassée',
|
|
message: 'Vous avez dépassé la limite de suppressions autorisées. Veuillez réessayer dans 5 minutes.',
|
|
retryAfter: 300
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Rate limiter strict pour la recherche d'utilisateurs (prévient l'énumération)
|
|
*/
|
|
const userSearchLimiter = rateLimit({
|
|
windowMs: 1 * 60 * 1000, // 1 minute
|
|
max: 10, // 10 recherches par minute
|
|
message: {
|
|
error: 'Limite de recherche atteinte',
|
|
retryAfter: '1 minute'
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: (req) => {
|
|
return req.ip === '127.0.0.1' || req.ip === '::1' || req.ip === '::ffff:127.0.0.1';
|
|
},
|
|
handler: (req, res) => {
|
|
logger.warn(`User search rate limit exceeded for ${req.ip}`);
|
|
res.status(429).json({
|
|
error: 'Limite de recherche dépassée',
|
|
message: 'Vous avez dépassé la limite de recherches autorisées. Veuillez réessayer dans 1 minute.',
|
|
retryAfter: 60
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
generalLimiter,
|
|
authLimiter,
|
|
uploadLimiter,
|
|
apiLimiter,
|
|
createLimiter,
|
|
deleteLimiter,
|
|
userSearchLimiter
|
|
};
|