Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusManejo de rutas seguras
El manejo seguro de rutas en Express 5 es fundamental para prevenir vulnerabilidades como path traversal y acceso no autorizado a archivos del sistema. Cuando trabajamos con rutas de archivos proporcionadas por usuarios, debemos implementar validaciones y sanitización para garantizar que solo se acceda a recursos permitidos.
Express 5 proporciona herramientas integradas y mejores prácticas que nos permiten manejar rutas de forma segura, especialmente cuando servimos archivos estáticos o procesamos uploads de usuarios.
Validación de rutas con path.resolve()
La función path.resolve()
de Node.js es esencial para normalizar rutas y prevenir ataques de path traversal. Esta función convierte rutas relativas en absolutas y elimina secuencias peligrosas como ../
:
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.get('/files/:filename', (req, res) => {
const filename = req.params.filename;
// Resolver la ruta de forma segura
const safePath = path.resolve(__dirname, 'uploads', filename);
const uploadsDir = path.resolve(__dirname, 'uploads');
// Verificar que la ruta esté dentro del directorio permitido
if (!safePath.startsWith(uploadsDir)) {
return res.status(403).json({ error: 'Acceso denegado' });
}
res.sendFile(safePath);
});
Sanitización de nombres de archivo
Los nombres de archivo proporcionados por usuarios pueden contener caracteres peligrosos o secuencias de escape. Es crucial sanitizar estos nombres antes de usarlos:
import path from 'path';
function sanitizeFilename(filename) {
// Remover caracteres peligrosos
const sanitized = filename
.replace(/[^a-zA-Z0-9.-]/g, '_') // Solo permitir caracteres seguros
.replace(/\.{2,}/g, '.') // Evitar múltiples puntos consecutivos
.replace(/^\.+|\.+$/g, ''); // Remover puntos al inicio y final
// Limitar longitud
return sanitized.substring(0, 255);
}
app.post('/upload', (req, res) => {
const originalName = req.body.filename;
const safeName = sanitizeFilename(originalName);
if (!safeName) {
return res.status(400).json({ error: 'Nombre de archivo inválido' });
}
const safePath = path.join(__dirname, 'uploads', safeName);
// Procesar archivo...
});
Implementación de middleware de seguridad
Un middleware personalizado puede centralizar la lógica de validación de rutas y aplicarla consistentemente en toda la aplicación:
function securePathMiddleware(baseDir) {
const resolvedBaseDir = path.resolve(baseDir);
return (req, res, next) => {
const requestedPath = req.params.path || req.query.path;
if (!requestedPath) {
return res.status(400).json({ error: 'Ruta requerida' });
}
try {
const resolvedPath = path.resolve(resolvedBaseDir, requestedPath);
// Verificar que la ruta esté dentro del directorio base
if (!resolvedPath.startsWith(resolvedBaseDir + path.sep)) {
return res.status(403).json({ error: 'Ruta no permitida' });
}
// Añadir la ruta segura al objeto request
req.safePath = resolvedPath;
next();
} catch (error) {
res.status(400).json({ error: 'Ruta inválida' });
}
};
}
// Uso del middleware
app.use('/secure-files/*', securePathMiddleware('./public'));
app.get('/secure-files/:path(*)', (req, res) => {
// req.safePath ya está validada y es segura
res.sendFile(req.safePath);
});
Validación de extensiones de archivo
Restringir las extensiones de archivo permitidas añade una capa adicional de seguridad, especialmente importante cuando se manejan uploads:
const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.txt'];
function validateFileExtension(filename) {
const ext = path.extname(filename).toLowerCase();
return ALLOWED_EXTENSIONS.includes(ext);
}
app.get('/download/:filename', (req, res) => {
const filename = req.params.filename;
// Validar extensión
if (!validateFileExtension(filename)) {
return res.status(400).json({ error: 'Tipo de archivo no permitido' });
}
const safePath = path.resolve(__dirname, 'downloads', filename);
const downloadsDir = path.resolve(__dirname, 'downloads');
if (!safePath.startsWith(downloadsDir)) {
return res.status(403).json({ error: 'Acceso denegado' });
}
res.download(safePath);
});
Manejo de rutas con express.static()
Express 5 mejora la seguridad del middleware express.static()
con opciones adicionales para controlar el acceso a archivos:
app.use('/public', express.static('public', {
dotfiles: 'deny', // Denegar acceso a archivos ocultos
index: false, // No servir archivos index automáticamente
redirect: false, // No redirigir rutas que terminan en /
setHeaders: (res, path) => {
// Configurar headers de seguridad
res.set('X-Content-Type-Options', 'nosniff');
res.set('X-Frame-Options', 'DENY');
}
}));
Implementación de límites de profundidad
Para prevenir el acceso a directorios anidados excesivamente profundos, podemos implementar límites de profundidad:
function validatePathDepth(requestPath, maxDepth = 3) {
const pathParts = requestPath.split(path.sep).filter(part => part && part !== '.');
return pathParts.length <= maxDepth;
}
app.get('/files/:path(*)', (req, res) => {
const requestPath = req.params.path;
if (!validatePathDepth(requestPath)) {
return res.status(400).json({ error: 'Ruta demasiado profunda' });
}
// Continuar con validación de ruta segura...
const safePath = path.resolve(__dirname, 'files', requestPath);
// Resto de la lógica...
});
Estas técnicas de manejo seguro de rutas son esenciales para mantener la integridad y seguridad de aplicaciones Express 5, especialmente cuando se manejan archivos proporcionados por usuarios o se sirve contenido dinámico basado en parámetros de entrada.
Creación de directorios
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La creación de directorios en Express 5 es una operación fundamental cuando desarrollamos aplicaciones que manejan archivos, uploads de usuarios o necesitan organizar contenido dinámicamente. Express 5 aprovecha las capacidades nativas de Node.js para crear directorios de forma eficiente y segura, proporcionando herramientas que nos permiten manejar la estructura de carpetas de manera robusta.
Cuando trabajamos con aplicaciones Express que requieren almacenamiento de archivos, es común necesitar crear directorios para organizar uploads, logs, cachés temporales o contenido generado dinámicamente.
Creación básica con fs.mkdir()
El módulo fs
de Node.js proporciona métodos síncronos y asíncronos para crear directorios. En Express 5, utilizamos principalmente la versión asíncrona para evitar bloquear el event loop:
import fs from 'fs/promises';
import path from 'path';
app.post('/create-user-folder', async (req, res) => {
const { userId } = req.body;
if (!userId) {
return res.status(400).json({ error: 'ID de usuario requerido' });
}
try {
const userDir = path.join(__dirname, 'uploads', userId);
await fs.mkdir(userDir);
res.json({
message: 'Directorio creado exitosamente',
path: userDir
});
} catch (error) {
if (error.code === 'EEXIST') {
return res.status(409).json({ error: 'El directorio ya existe' });
}
res.status(500).json({ error: 'Error al crear directorio' });
}
});
Creación recursiva de directorios
La opción recursive: true
permite crear directorios anidados en una sola operación, creando automáticamente los directorios padre que no existan:
app.post('/setup-project-structure', async (req, res) => {
const { projectName } = req.body;
try {
const projectPath = path.join(__dirname, 'projects', projectName);
// Crear estructura completa de directorios
const directories = [
path.join(projectPath, 'assets', 'images'),
path.join(projectPath, 'assets', 'documents'),
path.join(projectPath, 'temp'),
path.join(projectPath, 'exports')
];
// Crear todos los directorios de forma concurrente
await Promise.all(
directories.map(dir => fs.mkdir(dir, { recursive: true }))
);
res.json({
message: 'Estructura de proyecto creada',
directories: directories
});
} catch (error) {
res.status(500).json({ error: 'Error al crear estructura' });
}
});
Middleware para creación automática de directorios
Un middleware personalizado puede verificar y crear directorios automáticamente antes de procesar requests que los requieran:
function ensureDirectoryExists(basePath) {
return async (req, res, next) => {
try {
const targetDir = path.join(basePath, req.params.category || '');
// Verificar si el directorio existe
try {
await fs.access(targetDir);
} catch {
// Si no existe, crearlo
await fs.mkdir(targetDir, { recursive: true });
console.log(`Directorio creado: ${targetDir}`);
}
req.targetDirectory = targetDir;
next();
} catch (error) {
res.status(500).json({ error: 'Error al preparar directorio' });
}
};
}
// Uso del middleware
app.use('/upload/:category', ensureDirectoryExists('./uploads'));
app.post('/upload/:category', (req, res) => {
// req.targetDirectory ya está disponible y garantizado que existe
const uploadPath = path.join(req.targetDirectory, req.file.originalname);
// Procesar upload...
});
Creación con permisos específicos
En sistemas Unix/Linux, podemos especificar permisos de directorio durante la creación para controlar el acceso:
app.post('/create-secure-folder', async (req, res) => {
const { folderName, isPublic } = req.body;
try {
const folderPath = path.join(__dirname, 'data', folderName);
// Definir permisos según el tipo de carpeta
const mode = isPublic ? 0o755 : 0o700; // 755 para público, 700 para privado
await fs.mkdir(folderPath, {
recursive: true,
mode: mode
});
res.json({
message: 'Carpeta creada con permisos específicos',
path: folderPath,
permissions: mode.toString(8)
});
} catch (error) {
res.status(500).json({ error: 'Error al crear carpeta segura' });
}
});
Validación antes de crear directorios
Implementar validaciones robustas antes de crear directorios previene errores y mejora la experiencia del usuario:
async function validateDirectoryCreation(basePath, dirName) {
// Validar nombre del directorio
if (!/^[a-zA-Z0-9_-]+$/.test(dirName)) {
throw new Error('Nombre de directorio contiene caracteres inválidos');
}
// Validar longitud
if (dirName.length > 50) {
throw new Error('Nombre de directorio demasiado largo');
}
const fullPath = path.join(basePath, dirName);
// Verificar que no exceda límites de profundidad
const relativePath = path.relative(basePath, fullPath);
if (relativePath.split(path.sep).length > 5) {
throw new Error('Estructura de directorios demasiado profunda');
}
return fullPath;
}
app.post('/create-validated-directory', async (req, res) => {
const { directoryName } = req.body;
try {
const validatedPath = await validateDirectoryCreation(
path.join(__dirname, 'user-content'),
directoryName
);
await fs.mkdir(validatedPath, { recursive: true });
res.json({
message: 'Directorio creado y validado',
path: validatedPath
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Creación de directorios temporales
Para directorios temporales que se crean y eliminan dinámicamente, podemos implementar un sistema de gestión automática:
import { randomUUID } from 'crypto';
class TempDirectoryManager {
constructor(basePath) {
this.basePath = basePath;
this.tempDirs = new Map();
}
async createTempDirectory(ttl = 3600000) { // TTL por defecto: 1 hora
const tempId = randomUUID();
const tempPath = path.join(this.basePath, 'temp', tempId);
await fs.mkdir(tempPath, { recursive: true });
// Programar limpieza automática
const cleanupTimer = setTimeout(async () => {
await this.cleanupTempDirectory(tempId);
}, ttl);
this.tempDirs.set(tempId, {
path: tempPath,
timer: cleanupTimer,
created: Date.now()
});
return { id: tempId, path: tempPath };
}
async cleanupTempDirectory(tempId) {
const tempInfo = this.tempDirs.get(tempId);
if (tempInfo) {
clearTimeout(tempInfo.timer);
try {
await fs.rm(tempInfo.path, { recursive: true, force: true });
} catch (error) {
console.error(`Error limpiando directorio temporal: ${error.message}`);
}
this.tempDirs.delete(tempId);
}
}
}
const tempManager = new TempDirectoryManager(__dirname);
app.post('/create-temp-workspace', async (req, res) => {
try {
const { id, path: tempPath } = await tempManager.createTempDirectory();
res.json({
message: 'Espacio temporal creado',
workspaceId: id,
path: tempPath,
expiresIn: '1 hora'
});
} catch (error) {
res.status(500).json({ error: 'Error al crear espacio temporal' });
}
});
Manejo de errores específicos
Diferentes códigos de error requieren respuestas específicas para proporcionar feedback útil al cliente:
app.post('/create-directory-with-error-handling', async (req, res) => {
const { path: requestedPath } = req.body;
try {
const fullPath = path.join(__dirname, 'managed-dirs', requestedPath);
await fs.mkdir(fullPath, { recursive: true });
res.json({
message: 'Directorio creado exitosamente',
path: fullPath
});
} catch (error) {
let statusCode = 500;
let message = 'Error interno del servidor';
switch (error.code) {
case 'EEXIST':
statusCode = 409;
message = 'El directorio ya existe';
break;
case 'EACCES':
statusCode = 403;
message = 'Permisos insuficientes para crear directorio';
break;
case 'ENOSPC':
statusCode = 507;
message = 'Espacio insuficiente en disco';
break;
case 'ENAMETOOLONG':
statusCode = 400;
message = 'Nombre de directorio demasiado largo';
break;
}
res.status(statusCode).json({
error: message,
code: error.code
});
}
});
La creación de directorios en Express 5 requiere considerar aspectos de seguridad, validación y manejo de errores para construir aplicaciones robustas que gestionen eficientemente la estructura de archivos y directorios.
Aprendizajes de esta lección de Express
- Comprender cómo validar y sanitizar rutas y nombres de archivo para evitar ataques de path traversal.
- Implementar middleware personalizado para centralizar la seguridad en el manejo de rutas.
- Aplicar técnicas para restringir extensiones de archivo y controlar el acceso mediante express.static().
- Crear directorios de forma segura y eficiente utilizando fs.mkdir() con opciones recursivas y permisos específicos.
- Manejar errores comunes y validar entradas para garantizar la robustez en la creación de directorios.
Completa este curso de Express y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs