stalniy / casl

CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access
https://casl.js.org/
MIT License
5.87k stars 265 forks source link

[prisma] Using extensions to remove the need for `subject` helper with prisma #783

Open Faithfinder opened 1 year ago

Faithfinder commented 1 year ago

Is your feature request related to a problem? Please describe. Currently, you need the subject helper to use CASL with Prisma. With client extensions being stable now, we can do better.

Describe the solution you'd like Provide a pre-packaged extension in CASL, that will add the model name to a field in prisma client (maybe use a symbol for the field?). Make the default detectSubjectType know how to read that field.

Describe alternatives you've considered This is fairly easily solvable in user-land, just thought it might be a good idea if the prisma package made the process streamlined.

stalniy commented 1 year ago

Makes sense. Im glad to accept PR.

PavlMais commented 1 year ago

Why not go further and write an extension that will add additional query functions that will accept the ability, Under the hood, it might look like this:

Prisma.defineExtension({
  model: {
    $allModels: {
      async tryFindMany<T, A extends Prisma.Args<T, 'findMany'>>(
        this: T,
        { ability, ...args }: A & { ability: AppAbility },
      ): Promise<Prisma.Result<T, A, 'findMany'>> {
        const context = Prisma.getExtensionContext(this) as { name: Prisma.ModelName };
        const where = {
          AND: [accessibleBy(args.ability, 'read')[context.name], args.where],
        };

        return (context as any).findMany({ ...args, where });
      },
      // ... other methods
    },
  },
});
stalniy commented 1 year ago

Adding methods is a bit more opinionated, also I don’t know how to adjust prisma types in this case. I have experience adding methods to mongoose models, it made initial setup complicated and eventually I deprecated that and implemented accessibleBy helper, similar to Prisma’s one

so, there is no much difference what to use

model.find(accessibleBy().Post) or model.accessibleBy() in both cases you need to know that you need to use specific method/helper. It’s also doesn’t have much value from encapsulation point of view.

However boilerplate reduction can be reduced with methods but it’s very little reduction.

If you see this differently, please share your thoughts

PavlMais commented 1 year ago

@stalniy what do you mean by to adjust prisma types?

jrmyio commented 9 months ago

Maybe this could be handy: https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/query

According to their docs: For example, in a row-level security (RLS) scenario, you can keep each user in an entirely separate client. With middlewares, all users are active in the same client.

This means you could get a user /ability scoped prisma client that works on all models and operations and adds to nessecary checks as defined in your ability.

const prisma = new PrismaClient().$extends({
  query: {
    $allOperations({ model, operation, args, query }) {
      /* some  logic here that modifies the args / query depending on the ability */
      return query(args)
    },
  },
})