Files
soft_usite/SEGURIDAD_DESPLIEGUE.md
Lizandro GDandClaude Opus 5 c8e5afceb2 fix: seguridad de pagos y accesos, integración PayPal y Coolify ampliado
Seguridad (crítico):
- Los webhooks de Bold y dLocal solo validaban la firma si el atacante la
  enviaba: sin cabecera se aceptaba cualquier payload. Ahora es obligatoria.
- GET /pago-exitoso marcaba contratos como pagados leyendo un query param del
  navegador. Ahora solo muestra estado; la confirmación la hace la verificación
  contra la API de la pasarela o el webhook firmado.
- /uploads se servía como estático público: se descargaban RUTs, facturas y
  entregables sabiendo la ruta. Ahora exige sesión.
- Los secretos JWT no se podían sobreescribir por entorno (faltaba el tag env:)
  y su valor estaba en el repo, permitiendo firmarse una sesión de admin. Ahora
  son configurables y el arranque se detiene si siguen con el valor publicado.
- .env y session.db salen del control de versiones.
- Query Runner, gestión de usuarios/roles/módulos y seeds quedan restringidos a
  administradores; antes bastaba con tener sesión.

Pasarelas de pago:
- dLocal generaba enlaces que nunca se reconciliaban: mandaba el ID numérico en
  vez de "contrato-N", la URL de retorno apuntaba a la API de dLocal y nunca se
  enviaba notification_url, así que su webhook jamás se disparaba.
- PayPal solo tenía pantalla de configuración. Se implementa el servicio
  completo (OAuth, orden, captura, verificación de webhook) y queda
  seleccionable como pasarela.
- La moneda estaba fija en COP: un contrato en USD generaba un cobro por esa
  cifra en pesos.

Contratos:
- pago_confirmado nunca volvía a false, así que el segundo ciclo de renovación
  no se cobraba aunque el cliente pagara. Se reinicia al generar enlace nuevo.
- Los contratos vencidos nunca cambiaban de estado y recibían correo a diario
  de forma indefinida; ahora se cierran tras 30 días de gracia.

Otros:
- Coolify: coolifyCall ignoraba el status HTTP y reportaba errores como éxito.
  El agente pasa de 10 a cobertura completa (servicios, bases de datos,
  variables de entorno, proyectos, equipos y recursos de servidor).
- SeedBalanceData ya no corre en cada arranque (recreaba transacciones
  borradas); ahora se invoca con SEED_BALANCE=1.
- Los seeds dejan de devolver permisos revocados en cada despliegue.
- Timeouts en las llamadas HTTP a Telegram y dLocal que podían colgarse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 03:20:00 +00:00

82 lines
3.3 KiB
Markdown

# Variables de entorno requeridas
> **Leer antes del siguiente despliegue.** El arranque falla a propósito si los
> secretos JWT siguen teniendo el valor que estuvo publicado en el repositorio.
## 1. Secretos que hay que definir sí o sí
`.env` y `config.yml` estuvieron versionados con credenciales reales, así que
**todo lo que aparecía ahí debe considerarse comprometido y rotarse**, no solo
sacarse del repositorio: el historial de git lo sigue conteniendo.
Genera cada valor con `openssl rand -hex 32` y cárgalos como variables de
entorno del contenedor (en Coolify: *Environment Variables*):
| Variable | Para qué sirve | Si no se define |
|---|---|---|
| `APP_JWT_SECRET` | Firma las cookies de sesión del panel | **El arranque se detiene** |
| `API_JWT_SECRET` | Firma los tokens de la API v1 | **El arranque se detiene** |
| `APP_KEY` | Cifra las contraseñas de SMTP e integraciones | Arranca, pero avisa en los logs |
| `ADMIN_API_KEY` | Única llave de toda la API `/api/v2` | La API v2 responde 503 |
Al rotar `APP_JWT_SECRET` se cierran las sesiones abiertas: hay que volver a
iniciar sesión, nada más.
### Cuidado con `APP_KEY`
`APP_KEY` cifra las contraseñas guardadas en la base de datos. Si la cambias,
**las contraseñas cifradas con la clave anterior dejan de poder descifrarse** y
hay que volver a guardarlas desde el panel:
- Configuración SMTP (`/app/smtp-config`)
- Credenciales de las pasarelas de pago (`/app/pasarelas`)
- Cualquier otra integración con contraseña
Por eso el sistema solo advierte en vez de detenerse: para que elijas el momento.
## 2. Otras credenciales a rotar
Estaban en el `.env` versionado:
- Contraseña de PostgreSQL
- Contraseña del correo saliente
- Cualquier token de integración (Coolify, Cloudflare, Hostinger, Telegram…)
## 3. Webhooks de pago
Las notificaciones ahora **exigen firma válida**. Verifica en cada proveedor que
el secreto de firma coincida con el configurado en `/app/pasarelas`:
| Pasarela | URL del webhook | Requiere |
|---|---|---|
| Bold | `https://TU-DOMINIO/webhooks/bold` | Secret de firma en la config |
| dLocal | `https://TU-DOMINIO/webhooks/dlocal` | Secret de firma en la config |
| PayPal | `https://TU-DOMINIO/webhooks/paypal` | **Webhook ID** en la config |
Para PayPal hay que crear el webhook en el panel de PayPal suscrito a los
eventos `CHECKOUT.ORDER.APPROVED` y `PAYMENT.CAPTURE.COMPLETED`, y pegar el
Webhook ID que devuelve en `/app/pasarelas`. Sin ese ID no se pueden verificar
las notificaciones y se rechazan.
> PayPal no opera en pesos colombianos. Los contratos que se cobren por PayPal
> deben tener la moneda en USD (u otra soportada), o PayPal rechazará la orden.
## 4. Datos contables de ejemplo
`SeedBalanceData` (27 transacciones de 2026 escritas en el código) ya **no**
corre en cada arranque: si el contador editaba o borraba una, el siguiente
reinicio la recreaba y el balance quedaba duplicado.
Para cargarla puntualmente:
```bash
SEED_BALANCE=1 ./apiv2 -config config.yml
```
## 5. Archivos subidos
`/uploads` ya no es público. Antes, sabiendo la ruta se podía descargar el RUT de
un cliente o una factura sin iniciar sesión. Ahora requiere sesión en el panel;
los clientes del portal siguen descargando sus documentos por los endpoints de
siempre, que además validan que el archivo les pertenezca.