const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const app = express();
// Secure: No directory listing in production
if (process.env.NODE_ENV === 'development') {
// Development-only directory listing with restrictions
const serveIndex = require('serve-index');
app.use('/dev-uploads',
authenticateAdmin,
express.static('dev-uploads'),
serveIndex('dev-uploads', {
'icons': true,
'filter': (name, index, files, dir) => {
// Only show safe file types
const safeExtensions = /\.(jpg|jpeg|png|gif|pdf|txt)$/i;
return safeExtensions.test(name) &&
!name.startsWith('.') && // Hide hidden files
!name.includes('sensitive'); // Hide files with 'sensitive' in name
},
'template': path.join(__dirname, 'views/directory-listing.html')
})
);
}
// Secure: Controlled file serving for production
const ALLOWED_FILE_TYPES = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.txt'];
const UPLOADS_DIR = path.resolve('./secure-uploads');
// API endpoint to list files with authorization
app.get('/api/files', authenticateUser, async (req, res) => {
try {
const userFolder = path.join(UPLOADS_DIR, req.user.id);
// Ensure user folder exists
await fs.mkdir(userFolder, { recursive: true });
const files = await fs.readdir(userFolder);
const fileList = files
.filter(file => {
const ext = path.extname(file).toLowerCase();
return ALLOWED_FILE_TYPES.includes(ext);
})
.map(file => ({
name: file,
downloadUrl: `/api/files/${encodeURIComponent(file)}`,
uploadDate: getFileUploadDate(file) // Implement this function
}));
res.json({ files: fileList });
} catch (error) {
console.error('Error listing user files:', error);
res.status(500).json({ error: 'Failed to list files' });
}
});
// Secure file download with authorization
app.get('/api/files/:filename', authenticateUser, async (req, res) => {
try {
const filename = req.params.filename;
const userId = req.user.id;
// Validate filename
if (!isSecureFilename(filename)) {
return res.status(400).json({ error: 'Invalid filename' });
}
// Construct secure file path
const filePath = path.join(UPLOADS_DIR, userId, filename);
const resolvedPath = path.resolve(filePath);
const userDir = path.resolve(UPLOADS_DIR, userId);
// Ensure file is within user's directory
if (!resolvedPath.startsWith(userDir)) {
return res.status(403).json({ error: 'Access denied' });
}
// Check if file exists
await fs.access(resolvedPath, fs.constants.R_OK);
// Serve file securely
res.sendFile(resolvedPath, {
headers: {
'Content-Disposition': `attachment; filename="${filename}"`,
'X-Content-Type-Options': 'nosniff'
}
});
// Log file access
console.log(`User ${userId} downloaded file: ${filename}`);
} catch (error) {
if (error.code === 'ENOENT' || error.code === 'EACCES') {
res.status(404).json({ error: 'File not found' });
} else {
console.error('Error serving file:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
});
function isSecureFilename(filename) {
if (typeof filename !== 'string' || filename.length === 0) {
return false;
}
// Reject path traversal attempts
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
return false;
}
// Reject hidden files and system files
if (filename.startsWith('.') || filename.toLowerCase().includes('system')) {
return false;
}
// Check allowed extensions
const ext = path.extname(filename).toLowerCase();
return ALLOWED_FILE_TYPES.includes(ext);
}
function authenticateUser(req, res, next) {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Authentication required' });
}
req.user = {
id: req.session.userId,
role: req.session.userRole || 'user'
};
next();
}
function authenticateAdmin(req, res, next) {
authenticateUser(req, res, (err) => {
if (err) return next(err);
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
});
}
function getFileUploadDate(filename) {
// Extract upload date from filename or database
// This is a placeholder implementation
const match = filename.match(/(\d{4}-\d{2}-\d{2})/);
return match ? match[1] : 'Unknown';
}