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

@@ -172,7 +172,8 @@ router.delete('/:folderName', authenticateToken, (req, res) => {
return res.status(403).json({ error: 'You do not have permission to delete this folder.' });
}
fs.rmdir(folderPath, { recursive: true }, (err) => {
// Node.js 14+ : fs.rm remplace fs.rmdir (deprecated)
fs.rm(folderPath, { recursive: true, force: true }, (err) => {
if (err) {
return res.status(500).json({ error: 'Error deleting the folder.' });
}

View File

@@ -0,0 +1,36 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const router = express.Router();
const authMiddleware = require('../../../Middlewares/authMiddleware');
const { ErrorLogger } = require('../../../config/logs');
// Quitter un dossier partagé
router.post('/leave', authMiddleware, async (req, res) => {
try {
const userId = req.userData.name;
const { folderName, folderOwner } = req.body;
if (!folderName || !folderOwner) {
return res.status(400).json({ error: 'Missing folderName or folderOwner' });
}
const collabPath = path.join(__dirname, '../../../data', 'collaboration.json');
let collabData = JSON.parse(fs.readFileSync(collabPath, 'utf8'));
const itemId = `folder-${folderName}`;
if (!collabData.activeFiles[itemId]) {
return res.status(404).json({ error: 'Shared folder not found' });
}
// Retirer l'utilisateur de la liste des collaborateurs
collabData.activeFiles[itemId].activeUsers = collabData.activeFiles[itemId].activeUsers.filter(u => u.id !== userId);
// Si plus aucun utilisateur, on peut supprimer la collaboration
if (collabData.activeFiles[itemId].activeUsers.length === 0) {
delete collabData.activeFiles[itemId];
}
fs.writeFileSync(collabPath, JSON.stringify(collabData, null, 2), 'utf8');
return res.json({ success: true });
} catch (error) {
ErrorLogger.error('Error leaving shared folder:', error);
return res.status(500).json({ error: 'Erreur lors du quit du dossier partagé.' });
}
});
module.exports = router;

View File

@@ -31,10 +31,43 @@ router.post('/', (req, res) => {
}
const file = files.file[0];
const userDir = path.join(process.cwd(), 'cdn-files', req.user.name);
const targetFolder = fields.targetFolder ? fields.targetFolder[0] : '';
const isSharedFolder = fields.isSharedFolder ? fields.isSharedFolder[0] === 'true' : false;
const ownerName = fields.ownerName ? fields.ownerName[0] : '';
// Construction du chemin cible avec le dossier spécifié
let userName = req.user.name;
// Si c'est un dossier partagé, utiliser le nom du propriétaire
if (isSharedFolder && ownerName) {
// Vérifier les permissions de collaboration
const collaborationFilePath = path.join(__dirname, '../../../data', 'collaboration.json');
try {
const collaborationData = JSON.parse(await fs.promises.readFile(collaborationFilePath, 'utf8'));
const itemId = `folder-${targetFolder}`;
const folderInfo = collaborationData.activeFiles[itemId];
// Vérifier si l'utilisateur a accès au dossier partagé
if (!folderInfo || !folderInfo.isCollaborative ||
!folderInfo.activeUsers.some(u => u.id === req.user.id)) {
return res.status(403).send('Accès refusé au dossier partagé');
}
userName = ownerName;
} catch (error) {
console.error('Error checking collaboration permissions:', error);
return res.status(500).send('Erreur lors de la vérification des permissions');
}
}
let userDir = path.join(process.cwd(), 'cdn-files', userName);
if (targetFolder) {
userDir = path.join(userDir, targetFolder);
}
const filename = fields.filename ? fields.filename[0] : file.originalFilename;
const filePath = path.join(userDir, filename);
// Récupérer les champs supplémentaires
const expiryDate = fields.expiryDate ? fields.expiryDate[0] : '';
const password = fields.password ? fields.password[0] : '';

View File

@@ -50,7 +50,7 @@ router.get('/', authMiddleware, async (req, res) => {
});
Promise.all([Promise.all(reports)]).then(([completedReports]) => {
res.render('paramAdminPrivacy&Security', { users: User, reports: completedReports });
res.render('paramAdminPrivacy&Security', { user: user, users: User, reports: completedReports });
});
} catch (err) {
console.error(err);

View File

@@ -61,7 +61,7 @@ router.get('/', authMiddleware, async (req, res) => {
});
Promise.all(logs).then(completed => {
res.render('paramAdminStats&Logs', { users: User, setup: setup, uptime, memoryUsage, cpuUsage, logs: completed });
res.render('paramAdminStats&Logs', { user: user, users: User, setup: setup, uptime, memoryUsage, cpuUsage, logs: completed });
});
});
});

View File

@@ -45,7 +45,7 @@ router.get('/', authMiddleware, async (req, res) => {
let end = start + limit;
let usersForPage = users.slice(start, end);
res.render('paramAdminUser', { users: usersForPage, setup: setup, pages: pages, currentPage: currentPage, limit: limit });
res.render('paramAdminUser', { user: user, users: usersForPage, setup: setup, pages: pages, currentPage: currentPage, limit: limit });
} catch (err) {
console.error(err);
res.status(500).send('Server Error');

View File

@@ -28,15 +28,54 @@ router.get('/', authMiddleware, async (req, res) => {
const data = fs.readFileSync(path.join(__dirname, '../../../data', 'user.json'), 'utf8');
const users = JSON.parse(data);
const user = users.find(user => user.name === req.user.name);
if (!user || user.role !== 'admin') {
console.log('Access denied');
console.log('Access denied');
return res.status(403).json({ message: "You do not have the necessary rights to access this resource." });
}
res.render('paramAdmin', { users: User, setup: setup });
// Calculer les stats pour le dashboard admin
let foldersCount = 0;
let totalSize = 0;
try {
const fileInfoData = fs.readFileSync(path.join(__dirname, '../../../data', 'file_info.json'), 'utf8');
const fileInfo = JSON.parse(fileInfoData);
// Compter les dossiers
users.forEach(u => {
const userFolderPath = path.join(__dirname, '../../../cdn-files', u.name);
if (fs.existsSync(userFolderPath)) {
const folders = fs.readdirSync(userFolderPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory());
foldersCount += folders.length;
}
});
// Calculer l'espace total utilisé
totalSize = fileInfo.reduce((sum, item) => sum + (item.size || 0), 0);
} catch (err) {
console.warn('Could not read file_info.json:', err.message);
}
// Stats système
const uptime = process.uptime();
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
res.render('paramAdmin', {
user: user,
users: User,
setup: setup,
stats: {
foldersCount,
totalSize,
uptime,
memoryUsage: Math.round(memoryUsage.heapUsed / 1024 / 1024), // MB
cpuUsage: Math.round((cpuUsage.user + cpuUsage.system) / 1000) // ms
}
});
} catch (err) {
console.error(err);
res.status(500).send('Server Error');

View File

@@ -80,7 +80,11 @@ router.get('/shared/:ownerName/:folderName', authMiddleware, async (req, res) =>
const availableExtensions = Array.from(new Set(fileDetails.map(file => file.extension)));
// Determine if current user is owner
const isOwner = folderInfo.activeUsers.length > 0 && folderInfo.activeUsers[0].id === userId;
res.render('folder', {
user: req.userData,
files: fileDetails,
folders,
allFolders,
@@ -90,7 +94,10 @@ router.get('/shared/:ownerName/:folderName', authMiddleware, async (req, res) =>
fileInfoNames,
userName,
isSharedFolder: true,
ownerName
ownerName,
isCollaborativeFolder: true,
isOwner,
currentUserId: userId
});
} catch (error) {
@@ -196,10 +203,47 @@ router.get('/:folderName', authMiddleware, async (req, res) => {
});
Promise.all(fileDetailsPromises)
.then(fileDetails => {
.then(async fileDetails => {
const availableExtensions = Array.from(new Set(fileDetails.map(file => file.extension)));
res.render('folder', { files: fileDetails, folders, allFolders, extensions: availableExtensions, currentFolder: currentFolderName, folderName: folderName, fileInfoNames, userName });
// Check if current folder is collaborative
let isCollaborativeFolder = false;
let isOwner = true; // By default, user is owner of their own folder
let collaborators = [];
try {
const collaborationFilePath = path.join(__dirname, '../../../data', 'collaboration.json');
const collaborationData = JSON.parse(await fs.promises.readFile(collaborationFilePath, 'utf8'));
const itemId = `folder-${folderName}`;
const folderInfo = collaborationData.activeFiles[itemId];
if (folderInfo && folderInfo.isCollaborative && folderInfo.activeUsers) {
isCollaborativeFolder = true;
collaborators = folderInfo.activeUsers;
// First user in activeUsers array is the owner
if (collaborators.length > 0) {
isOwner = collaborators[0].id === userRealId;
}
}
} catch (error) {
// If collaboration.json doesn't exist or can't be read, continue without collaboration info
console.log('No collaboration data found:', error.message);
}
res.render('folder', {
user: user,
files: fileDetails,
folders,
allFolders,
extensions: availableExtensions,
currentFolder: currentFolderName,
folderName: folderName,
fileInfoNames,
userName,
isCollaborativeFolder,
isOwner,
currentUserId: userRealId
});
})
.catch(error => {
console.error('Error processing file details:', error);

View File

@@ -116,7 +116,11 @@ router.get('/:userId/:filename', async (req, res) => {
await pipeline(readStream, res);
}
} catch (err) {
ErrorLogger.error('Error handling request:', err);
// Ne pas logger les fermetures prématurées côté client (comportement normal)
// Cela se produit quand l'utilisateur annule le téléchargement, ferme le navigateur, etc.
if (err.code !== 'ERR_STREAM_PREMATURE_CLOSE' && err.code !== 'ECONNRESET' && err.code !== 'EPIPE') {
ErrorLogger.error('Error handling request:', err);
}
if (!res.headersSent) {
res.status(500).send('Error reading file.');
}

View File

@@ -30,6 +30,7 @@ const ProfilUser = require('./Dpanel/Dashboard/ProfilUser.js');
const PofilPictureRoute = require('./Dpanel/API/ProfilPicture.js');
const CollaborationRoute = require('./Dpanel/API/Collaboration.js');
const UserSearchRoute = require('./Dpanel/API/UserSearch.js');
const SharedFoldersRoute = require('./Dpanel/API/SharedFolders.js');
const loginRoute = require('./Auth/Login.js');
const logoutRoute = require('./Auth/Logout.js');
@@ -78,6 +79,7 @@ router.use('/api/dpanel/dashboard/getfilefolder', getFileFolderRoute, logApiRequ
router.use('/api/dpanel/dashboard/profilpicture', PofilPictureRoute, logApiRequest);
router.use('/api/dpanel/collaboration', discordWebhookSuspisiousAlertMiddleware, logApiRequest, CollaborationRoute);
router.use('/api/dpanel/users/search', UserSearchRoute, logApiRequest);
router.use('/api/dpanel/sharedfolders', discordWebhookSuspisiousAlertMiddleware, logApiRequest, SharedFoldersRoute);
router.use('/auth/login', loginRoute);
router.use('/auth/logout', logoutRoute);