CRITICAL fixes: - Restrict X-View-Tenant impersonation to global admin only (was any admin) - Add authorization to subscription endpoints (was open to any user) - Make webhook signature verification mandatory (was skippable) - Remove databaseName from JWT payload (resolve server-side with cache) - Reduce body size limit from 1GB to 10MB (50MB for bulk CFDI) - Restrict .env file permissions to 600 HIGH fixes: - Add authorization to SAT cron endpoints (global admin only) - Add Content-Security-Policy and Permissions-Policy headers - Centralize isGlobalAdmin() utility with caching - Add rate limiting on auth endpoints (express-rate-limit) - Require authentication on logout endpoint MEDIUM fixes: - Replace Math.random() with crypto.randomBytes for temp passwords - Remove console.log of temporary passwords in production - Remove DB credentials from admin notification email - Add escapeHtml() to email templates (prevent HTML injection) - Add file size validation on FIEL upload (50KB max) - Require TLS for SMTP connections - Normalize email to lowercase before uniqueness check - Remove hardcoded default for FIEL_ENCRYPTION_KEY Also includes: - Complete production deployment documentation - API reference documentation - Security audit report with remediation details - Updated README with v0.5.0 changelog - New client admin email template - Utility scripts (create-carlos, test-emails) - PM2 ecosystem config updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
import type { Request, Response } from 'express';
|
|
import { uploadFiel, getFielStatus, deleteFiel } from '../services/fiel.service.js';
|
|
import type { FielUploadRequest } from '@horux/shared';
|
|
|
|
/**
|
|
* Sube y configura las credenciales FIEL
|
|
*/
|
|
export async function upload(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
|
|
const { cerFile, keyFile, password } = req.body as FielUploadRequest;
|
|
|
|
if (!cerFile || !keyFile || !password) {
|
|
res.status(400).json({ error: 'cerFile, keyFile y password son requeridos' });
|
|
return;
|
|
}
|
|
|
|
// Validate file sizes (typical .cer/.key files are under 10KB, base64 ~33% larger)
|
|
const MAX_FILE_SIZE = 50_000; // 50KB base64 ≈ ~37KB binary
|
|
if (cerFile.length > MAX_FILE_SIZE || keyFile.length > MAX_FILE_SIZE) {
|
|
res.status(400).json({ error: 'Los archivos FIEL son demasiado grandes (máx 50KB)' });
|
|
return;
|
|
}
|
|
|
|
if (password.length > 256) {
|
|
res.status(400).json({ error: 'Contraseña FIEL demasiado larga' });
|
|
return;
|
|
}
|
|
|
|
const result = await uploadFiel(tenantId, cerFile, keyFile, password);
|
|
|
|
if (!result.success) {
|
|
res.status(400).json({ error: result.message });
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
message: result.message,
|
|
status: result.status,
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[FIEL Controller] Error en upload:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene el estado de la FIEL configurada
|
|
*/
|
|
export async function status(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const fielStatus = await getFielStatus(tenantId);
|
|
res.json(fielStatus);
|
|
} catch (error: any) {
|
|
console.error('[FIEL Controller] Error en status:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Elimina las credenciales FIEL
|
|
*/
|
|
export async function remove(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const deleted = await deleteFiel(tenantId);
|
|
|
|
if (!deleted) {
|
|
res.status(404).json({ error: 'No hay FIEL configurada' });
|
|
return;
|
|
}
|
|
|
|
res.json({ message: 'FIEL eliminada correctamente' });
|
|
} catch (error: any) {
|
|
console.error('[FIEL Controller] Error en remove:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|