Jerarquía de Roles

El problema

Tenés usuarios con distintos niveles de acceso: admin, usuario premium, usuario gratis. En cada endpoint necesitás verificar que el usuario tenga permiso suficiente. La solución instintiva es comparar strings: if role != "admin".

Eso funciona al principio. Pero cuando tenés herencia (admin puede hacer todo lo que puede user, user premium puede hacer todo lo que puede user gratis), las comparaciones de strings se vuelven un desastre. Cada endpoint tiene su propia lógica, y cuando agregás un rol nuevo, tenés que tocar N archivos.

Por qué jerarquía numérica

Modelo el rol como un valor con ranking interno y un solo método: ¿mi rol es al menos X?

func (r Role) AtLeast(target Role) bool {
    return r.rank() >= target.rank()
}

El middleware queda limpio:

func requireRole(minRole domain.Role) gin.HandlerFunc {
    return func(c *gin.Context) {
        if !claims.Role.AtLeast(minRole) {
            c.AbortWithStatusJSON(403, "no tenés permiso")
            return
        }
        c.Next()
    }
}

Y el router simplemente dice lo que necesita:

admin := r.Group("/admin", requireRole(RoleAdmin))
admin.GET("/users", handler.ListUsers)

Lo que aprendí

  • Un solo lugar donde cambiar la jerarquía: si mañana agregás un rol "moderador" entre admin y user, cambiás un switch y listo.
  • Los tests de roles son los más fáciles de escribir: son tabulares, puros, sin mocks. Si la jerarquía cambia, los tests te avisan.
  • No sirve para todo: si necesitás permisos cruzados (ej. "solo puede editar su propio perfil"), la jerarquía no alcanza. Ahí necesitás ABAC o políticas.

Alternativas que consideré

Opción Por qué no
Comparar strings en cada handler No escala, herencia manual, fácil de olvidar
Lista de permisos granular (can_create_user) Overkill para 3 roles, agrega complejidad prematura

Cuándo usarlo

  • ✅ Cuando los roles forman una pirámide clara (admin > user > invitado)
  • ✅ Cuando querés que el middleware sea de 5 líneas
  • ❌ Cuando los permisos son cruzados o dependen del recurso
  • ❌ Si tenés 2 roles sin herencia — una comparación directa es más honesta

Relacionado: Dual-Token JWT | Testing de Calidad | Conocimientos