feat(umind): el cliente administra sus agentes desde el portal
Acá es donde uMind deja de ser una herramienta interna: el cliente entra a /portal/studio con su sesión de portal y gestiona lo suyo. - UmindScopePortal/UmindScopeStaff es el ÚNICO punto donde se decide el alcance. El del cliente sale de GetClienteIDsForPortalUser, el mismo que ya autoriza el resto del portal. nil = staff sin restricción, slice vacío = no ve nada; una ruta sin scope también cae en "no ve nada" para que olvidarse el middleware falle visible y no abra todo. - Un solo set de handlers montado bajo /app/umind y /portal/umind (RegistrarRutasUmind). Duplicarlos sería duplicar las chances de olvidar un chequeo. - Guarda de acceso en TODOS los handlers, incluidos los sub-recursos que llegan por :id (documento, tool, canal, conexión): hay que cargarlos para saber de quién son, si no un cliente podría borrar el canal de otro adivinando el id. Responden 404, no 403: un 403 confirmaría que el recurso existe. - Cierra un bug preexistente: las lecturas GET /app/umind/* no tenían SoloAdmin ni pasaban por MenuMiddleware, así que cualquier usuario de staff podía leer los tenants de todos los clientes. - Límite de agentes por plan (409 con mensaje claro). Un tenant sin plan no tiene límite: cortarles de golpe sería peor que dejarlos como estaban. - /umind/ai-configs reemplaza con alcance a /app/api/ai-config/select, que devolvía TODAS las configs del sistema. - El SPA deduce por la URL si es staff o cliente (base del router, prefijo de API y URL de login) y oculta lo que es solo de staff. - Test de aislamiento entre clientes: 7 casos, incluido que un scope vacío no se confunda con staff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
08265510ea
commit
5b78f6677c
@@ -57,4 +57,17 @@ func PortalRoutes(app fiber.Router) {
|
||||
portal.Post("/mi-perfil/telegram-init", controllers.PortalTelegramInit)
|
||||
portal.Get("/mi-perfil/telegram-status", controllers.PortalTelegramStatus)
|
||||
portal.Post("/mi-perfil/telegram-validar", controllers.PortalTelegramValidar)
|
||||
|
||||
// ─── uMind Studio para el cliente ──────────────────────────────────────────
|
||||
// Los MISMOS handlers que usa el staff, pero con UmindScopePortal: el
|
||||
// cliente solo alcanza los tenants de sus Cliente. Sin middleware de
|
||||
// escritura extra — administrar lo suyo es justamente el punto.
|
||||
RegistrarRutasUmind(portal, middlewares.UmindScopePortal, nil)
|
||||
|
||||
// El SPA (mismo build que /orchestrator) con fallback para vue-router.
|
||||
studioFallback := func(c *fiber.Ctx) error {
|
||||
return c.SendFile("./public/orchestrator/index.html")
|
||||
}
|
||||
portal.Get("/studio", studioFallback)
|
||||
portal.Get("/studio/*", studioFallback)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
|
||||
)
|
||||
|
||||
// RegistrarRutasUmind monta los mismos handlers de uMind bajo dos prefijos:
|
||||
// /app/umind para el staff y /portal/umind para el cliente. Una sola
|
||||
// implementación, dos historias de autenticación — duplicar los handlers sería
|
||||
// duplicar también las chances de que uno se olvide un chequeo de alcance.
|
||||
//
|
||||
// scope decide QUÉ tenants ve el que llama (UmindScopeStaff/UmindScopePortal).
|
||||
// escritura middleware extra para POST/PUT/DELETE (SoloAdmin del lado staff;
|
||||
// nil del lado del cliente, que ya está acotado por su scope).
|
||||
//
|
||||
// El CRUD de tenants NO se registra acá a propósito: crear/borrar tenants y
|
||||
// asignarles cliente y plan es del staff, el cliente solo administra lo que
|
||||
// hay adentro.
|
||||
func RegistrarRutasUmind(g fiber.Router, scope fiber.Handler, escritura fiber.Handler) {
|
||||
// Concatena el scope con el middleware de escritura, si lo hay. El scope
|
||||
// va SIEMPRE, también en las lecturas.
|
||||
w := func(h ...fiber.Handler) []fiber.Handler {
|
||||
out := []fiber.Handler{scope}
|
||||
if escritura != nil {
|
||||
out = append(out, escritura)
|
||||
}
|
||||
return append(out, h...)
|
||||
}
|
||||
r := func(h ...fiber.Handler) []fiber.Handler {
|
||||
return append([]fiber.Handler{scope}, h...)
|
||||
}
|
||||
|
||||
g.Get("/umind/tenants", r(controllers.GetUmindTenants)...)
|
||||
g.Get("/umind/agentes", r(controllers.GetUmindAgentesHandler)...)
|
||||
g.Post("/umind/agentes", w(controllers.CreateUmindAgenteHandler)...)
|
||||
g.Put("/umind/agentes/:id", w(controllers.UpdateUmindAgenteHandler)...)
|
||||
g.Delete("/umind/agentes/:id", w(controllers.DeleteUmindAgenteHandler)...)
|
||||
|
||||
g.Get("/umind/documentos", r(controllers.GetUmindDocumentosHandler)...)
|
||||
g.Post("/umind/documentos", w(controllers.CreateUmindDocumentoHandler)...)
|
||||
g.Delete("/umind/documentos/:id", w(controllers.DeleteUmindDocumentoHandler)...)
|
||||
|
||||
g.Get("/umind/sesiones", r(controllers.GetUmindSesionesHandler)...)
|
||||
g.Get("/umind/historial", r(controllers.GetUmindHistorialHandler)...)
|
||||
|
||||
g.Get("/umind/tools", r(controllers.GetUmindHerramientasHandler)...)
|
||||
g.Post("/umind/tools", w(controllers.CreateUmindHerramientaHandler)...)
|
||||
g.Put("/umind/tools/:id", w(controllers.UpdateUmindHerramientaHandler)...)
|
||||
g.Delete("/umind/tools/:id", w(controllers.DeleteUmindHerramientaHandler)...)
|
||||
|
||||
g.Get("/umind/canales", r(controllers.GetUmindCanalesHandler)...)
|
||||
g.Post("/umind/canales", w(controllers.CreateUmindCanalHandler)...)
|
||||
g.Put("/umind/canales/:id", w(controllers.UpdateUmindCanalHandler)...)
|
||||
g.Delete("/umind/canales/:id", w(controllers.DeleteUmindCanalHandler)...)
|
||||
|
||||
g.Post("/umind/chat", r(controllers.UmindChatPruebaHandler)...)
|
||||
|
||||
g.Get("/umind/conexiones", r(controllers.GetUmindConexionesHandler)...)
|
||||
g.Get("/umind/conexiones/conectar", w(controllers.UmindConectarHandler)...)
|
||||
g.Delete("/umind/conexiones/:id", w(controllers.DeleteUmindConexionHandler)...)
|
||||
|
||||
g.Get("/umind/eventos", r(controllers.GetUmindEventosHandler)...)
|
||||
g.Get("/umind/uso", r(controllers.GetUmindUsoHandler)...)
|
||||
g.Get("/umind/ai-configs", r(controllers.GetUmindAiConfigsHandler)...)
|
||||
}
|
||||
+8
-28
@@ -356,34 +356,14 @@ func UserRoutes(app fiber.Router) {
|
||||
// desde qué dominios se puede llamar al widget, así que crear/editar es
|
||||
// solo para administradores.
|
||||
protected.Get("/umind", middlewares.MenuMiddleware, controllers.UmindIndex)
|
||||
protected.Get("/umind/tenants", controllers.GetUmindTenants)
|
||||
protected.Post("/umind/tenants", middlewares.SoloAdmin, controllers.CreateUmindTenantHandler)
|
||||
protected.Put("/umind/tenants/:id", middlewares.SoloAdmin, controllers.UpdateUmindTenantHandler)
|
||||
protected.Delete("/umind/tenants/:id", middlewares.SoloAdmin, controllers.DeleteUmindTenantHandler)
|
||||
protected.Get("/umind/agentes", controllers.GetUmindAgentesHandler)
|
||||
protected.Post("/umind/agentes", middlewares.SoloAdmin, controllers.CreateUmindAgenteHandler)
|
||||
protected.Put("/umind/agentes/:id", middlewares.SoloAdmin, controllers.UpdateUmindAgenteHandler)
|
||||
protected.Delete("/umind/agentes/:id", middlewares.SoloAdmin, controllers.DeleteUmindAgenteHandler)
|
||||
protected.Get("/umind/documentos", controllers.GetUmindDocumentosHandler)
|
||||
protected.Post("/umind/documentos", middlewares.SoloAdmin, controllers.CreateUmindDocumentoHandler)
|
||||
protected.Delete("/umind/documentos/:id", middlewares.SoloAdmin, controllers.DeleteUmindDocumentoHandler)
|
||||
protected.Get("/umind/sesiones", controllers.GetUmindSesionesHandler)
|
||||
protected.Get("/umind/historial", controllers.GetUmindHistorialHandler)
|
||||
protected.Get("/umind/tools", controllers.GetUmindHerramientasHandler)
|
||||
protected.Post("/umind/tools", middlewares.SoloAdmin, controllers.CreateUmindHerramientaHandler)
|
||||
protected.Put("/umind/tools/:id", middlewares.SoloAdmin, controllers.UpdateUmindHerramientaHandler)
|
||||
protected.Delete("/umind/tools/:id", middlewares.SoloAdmin, controllers.DeleteUmindHerramientaHandler)
|
||||
protected.Get("/umind/canales", controllers.GetUmindCanalesHandler)
|
||||
protected.Post("/umind/canales", middlewares.SoloAdmin, controllers.CreateUmindCanalHandler)
|
||||
protected.Put("/umind/canales/:id", middlewares.SoloAdmin, controllers.UpdateUmindCanalHandler)
|
||||
protected.Delete("/umind/canales/:id", middlewares.SoloAdmin, controllers.DeleteUmindCanalHandler)
|
||||
protected.Post("/umind/chat", controllers.UmindChatPruebaHandler)
|
||||
protected.Get("/umind/conexiones", controllers.GetUmindConexionesHandler)
|
||||
protected.Get("/umind/conexiones/conectar", middlewares.SoloAdmin, controllers.UmindConectarHandler)
|
||||
protected.Get("/umind/conexiones/callback/:proveedor", controllers.UmindOAuthCallbackHandler)
|
||||
protected.Delete("/umind/conexiones/:id", middlewares.SoloAdmin, controllers.DeleteUmindConexionHandler)
|
||||
protected.Get("/umind/eventos", controllers.GetUmindEventosHandler)
|
||||
protected.Get("/umind/uso", controllers.GetUmindUsoHandler)
|
||||
// Las escrituras siguen siendo SoloAdmin del lado del staff; el alcance
|
||||
// (qué tenants ve cada quien) lo decide el middleware de scope, que es el
|
||||
// mismo contrato para /app/umind y /portal/umind.
|
||||
RegistrarRutasUmind(protected, middlewares.UmindScopeStaff, middlewares.SoloAdmin)
|
||||
protected.Post("/umind/tenants", middlewares.SoloAdmin, middlewares.UmindScopeStaff, controllers.CreateUmindTenantHandler)
|
||||
protected.Put("/umind/tenants/:id", middlewares.SoloAdmin, middlewares.UmindScopeStaff, controllers.UpdateUmindTenantHandler)
|
||||
protected.Delete("/umind/tenants/:id", middlewares.SoloAdmin, middlewares.UmindScopeStaff, controllers.DeleteUmindTenantHandler)
|
||||
protected.Get("/umind/conexiones/callback/:proveedor", middlewares.UmindScopeStaff, controllers.UmindOAuthCallbackHandler)
|
||||
// Planes: definen el límite de agentes y los precios por consumo. Solo staff.
|
||||
protected.Get("/umind-planes", middlewares.MenuMiddleware, controllers.UmindPlanesPage)
|
||||
protected.Get("/umind-planes/list", controllers.GetUmindPlanesHandler)
|
||||
|
||||
Reference in New Issue
Block a user