V1.0.0-beta.17 Update 2
All checks were successful
continuous-integration/drone Build is passing

This commit is contained in:
2024-12-15 00:49:12 +01:00
parent 243ab5f55e
commit f7658eca22
15 changed files with 1457 additions and 501 deletions

View File

@@ -1,206 +1,87 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const multiparty = require('multiparty');
const router = express.Router();
const fileUpload = require('express-fileupload');
const { loggers } = require('winston');
const ncp = require('ncp');
const util = require('util');
const configFile = fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8')
const config = JSON.parse(configFile);
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const authMiddleware = require('../../../Middlewares/authMiddleware');
const { getUserData, getSetupData } = require('../../../Middlewares/watcherMiddleware');
const { logger, logRequestInfo, ErrorLogger, authLogger } = require('../../../config/logs');
let setupData = getSetupData();
let userData = getUserData();
router.use(bodyParser.json());
// Limite de taille de fichier à 10 Go
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go
/**
* @swagger
* /dpanel/upload?token={token}:
* post:
* security:
* - bearerAuth: []
* tags:
* - File
* summary: Upload a file
* description: This route allows you to upload a file. It requires a valid JWT token in the Authorization header.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* description: The file to upload
* expiryDate:
* type: string
* format: date-time
* description: The expiry date of the file
* password:
* type: string
* description: The password to protect the file
* parameters:
* - in: header
* name: Authorization
* required: true
* schema:
* type: string
* description: The JWT token of your account to have access
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* 400:
* description: Bad Request
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* 401:
* description: Unauthorized
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* 500:
* description: Error uploading the file
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* error:
* type: string
*/
function authenticateToken(req, res, next) {
let token = null;
const authHeader = req.headers['authorization'];
if (authHeader) {
token = authHeader.split(' ')[1];
} else if (req.query.token) {
token = req.query.token;
}
if (token == null) {
if (req.user) {
return next();
} else {
return res.status(401).json({ message: 'Unauthorized' });
}
}
fs.readFile(path.join(__dirname, '../../../data', 'user.jso,'), 'utf8', (err, data) => {
if (err) {
console.error('Error reading user.jso,:', err);
return res.status(401).json({ message: 'Unauthorized' });
}
const users = JSON.parse(data);
const user = users.find(u => u.token === token);
if (user) {
req.user = user;
next();
} else {
return res.status(401).json({ message: 'Unauthorized' });
}
});
// 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.get('/', (req, res) => {
res.status(400).json({ error: 'Bad Request. The request cannot be fulfilled due to bad syntax or missing parameters.' });
});
router.use(fileUpload({
limits: { fileSize: 15 * 1024 * 1024 * 1024 },
}));
router.post('/', authenticateToken, async (req, res) => {
try {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('5410 - Download error, please try again later.');
router.post('/', (req, res) => {
const form = new multiparty.Form({
uploadDir: tempDir,
maxFilesSize: MAX_FILE_SIZE,
});
form.parse(req, (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 = req.files.file;
const userId = req.user.name;
const Id = req.user.id;
const uploadDir = path.join('cdn-files', userId);
const originalFileName = file.name;
const domain = config.domain || 'mydomain.com';
let expiryDate = req.body.expiryDate;
let password = req.body.password;
const file = files.file[0];
// Modifier le chemin pour être relatif à la racine
const userDir = path.join(process.cwd(), 'cdn-files', req.user.name);
// Utiliser le nom sécurisé fourni par le client
const filename = fields.filename ? fields.filename[0] : file.originalFilename;
const filePath = path.join(userDir, filename);
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
// Crée le répertoire s'il n'existe pas
if (!fs.existsSync(userDir)) {
fs.mkdirSync(userDir, { recursive: true });
}
file.mv(path.join(uploadDir, originalFileName), async (err) => {
if (err) {
console.error(err);
return res.status(500).send({ message: 'Error downloading file.' });
// 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', () => {
// 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);
}
res.status(200).send({
message: 'File uploaded successfully.',
filename: filename
});
});
const fileExtension = path.extname(originalFileName).toLowerCase();
const bcrypt = require('bcrypt');
const saltRounds = 10;
let hashedPassword = '';
if (password) {
hashedPassword = bcrypt.hashSync(password, saltRounds);
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.' });
});
const fileInfo = {
fileName: originalFileName,
expiryDate: expiryDate || '',
password: hashedPassword,
Id: Id,
path: path.join(uploadDir, originalFileName)
};
if (expiryDate || password) {
let data = [];
if (fs.existsSync(path.join(__dirname, '../../../data', 'file_info.json'))) {
const existingData = await fs.promises.readFile(path.join(__dirname, '../../../data', 'file_info.json'), 'utf8');
data = JSON.parse(existingData);
if (!Array.isArray(data)) {
data = [];
}
}
data.push(fileInfo);
await fs.promises.writeFile(path.join(__dirname, '../../../data', 'file_info.json'), JSON.stringify(data, null, 2));
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(200).send({ message: 'Your file has been successfully uploaded.' });
});
} catch (error) {
console.error(error);
return res.status(500).send({ message: 'Error downloading file.' });
}
res.status(500).send({ message: 'Error uploading file.' });
});
});
});
module.exports = router;

