fix(permisos): compara la ruta completa y avisa de los menús que dan 404

Auditoría del sistema de permisos a partir del 404 en /app/doc/paginas.

1. La comparación de permisos usaba solo el último segmento de la URL
   (lo que va después del último '/'). Rompía en las dos direcciones: con
   permiso sobre /app/doc/categorias se entraba a cualquier otra ruta
   terminada en "categorias", y a la vez el permiso parecía no aplicarse
   donde sí correspondía porque el segmento coincidía por casualidad.
   Ahora se compara la ruta completa, cortando en el separador para que
   /app/doc/paginas no habilite /app/doc/paginas-privadas.

2. El submódulo de Documentación nunca se sembró, aunque las rutas y las
   vistas existen desde mayo. Había que crear el ítem del menú a mano, y
   una URL mal tipeada ahí se ve exactamente igual que un permiso mal
   asignado: un 404. Ahora se siembra con la URL correcta.

3. El submódulo "Statuspage" apuntaba a /app/statuspage, que no existe en
   ninguna parte — la página real es la pública /status. Se corrige el
   seed y también el registro ya creado en la base.

4. Chequeo en el arranque que recorre los submódulos de la base y avisa
   cuáles apuntan a una URL sin ruta. Cubre los creados a mano, que es
   justo donde el compilador y los tests no llegan.

5. Test que cruza las URLs sembradas contra las rutas registradas: fue el
   que encontró lo de statuspage.

Queda pendiente y es más grande: de 454 rutas protegidas solo 54 pasan por
MenuMiddleware. El permiso gatea la página HTML pero no los endpoints de
datos — cualquier usuario autenticado puede llamar a
GET /app/doc/loadpaginas o DELETE /app/doc/paginas/:id sin tener el
submódulo asignado. Se reporta antes de tocarlo porque cerrarlo de golpe
puede dejar gente afuera.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-15 09:21:44 -05:00
co-authored by Claude Sonnet 5
parent ad3458bb71
commit 105ab44759
5 changed files with 278 additions and 20 deletions
+34 -19
View File
@@ -87,25 +87,15 @@ func MenuMiddleware(c *fiber.Ctx) error {
return c.Next()
}
// Verificar si la URL de la solicitud está en la lista de URLs permitidas
requestURL := c.Path()
// Obtener el índice del último '/' en la URL de la solicitud
lastSlashIndex := strings.LastIndex(requestURL, "/")
if lastSlashIndex != -1 {
// Obtener solo la parte de la URL después del último '/'
requestURL = requestURL[lastSlashIndex+1:]
}
// Verificar si la URL modificada está en la lista de URLs permitidas
urlAllowed := false
for _, url := range urls {
// Comparar solo la parte de la URL después del último '/'
if requestURL == url[strings.LastIndex(url, "/")+1:] {
urlAllowed = true
break
}
}
// Se compara la ruta COMPLETA contra las URLs permitidas.
//
// Antes se comparaba solo el último segmento (lo que va después del último
// '/'), y eso rompía en las dos direcciones: dos rutas distintas que
// terminan igual quedaban indistinguibles — con permiso sobre
// /app/doc/categorias se entraba a cualquier otra ruta terminada en
// "categorias" — y a la vez el permiso parecía no aplicarse donde sí
// debía, porque el segmento coincidía por casualidad.
urlAllowed := PuedeVerRuta(c.Path(), urls)
// Si la URL no está permitida, devolver un error
if !urlAllowed {
@@ -118,3 +108,28 @@ func MenuMiddleware(c *fiber.Ctx) error {
// Continuar con la siguiente función de middleware o manejador
return c.Next()
}
// PuedeVerRuta decide si una ruta está cubierta por alguna de las URLs que el
// rol tiene asignadas.
//
// Coincide la ruta exacta o cualquier ruta por debajo de ella: quien tiene
// /app/doc/paginas también puede entrar a /app/doc/paginas/7, que es la misma
// pantalla con un detalle. No coincide /app/doc/paginas-privadas, porque el
// corte se hace en el separador y no en el prefijo de texto — si no, un
// permiso abriría rutas vecinas que solo comparten el comienzo del nombre.
func PuedeVerRuta(ruta string, permitidas []string) bool {
ruta = strings.TrimRight(ruta, "/")
if ruta == "" {
ruta = "/"
}
for _, u := range permitidas {
u = strings.TrimRight(strings.TrimSpace(u), "/")
if u == "" {
continue
}
if ruta == u || strings.HasPrefix(ruta, u+"/") {
return true
}
}
return false
}