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>
161 lines
4.6 KiB
TypeScript
161 lines
4.6 KiB
TypeScript
import type { Request, Response } from 'express';
|
|
import {
|
|
startSync,
|
|
getSyncStatus,
|
|
getSyncHistory,
|
|
retryJob,
|
|
} from '../services/sat/sat.service.js';
|
|
import { getJobInfo, runSatSyncJobManually } from '../jobs/sat-sync.job.js';
|
|
import type { StartSyncRequest } from '@horux/shared';
|
|
import { isGlobalAdmin } from '../utils/global-admin.js';
|
|
|
|
/**
|
|
* Inicia una sincronización manual
|
|
*/
|
|
export async function start(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const { type, dateFrom, dateTo } = req.body as StartSyncRequest;
|
|
|
|
const jobId = await startSync(
|
|
tenantId,
|
|
type || 'daily',
|
|
dateFrom ? new Date(dateFrom) : undefined,
|
|
dateTo ? new Date(dateTo) : undefined
|
|
);
|
|
|
|
res.json({
|
|
jobId,
|
|
message: 'Sincronización iniciada',
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en start:', error);
|
|
|
|
if (error.message.includes('FIEL') || error.message.includes('sincronización en curso')) {
|
|
res.status(400).json({ error: error.message });
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene el estado actual de sincronización
|
|
*/
|
|
export async function status(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const syncStatus = await getSyncStatus(tenantId);
|
|
res.json(syncStatus);
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en status:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene el historial de sincronizaciones
|
|
*/
|
|
export async function history(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
const limit = parseInt(req.query.limit as string) || 10;
|
|
|
|
const result = await getSyncHistory(tenantId, page, limit);
|
|
res.json({
|
|
...result,
|
|
page,
|
|
limit,
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en history:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene detalle de un job específico
|
|
*/
|
|
export async function jobDetail(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const tenantId = req.user!.tenantId;
|
|
const { id } = req.params;
|
|
const { jobs } = await getSyncHistory(tenantId, 1, 100);
|
|
const job = jobs.find(j => j.id === id);
|
|
|
|
if (!job) {
|
|
res.status(404).json({ error: 'Job no encontrado' });
|
|
return;
|
|
}
|
|
|
|
res.json(job);
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en jobDetail:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reintenta un job fallido
|
|
*/
|
|
export async function retry(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const id = req.params.id as string;
|
|
const newJobId = await retryJob(id);
|
|
|
|
res.json({
|
|
jobId: newJobId,
|
|
message: 'Job reintentado',
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en retry:', error);
|
|
|
|
if (error.message.includes('no encontrado') || error.message.includes('Solo se pueden')) {
|
|
res.status(400).json({ error: error.message });
|
|
return;
|
|
}
|
|
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene información del job programado (solo admin global)
|
|
*/
|
|
export async function cronInfo(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
if (!(await isGlobalAdmin(req.user!.tenantId, req.user!.role))) {
|
|
res.status(403).json({ error: 'Solo el administrador global puede ver info del cron' });
|
|
return;
|
|
}
|
|
const info = getJobInfo();
|
|
res.json(info);
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en cronInfo:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ejecuta el job de sincronización manualmente (solo admin global)
|
|
*/
|
|
export async function runCron(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
if (!(await isGlobalAdmin(req.user!.tenantId, req.user!.role))) {
|
|
res.status(403).json({ error: 'Solo el administrador global puede ejecutar el cron' });
|
|
return;
|
|
}
|
|
// Ejecutar en background
|
|
runSatSyncJobManually().catch(err =>
|
|
console.error('[SAT Controller] Error ejecutando cron manual:', err)
|
|
);
|
|
|
|
res.json({ message: 'Job de sincronización iniciado' });
|
|
} catch (error: any) {
|
|
console.error('[SAT Controller] Error en runCron:', error);
|
|
res.status(500).json({ error: 'Error interno del servidor' });
|
|
}
|
|
}
|