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>
3.3 KiB
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:
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.