security: comprehensive security audit and remediation (20 fixes)

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>
This commit is contained in:
Consultoria AS
2026-03-18 22:32:04 +00:00
parent 38626bd3e6
commit 351b14a78c
31 changed files with 1287 additions and 103 deletions

View File

@@ -0,0 +1,323 @@
# API Reference - Horux360
**Base URL:** `https://horuxfin.com/api`
---
## Autenticación
Todos los endpoints (excepto auth) requieren header:
```
Authorization: Bearer <accessToken>
```
### Rate Limits (por IP)
| Endpoint | Límite | Ventana |
|----------|--------|---------|
| `POST /auth/login` | 10 requests | 15 minutos |
| `POST /auth/register` | 3 requests | 1 hora |
| `POST /auth/refresh` | 20 requests | 15 minutos |
| General `/api/*` | 30 requests/s | burst 50 |
---
## Auth (`/api/auth`)
### `POST /auth/register`
Registra nueva empresa y usuario admin. Provisiona base de datos dedicada.
**Body:**
```json
{
"empresa": { "nombre": "Mi Empresa", "rfc": "ABC123456789" },
"usuario": { "nombre": "Juan", "email": "juan@empresa.com", "password": "min8chars" }
}
```
**Response:** `{ accessToken, refreshToken, user: UserInfo }`
### `POST /auth/login`
```json
{ "email": "usuario@empresa.com", "password": "..." }
```
**Response:** `{ accessToken, refreshToken, user: UserInfo }`
### `POST /auth/refresh`
```json
{ "refreshToken": "..." }
```
**Response:** `{ accessToken, refreshToken }`
### `POST /auth/logout` *(requiere auth)*
```json
{ "refreshToken": "..." }
```
### `GET /auth/me` *(requiere auth)*
**Response:** `UserInfo`
---
## Dashboard (`/api/dashboard`)
### `GET /dashboard/kpis?año=2026&mes=3`
KPIs principales: ingresos, egresos, utilidad, margen, IVA balance, conteo de CFDIs.
### `GET /dashboard/ingresos-egresos?año=2026`
Datos mensuales de ingresos/egresos para gráfica anual.
### `GET /dashboard/resumen-fiscal?año=2026&mes=3`
IVA por pagar, IVA a favor, ISR, declaraciones pendientes, próxima obligación.
### `GET /dashboard/alertas?limit=5`
Alertas activas no resueltas, ordenadas por prioridad.
---
## CFDI (`/api/cfdi`)
### `GET /cfdi?page=1&limit=20&tipo=ingreso&search=...`
Lista paginada de CFDIs con filtros.
### `GET /cfdi/resumen`
Resumen de conteo por tipo y estado.
### `GET /cfdi/emisores`
Lista de emisores únicos.
### `GET /cfdi/receptores`
Lista de receptores únicos.
### `GET /cfdi/:id`
Detalle de un CFDI.
### `GET /cfdi/:id/xml`
XML original del CFDI.
### `POST /cfdi`
Crear un CFDI individual. Sujeto a límite de plan.
### `POST /cfdi/bulk`
Carga masiva de CFDIs. Body limit: 50MB. Sujeto a límite de plan.
### `DELETE /cfdi/:id`
Eliminar un CFDI.
---
## Impuestos (`/api/impuestos`)
### `GET /impuestos/iva?año=2026`
Datos mensuales de IVA (trasladado, acreditable, resultado, acumulado).
---
## Alertas (`/api/alertas`)
### `GET /alertas`
### `POST /alertas`
### `PUT /alertas/:id`
### `DELETE /alertas/:id`
### `PATCH /alertas/:id/read`
### `PATCH /alertas/:id/resolve`
---
## Calendario (`/api/calendario`)
### `GET /calendario?año=2026&mes=3`
### `POST /calendario`
### `PUT /calendario/:id`
### `DELETE /calendario/:id`
---
## Reportes (`/api/reportes`)
### `GET /reportes/flujo-efectivo?año=2026`
### `GET /reportes/impuestos?año=2026`
### `GET /reportes/forecasting?año=2026`
### `GET /reportes/concentrado?año=2026`
---
## Export (`/api/export`)
### `GET /export/cfdis?format=excel&tipo=ingreso`
Exporta CFDIs a Excel o CSV.
---
## FIEL (`/api/fiel`)
### `POST /fiel/upload`
```json
{
"cerFile": "<base64>",
"keyFile": "<base64>",
"password": "..."
}
```
- Archivos max 50KB cada uno
- Password max 256 caracteres
### `GET /fiel/status`
Estado actual de la FIEL configurada.
### `DELETE /fiel`
Eliminar credenciales FIEL.
---
## SAT Sync (`/api/sat`)
### `POST /sat/sync`
Iniciar sincronización manual.
```json
{ "type": "daily", "dateFrom": "2026-01-01", "dateTo": "2026-01-31" }
```
### `GET /sat/sync/status`
Estado actual de sincronización.
### `GET /sat/sync/history?page=1&limit=10`
Historial de sincronizaciones.
### `GET /sat/sync/:id`
Detalle de un job de sincronización.
### `POST /sat/sync/:id/retry`
Reintentar un job fallido.
### `GET /sat/cron` *(admin global)*
Info del job programado.
### `POST /sat/cron/run` *(admin global)*
Ejecutar sincronización global manualmente.
---
## Usuarios (`/api/usuarios`)
### `GET /usuarios`
Usuarios del tenant actual.
### `GET /usuarios/all` *(admin global)*
Todos los usuarios de todas las empresas.
### `POST /usuarios`
Invitar usuario (genera password temporal con `crypto.randomBytes`).
```json
{ "email": "nuevo@empresa.com", "nombre": "María", "role": "contador" }
```
### `PUT /usuarios/:id`
Actualizar usuario (nombre, role, active).
### `DELETE /usuarios/:id`
### `PUT /usuarios/:id/global` *(admin global)*
Actualizar usuario de cualquier empresa.
### `DELETE /usuarios/:id/global` *(admin global)*
---
## Tenants / Clientes (`/api/tenants`) *(admin global)*
### `GET /tenants`
Lista de todos los tenants/clientes.
### `POST /tenants`
Crear nuevo tenant. Provisiona base de datos. Envía email al admin.
```json
{
"nombre": "Empresa Nueva",
"rfc": "ENE123456789",
"plan": "business",
"adminNombre": "Pedro",
"adminEmail": "pedro@nueva.com"
}
```
### `PUT /tenants/:id`
Actualizar tenant (plan, limits, active).
### `DELETE /tenants/:id`
Soft delete — renombra la base de datos a `*_deleted_*`.
---
## Suscripciones (`/api/subscriptions`) *(admin global)*
### `GET /subscriptions/:tenantId`
Suscripción activa del tenant.
### `POST /subscriptions/:tenantId/generate-link`
Generar link de pago MercadoPago.
### `POST /subscriptions/:tenantId/mark-paid`
Marcar como pagado manualmente.
```json
{ "amount": 999 }
```
### `GET /subscriptions/:tenantId/payments`
Historial de pagos.
---
## Webhooks (`/api/webhooks`)
### `POST /webhooks/mercadopago`
Webhook de MercadoPago. Requiere headers:
- `x-signature`: Firma HMAC-SHA256
- `x-request-id`: ID del request
---
## Roles y Permisos
| Rol | Descripción | Acceso |
|-----|-------------|--------|
| `admin` | Administrador del tenant | Todo dentro de su tenant + invitar usuarios |
| `contador` | Contador | CFDI, impuestos, reportes, dashboard |
| `visor` | Solo lectura | Dashboard, CFDI (solo ver), reportes |
### Admin Global
El admin del tenant con RFC `CAS2408138W2` tiene acceso adicional:
- Gestión de todos los tenants
- Suscripciones
- SAT cron
- Impersonación via `X-View-Tenant` header
- Bypass de plan limits al impersonar
---
## Tipos Compartidos (`@horux/shared`)
### UserInfo
```typescript
interface UserInfo {
id: string;
email: string;
nombre: string;
role: 'admin' | 'contador' | 'visor';
tenantId: string;
tenantName: string;
tenantRfc: string;
plan: string;
}
```
### JWTPayload
```typescript
interface JWTPayload {
userId: string;
email: string;
role: Role;
tenantId: string;
iat?: number;
exp?: number;
}
```