package middlewares import ( "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // umindScopeKey guarda en el contexto los tenants que el que llama puede ver. // // nil → staff, sin restricción // []uint{} → no ve nada (fail-closed) // []uint{1, 7} → solo esos tenants // // Es el ÚNICO lugar donde se decide el alcance: los handlers de uMind se // montan bajo /app/umind (staff) y /portal/umind (cliente) con distinto // middleware de scope, pero comparten implementación. Un bug acá es una fuga // de datos entre clientes, así que no se replica esta lógica en ningún lado. const umindScopeKey = "umind_tenant_ids" // UmindScopeStaff deja el scope sin restricción. Existe como middleware // explícito (en vez de "no poner nada") para que una ruta sin scope sea un // error visible y no un permiso implícito. func UmindScopeStaff(c *fiber.Ctx) error { c.Locals(umindScopeKey, []uint(nil)) return c.Next() } // UmindScopePortal acota al cliente autenticado en el portal: sus tenants son // los de los Cliente a los que tiene acceso (uno si es cliente, varios si es // partner), resueltos por el mismo GetClienteIDsForPortalUser que ya autoriza // el resto del portal. func UmindScopePortal(c *fiber.Ctx) error { u := PortalUserFromLocals(c) if u == nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "no autenticado"}) } clienteIDs := models.GetClienteIDsForPortalUser(u) tenants, err := models.GetUmindTenantsByClientes(clienteIDs) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo resolver el alcance"}) } // Siempre un slice no-nil, incluso vacío: nil significa "staff, sin // restricción", y confundir los dos le daría acceso total a un cliente. ids := make([]uint, 0, len(tenants)) for _, t := range tenants { ids = append(ids, t.ID) } c.Locals(umindScopeKey, ids) return c.Next() } // UmindScopeDe devuelve (tenants permitidos, esStaff). func UmindScopeDe(c *fiber.Ctx) ([]uint, bool) { v := c.Locals(umindScopeKey) if v == nil { // Ruta sin middleware de scope: fail-closed. Mejor romper visiblemente // que servir datos de todos los clientes por omisión. return []uint{}, false } ids, ok := v.([]uint) if !ok { return []uint{}, false } return ids, ids == nil } // UmindPuedeVerTenant es el chequeo que hacen los handlers. func UmindPuedeVerTenant(c *fiber.Ctx, tenantID uint) bool { ids, esStaff := UmindScopeDe(c) if esStaff { return true } for _, id := range ids { if id == tenantID { return true } } return false }