Implementación de RBAC
13 portales por rol, roles que se desalinearon entre dos seeders, y qué encontró una revisión de seguridad real.
UFT tiene 13 portales por rol a lo largo de una jerarquía de dominio (Federación → Liga → Club → Equipo → Jugador) — presidentes de federación, presidentes de liga, admins de club, coaches, líderes de equipo, organizadores de torneo, anotadores y jugadores necesitan capacidades distintas y más limitadas sobre los mismos datos subyacentes. Al principio, los roles se sembraron de forma inconsistente entre dos seeders separados más strings ad-hoc usados directamente en decoradores @Roles(...), así que el mismo rol conceptual terminó con múltiples variantes de nombre literal en la base de datos.
En vez de una migración riesgosa para renombrar cada rol de forma consistente en datos en vivo, construí una tabla de alias de rol que mapea variantes conocidas ("Organizador", "Tournament Organizer" → "Organizador de Torneo", etc.) a un nombre canónico, así una verificación @Roles(...) pasa sin importar con qué variante se haya sembrado a un usuario — agrupando solo variantes del MISMO nivel de privilegio, nunca fusionando entre niveles. El control de acceso en sí pasa por un único guard que verifica metadata basada en roles (@Roles) y en permisos (@RequirePermissions): si una ruta no declara ninguno, queda abierta a cualquier usuario autenticado; si declara alguno, el request debe satisfacerlo. "Super Administrador" es un bypass codificado, deliberadamente — seguro específicamente porque ese rol no se puede autoasignar (se encontró y cerró una ruta de escalada de privilegios que permitía a un usuario otorgarse admin a sí mismo). El default seguro es explícito: una verificación requerida que simplemente está ausente nunca concede acceso por sí sola.
La tabla de alias evitó un renombre masivo sobre datos en vivo, pero ahora es una pieza permanente de indirección — y coexiste con dos definiciones paralelas de entidades de rol/permiso (una bajo auth/, otra bajo un módulo rbac/ separado) que nunca se unificaron por completo. Deuda real y reconocida: pragmática en el momento, no gratis.
Una revisión de seguridad ejecutada en julio de 2026 encontró y corrigió cuatro problemas concretos: la ruta de escalada de privilegios mencionada arriba, un guard que no estaba imponiendo realmente lo que parecía, un endpoint de seed abierto, y contraseñas apareciendo en logs — cada uno con un test de regresión agregado después. Una revisión de IDOR en endpoints de subida de archivos encontró que no verificaban que el usuario que subía el archivo realmente fuera dueño del recurso modificado; eso ahora se valida contra el usuario autenticado, respaldado por 11 tests de regresión. Otros dos endpoints de subida siguen explícitamente abiertos y en seguimiento, no dejados sin corregir en silencio. La lección real: un sistema de permisos que acumula variantes de nombre de rol entre múltiples seeders eventualmente va a necesitar una capa de compatibilidad o una migración disruptiva — la tabla de alias fue la opción de menor riesgo dado que había datos en vivo, pero las dos definiciones paralelas de entidades son el arreglo real que todavía se debe. Y una revisión de seguridad solo es tan útil como lo que pasa después — encontrar cuatro problemas importó menos que acompañar cada uno con un test de regresión, y reconocer honestamente lo que sigue abierto.