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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5607836261
commit
c8e5afceb2
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user