View File

@@ -6,10 +6,13 @@ const fsStandard = require('fs');
const mime = require('mime-types');
const { logger, ErrorLogger } = require('../config/logs');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const compression = require('compression');
const { pipeline } = require('stream/promises'); // Utilisation du pipeline moderne
const baseDir = 'cdn-files';
// Middleware de compression gzip
router.use(compression());
async function getSamAccountNameFromUserId(userId) {
const data = await fs.readFile(path.join(__dirname, '../data', 'user.json'), 'utf8');
const users = JSON.parse(data);
@@ -60,27 +63,14 @@ router.get('/:userId/:filename', async (req, res) => {
try {
const filePath = await findFileInUserDir(userId, filename);
if (!filePath) {
return res.render('file-not-found');
}
const data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8');
let fileInfoArray;
try {
fileInfoArray = JSON.parse(data);
} catch (error) {
console.error('Error parsing file_info.json:', error);
return res.status(500).send('Error reading file info.');
}
if (!Array.isArray(fileInfoArray)) {
console.error('fileInfoArray is not an array');
return res.status(500).send('Invalid file info format.');
}
const fileInfoArray = JSON.parse(data);
const fileInfo = fileInfoArray.find(info => info.fileName === filename && info.Id === userId);
if (fileInfo) {
const expiryDate = new Date(fileInfo.expiryDate);
const now = new Date();
@@ -95,18 +85,41 @@ router.get('/:userId/:filename', async (req, res) => {
}
}
const readStream = fsStandard.createReadStream(filePath);
let mimeType = mime.lookup(filePath) || 'application/octet-stream';
const mimeType = mime.lookup(filePath) || 'application/octet-stream';
const range = req.headers.range;
const stats = await fs.stat(filePath);
const fileSize = stats.size;
res.setHeader('Content-Type', mimeType);
readStream.pipe(res);
if (range) {
const [start, end] = range.replace(/bytes=/, '').split('-');
const chunkStart = parseInt(start, 10);
const chunkEnd = end ? parseInt(end, 10) : fileSize - 1;
if (fileInfo) {
req.session.passwordVerified = false;
if (chunkStart >= fileSize || chunkEnd >= fileSize) {
res.setHeader('Content-Range', `bytes */${fileSize}`);
return res.status(416).send('Requested Range Not Satisfiable');
}
res.status(206);
res.setHeader('Content-Range', `bytes ${chunkStart}-${chunkEnd}/${fileSize}`);
res.setHeader('Accept-Ranges', 'bytes');
res.setHeader('Content-Length', chunkEnd - chunkStart + 1);
res.setHeader('Content-Type', mimeType);
const readStream = fsStandard.createReadStream(filePath, { start: chunkStart, end: chunkEnd });
await pipeline(readStream, res); // Utilisation de pipeline avec await pour éviter les erreurs
} else {
res.setHeader('Content-Length', fileSize);
res.setHeader('Content-Type', mimeType);
const readStream = fsStandard.createReadStream(filePath);
await pipeline(readStream, res);
}
} catch (err) {
ErrorLogger.error('Error reading file:', err);
return res.status(500).send('Error reading file.');
ErrorLogger.error('Error handling request:', err);
if (!res.headersSent) {
res.status(500).send('Error reading file.');
}
}
});
@@ -116,18 +129,7 @@ router.post('/:userId/:filename', async (req, res) => {
try {
const data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8');
let fileInfoArray;
try {
fileInfoArray = JSON.parse(data);
} catch (error) {
console.error('Error parsing file_info.json:', error);
return res.status(500).send('Error reading file info.');
}
if (!Array.isArray(fileInfoArray)) {
console.error('fileInfoArray is not an array');
return res.status(500).send('Invalid file info format.');
}
const fileInfoArray = JSON.parse(data);
const fileInfo = fileInfoArray.find(info => info.fileName === filename && info.Id === userId);
@@ -138,81 +140,26 @@ router.post('/:userId/:filename', async (req, res) => {
const passwordMatch = await bcrypt.compare(enteredPassword, fileInfo.password);
if (passwordMatch) {
req.session.passwordVerified = true;
const filePath = await findFileInUserDir(userId, filename);
const mimeType = mime.lookup(filePath) || 'application/octet-stream';
const readStream = fsStandard.createReadStream(filePath);
let mimeType = mime.lookup(filePath) || 'application/octet-stream';
let fileContent = '';
readStream.on('data', chunk => {
for await (const chunk of readStream) {
fileContent += chunk.toString('base64');
});
}
readStream.on('end', () => {
res.json({ success: true, fileContent, mimeType });
});
res.json({ success: true, fileContent, mimeType });
} else {
res.json({ success: false, message: 'Incorrect password' });
}
} catch (err) {
ErrorLogger.error('Error reading file:', err);
return res.status(500).send('Error reading file.');
if (!res.headersSent) {
res.status(500).send('Error reading file.');
}
}
});
async function deleteExpiredFiles() {
let data;
try {
data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8');
} catch (error) {
console.error('Error reading file_info.json:', error);
return;
}
let fileInfoArray;
try {
fileInfoArray = JSON.parse(data);
} catch (error) {
console.error('Error parsing file_info.json:', error);
return;
}
if (!Array.isArray(fileInfoArray)) {
console.error('fileInfoArray is not an array');
return;
}
const now = new Date();
let newFileInfoArray = [];
for (const fileInfo of fileInfoArray) {
let expiryDate;
if (fileInfo.expiryDate && fileInfo.expiryDate.trim() !== '') {
expiryDate = new Date(fileInfo.expiryDate);
} else {
continue;
}
if (expiryDate < now) {
try {
const samaccountname = await getSamAccountNameFromUserId(fileInfo.userId);
const userDir = path.join(baseDir, samaccountname);
const filePath = path.join(userDir, fileInfo.fileName);
await fs.unlink(filePath);
} catch (err) {
ErrorLogger.error('Error deleting file:', err);
}
} else {
newFileInfoArray.push(fileInfo);
}
}
try {
await fs.writeFile(path.join(__dirname, '../data', 'file_info.json'), JSON.stringify(newFileInfoArray, null, 2), 'utf8');
} catch (err) {
ErrorLogger.error('Error writing to file_info.json:', err);
}
}
setInterval(deleteExpiredFiles, 24 * 60 * 60 * 1000);
module.exports = router;