RBAC en el backend de GraphQL usando CASL y graphql-shield y reglas de uso compartido con mi interfaz de React

1

Estoy ejecutando Mongoose y exponiendo una API usando GraphQL (Apollo).

Quiero implementar un RBAC y después de investigar un poco, obtuve una solución usando CASL y graphql-shield. Idealmente, me gustaría compartir las reglas con mi interfaz de React.


Primer paso, planificar en una hoja de papel.

Primero definiría mis acciones: Crear, Leer, Actualizar, Eliminar.

Entonces definiría mis temas: Coche, Motocicleta.

Una vez hecho esto, procedería a definir mis roles: CarSpecialist, MotoSpecialist, Admin. Luego definiría algunas condiciones: "el sujeto es mío", etc.

Finalmente, asignaría a cada rol, un conjunto de habilidades (combinación de acción, sujeto, condiciones).


Ahora, con todo esto hecho, empiezo a codificar mi solución.

Empiezo escribiendo las habilidades en CASL: las acciones y los temas son bastante sencillos de definir.

Las condiciones son un poco más complicadas y tengo al menos dos opciones:

  • Utilizo nociones "vagas" que, a su vez, deben ser interpretadas por lo que sea necesario para hacerlas cumplir (al principio o al final).

  • Utilizo el complemento de integración CASL mongoose, a costa de perder la capacidad de compartir con mi interfaz.


¿Alguna entrada sobre la que elegir?

Ahora, una vez que se definen las habilidades CASL, ¿depende de graphql-shield hacerlas cumplir?

¿Cómo hago el mapeo entre acciones (CASL), sujetos y condiciones a términos de graphql: esquema, consulta, mutaciones ...?

1

Intentaré responder a esta pregunta tanto como pueda:

  1. No pierde la capacidad de compartir permisos con la interfaz de usuario si usa las condiciones predeterminadas. Las condiciones se interpretan en js cuando ejecuta ability.can. Entonces, si el lenguaje de consulta mongo está bien para usted, ¡no es necesario que lo cambie!
  2. Graphql shield es un tipo especial de middleware graphql. Si usa middlewares casl y graphql, ¡no necesita el escudo graphql! use casl + middleware graphql personalizado o graphql-shield
  3. Cada tipo de graphql tiene un tipo de fuente subyacente. El tipo de fuente es básicamente su modelo de dominio o simplemente el modelo de base de datos que encapsula la lógica empresarial. Este es su mapeo :) solo verifique los permisos en el tipo de fuente y eso es todo. Pero si comparte permisos con la interfaz de usuario, entonces necesita transformar los permisos de backend (antes de enviarlos a la interfaz de usuario) que están escritos para los tipos de fuente, ¡a aquellos que se pueden aplicar al tipo graphql! Alternativamente, puede exponer algunos accesorios privados (por ejemplo, ownerIdde Car) como parte del tipo graphql. Pero si el único propósito de esto es satisfacer el intercambio de permisos, entonces optaría por la opción de transformación:
function defineAbility(user, props) {
   const { can, rules} = new AbilityBuilder(Ability)

   can('read', 'Post', { [props.authorId]: user.id })
   // ...

   return rules;
}

const currentUser = { id: 1 }
const backendRules = defineAbility(currentUser, {
  authorId: 'authorId'
});

const uiRules = defineAbility(currentUser, {
  authorId: 'author.id'
});

Alternativamente, puede verificar los permisos en el backend y compartir los resultados con el frontend al exponer el subtipo en cada tipo de graphql:


{
   cars {
     items {
       permissions {
         canUpdate
         canRead
       }
     }
   }
}

Las consecuencias de esto es que su servidor dedicará más tiempo a generar respuestas, especialmente cuando recupera elementos para la paginación. Por lo tanto, verifique los tiempos de respuesta antes de continuar con eso. El buen punto de esto es que no necesita casl en la interfaz de usuario, y la lógica de verificación de permisos está completamente oculta en el backend