Files
soft_usite/SEGURIDAD_DESPLIEGUE.md
T
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

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.