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,194 @@
/**
* 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
};