Files
CDN-APP-INSIDER/routes/Dpanel/API/Upload.js
dinawo 76dc23c861 security: fix vulnerabilities and update security hardening (2026-03-12)
Code security fixes:
- Fixed 3 critical auth bypass bugs (user.jso, typo → user.json) in RenameFile, NewFolder, DeleteFolder API routes
- Added URL validation (HTTP/HTTPS only) on ProfilPicture and BackgroundCustom endpoints to prevent stored XSS/CSS injection
- Added path traversal protection in Upload.js (resolved path boundary check)
- Removed unsafe-eval from CSP script-src directive
- Removed information disclosure in BuildMetaData error responses
- Removed unused child_process import in BuildMetaData.js

Version bump: 1.2.1-beta → 1.2.2-beta
2026-03-12 17:16:16 +01:00

166 lines
6.6 KiB
JavaScript

const express = require('express');
const fs = require('fs');
const path = require('path');
const multiparty = require('multiparty');
const router = express.Router();
const bcrypt = require('bcrypt');
// Limite de taille de fichier à 10 Go
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go
// Crée le dossier temporaire à la racine s'il n'existe pas
const tempDir = path.join(process.cwd(), 'temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
router.post('/', (req, res) => {
const form = new multiparty.Form({
uploadDir: tempDir,
maxFilesSize: MAX_FILE_SIZE,
});
form.parse(req, async (err, fields, files) => {
if (err) {
console.error('Error parsing the file:', err);
return res.status(400).send('Error during the file upload');
}
if (!files.file || files.file.length === 0) {
return res.status(400).send('No file uploaded');
}
const file = files.file[0];
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);
// Path traversal protection: ensure resolved path stays within user directory
const resolvedUserDir = path.resolve(process.cwd(), 'cdn-files', userName);
const resolvedFilePath = path.resolve(filePath);
if (!resolvedFilePath.startsWith(resolvedUserDir + path.sep) && resolvedFilePath !== resolvedUserDir) {
if (fs.existsSync(file.path)) fs.unlinkSync(file.path);
return res.status(403).send('Path traversal detected');
}
// Récupérer les champs supplémentaires
const expiryDate = fields.expiryDate ? fields.expiryDate[0] : '';
const password = fields.password ? fields.password[0] : '';
// Hasher le mot de passe si présent
const saltRounds = 10;
let hashedPassword = '';
if (password) {
hashedPassword = await bcrypt.hash(password, saltRounds);
}
// Crée le répertoire s'il n'existe pas
if (!fs.existsSync(userDir)) {
fs.mkdirSync(userDir, { recursive: true });
}
// Lecture en chunks pour plus de performances
const readStream = fs.createReadStream(file.path, { highWaterMark: 1024 * 1024 });
const writeStream = fs.createWriteStream(filePath, { flags: 'a' });
readStream.pipe(writeStream);
readStream.on('end', async () => {
// Supprimer le fichier temporaire
fs.unlinkSync(file.path);
// Vérifier que le nom du fichier suit bien le format attendu
const fileNamePattern = /^\d{8}_[A-Z0-9]{6}_.*$/;
if (!fileNamePattern.test(filename)) {
console.warn('Le fichier uploadé ne suit pas le format de nom sécurisé attendu:', filename);
}
// Mettre à jour file_info.json si password ou expiryDate présent
if (expiryDate || password) {
const fileInfo = {
fileName: filename,
expiryDate: expiryDate,
password: hashedPassword,
Id: req.user.id,
path: filePath
};
try {
let data = [];
const fileInfoPath = path.join(__dirname, '../../../data', 'file_info.json');
if (fs.existsSync(fileInfoPath)) {
const existingData = await fs.promises.readFile(fileInfoPath, 'utf8');
data = JSON.parse(existingData);
if (!Array.isArray(data)) {
data = [];
}
}
data.push(fileInfo);
await fs.promises.writeFile(fileInfoPath, JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error updating file_info.json:', error);
}
}
res.status(200).send({
message: 'File uploaded successfully.',
filename: filename
});
});
readStream.on('error', (err) => {
console.error('Error reading the file:', err);
// Nettoyer le fichier temporaire en cas d'erreur
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
res.status(500).send({ message: 'Error uploading file.' });
});
writeStream.on('error', (err) => {
console.error('Error writing the file:', err);
// Nettoyer le fichier temporaire en cas d'erreur
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
res.status(500).send({ message: 'Error uploading file.' });
});
});
});
module.exports = router;