Files
soft_usite/rest/routes/publicas.go
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

100 lines
5.8 KiB
Go
Executable File

package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
apiControllers "github.com/sujit-baniya/fiber-boilerplate/rest/controllers/api"
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
)
func RutasPublicas(web fiber.Router) {
web.Get("/", controllers.Landing)
web.Get("/index", controllers.Index)
//web.Get("/login", controllers.Login)
web.Get("/ping", Pong)
web.Get("/all-routes", AllRoutes)
web.Get("/do/verify-email", middlewares.ValidateConfirmToken, controllers.VerifyRegisteredEmail)
// ─── Webhooks públicos (sin autenticación) ────────────────────────────
// Bold requiere respuesta HTTP 200 rápida, sin middleware de sesión.
web.Post("/webhooks/bold", apiControllers.BoldWebhook)
// dLocal requiere respuesta HTTP 200 inmediata.
web.Post("/webhooks/dlocal", apiControllers.DlocalWebhook)
// PayPal: configurar en el panel de PayPal → Webhooks, con los eventos
// CHECKOUT.ORDER.APPROVED y PAYMENT.CAPTURE.COMPLETED.
web.Post("/webhooks/paypal", apiControllers.PaypalWebhook)
// Webhook entrante por integración SaaS (token único por config)
web.Post("/webhooks/saas-in/:token", controllers.SaasWebhookInHandler)
web.Get("/webhooks/saas-in/:token", controllers.SaasWebhookInHandler) // algunos SaaS verifican con GET
// Coolify notifica eventos de deployment/restart aquí (configurar en Coolify → Settings → Notifications → Webhook)
// Ruta genérica
web.Post("/webhooks/coolify", controllers.CoolifyWebhook)
// Ruta por instancia: incluye el config_id para identificar de qué Coolify viene
web.Post("/webhooks/coolify/:config_id", controllers.CoolifyWebhook)
// ─── Página de confirmación de pago ───────────────────────────────────
web.Get("/pago-exitoso", apiControllers.PagoExitosoPage)
// Ruta fuera del prefijo /api para evitar que AuthApi() la intercepte
web.Get("/pago/estado", apiControllers.PagoEstadoAPI)
// ─── Documentación pública ────────────────────────────────────────────────
web.Get("/docs/:saas", controllers.DocsPublicoIndex)
web.Get("/docs/:saas/:slug", controllers.DocsPublicaPagina)
// ─── Webhook de Telegram para vinculación del portal ──────────────────────
// Configurar en el bot: POST https://api.telegram.org/bot{TOKEN}/setWebhook?url={HOST}/webhooks/telegram-portal
web.Post("/webhooks/telegram-portal", controllers.TelegramPortalWebhook)
// ─── Webhook del agente administrador (bot con IA) ──────────────────────
// Configurar en el bot: POST https://api.telegram.org/bot{TOKEN}/setWebhook?url={HOST}/webhooks/telegram-agent/{BOT_TOKEN}
web.Post("/webhooks/telegram-agent/:bot_token", controllers.TelegramAgentWebhook)
// ─── WebSMS (LabsMobile) — API de envío + webhooks ──────────────────
web.Post("/api/sms/send", controllers.ApiSendSms)
web.Post("/webhooks/websms/delivery", controllers.WebSmsDeliveryWebhook)
web.Post("/webhooks/websms/click", controllers.WebSmsClickWebhook)
web.Post("/webhooks/websms/incoming", controllers.WebSmsIncomingWebhook)
// ─── Agente de monitoreo de servidores ────────────────────────────────────
// El agente instalado en cada servidor reporta métricas aquí (sin sesión, auth por token).
web.Post("/agent/heartbeat", controllers.AgentHeartbeat)
// Servir el script de instalación del agente
web.Get("/agent/install.sh", func(c *fiber.Ctx) error {
c.Set("Content-Type", "text/plain; charset=utf-8")
return c.SendFile("./agent/install.sh")
})
// Descargar binario pre-compilado del agente
// Ejemplo: GET /agent/download/usite-agent-linux-amd64
web.Get("/agent/download/:filename", func(c *fiber.Ctx) error {
filename := c.Params("filename")
// Validar que sea un nombre de binario válido (sin path traversal)
for _, ch := range filename {
if ch == '/' || ch == '.' || ch == '\\' {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "nombre inválido"})
}
}
path := "./agent/dist/" + filename
c.Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
c.Set("Content-Type", "application/octet-stream")
return c.SendFile(path)
})
// ─── Página de estado del sistema (Atlassian Statuspage) ────────────────
web.Get("/status", controllers.StatusPage)
// ─── Webhook de soporte (correo entrante) ────────────────────────────────
// Configurar en SendGrid/Mailgun: POST {HOST}/webhooks/soporte/{provider}
web.Post("/webhooks/soporte/:provider", controllers.SoporteWebhook)
// ─── Landing Generator (acceso con X-Landing-Secret) ─────────────────────
// Rutas públicas (sin secret) para lectura de preview y polling
web.Get("/landing/preview/:token", apiControllers.LandingGetSession)
web.Get("/landing/poll/:token", apiControllers.LandingPollPayment)
// Rutas internas (requieren X-Landing-Secret del Next.js)
web.Post("/landing/session", apiControllers.LandingCreateSession)
web.Post("/landing/chat", apiControllers.LandingChat)
web.Post("/landing/generate", apiControllers.LandingGenerate)
web.Post("/landing/payment", apiControllers.LandingCreatePayment)
web.Get("/landing/answers/:token", apiControllers.LandingGetAnswers)
web.Get("/landing/download/:token", apiControllers.LandingDownload)
// Config de IA activa (para Landing Generator)
web.Get("/landing/ai-config", apiControllers.LandingGetAiConfig)
}