zenstackhq / zenstack

Fullstack TypeScript toolkit that enhances Prisma ORM with flexible Authorization layer for RBAC/ABAC/PBAC/ReBAC, offering auto-generated type-safe APIs and frontend hooks.
https://zenstack.dev
MIT License
2.07k stars 88 forks source link

[Feature Request] Permissions checker #242

Closed keanugrieves closed 5 months ago

keanugrieves commented 1 year ago

Is your feature request related to a problem? Please describe. I'd like to be able to check whether user has the permission to perform a create, update or delete operation so I could reflect it on the frontend.

Describe the solution you'd like A "simple" (ha ha) function on each record that optionally takes an input and returns a boolean that indicates whether the user has permission.

Describe alternatives you've considered Rewriting my zmodel permissions in an additional place as a separately callable function.

Additional context Brainstorming the structure, given the model Foo:

// If user has any create permissions, return true when input not provided or whether they can create with input
foo.canCreate(input?: Prisma.FooUncheckedCreateInput) => boolean;

// If user has update permissions on current record, return true when input not provided or return whether they can update with current record and input
foo.canUpdate(input?: Prisma.FooUncheckedUpdateInput) => boolean;

// If user has delete permissions on current record, return true
foo.canDelete() => boolean;
ymc9 commented 10 months ago

A design challenge is how to provide the flexibility to allow permission checking given different granularity of conditions, from the weakest one (only giving model type and CRUD operation), to the strongest one (giving CRUD operation and its entire query/mutation input).

Moving some discussions on Discord here:

const db = enhance(prisma, { user: ... });

// check if the user can read any post, equivalent to `count > 0`
await db.post.check('read');

// check read with a filter, equivalent to `count > 0` with filter
await db.post.check('read', { where: ... });

// check if the user can possibly create, "possibly" means there exists at least one payload that can make the create succeed
await db.post.check('create');

// variation of the above, by requiring some payload field values but keep the rest "free"
await db.post.check('create', { data: ...});

// "delete" check is the same as "read", and "update" check is the same as "create"

We can then expose the check API to the API layer (e.g., '/api/model/post/check'), and client hooks like:

const { data: canRead } = useCheckPost('read');
const { data: canCreate} = useCheckPost('create', { data: { published: true } });
gczh commented 8 months ago

@ymc9 adding some questions here:

  1. Would the approach to implement this require more database calls or can these all be done at runtime? Ideally, it shouldn't require more database calls for performance reasons.
ymc9 commented 8 months ago

@ymc9 adding some questions here:

  1. Would the approach to implement this require more database calls or can these all be done at runtime? Ideally, it shouldn't require more database calls for performance reasons.

Thanks @gczh . I believe it doesn't involve database calls, however it's possible it'll be served as a backend api so it'll involve an api from the frontend.

We haven't settled the design yet, and I'll share more information when the work starts.

Azzerty23 commented 8 months ago

Hi @ymc9, I'm interested in working on it. How do you envision the design? Initially, I was wondering if it might be better to create a single generic hook to fetch multiple permissions in a single API call.

To ensure that I could achieve this, I started a draft that I will share here. Hopefully, this will help in the process.

ymc9 commented 8 months ago

Hi @ymc9, I'm interested in working on it. How do you envision the design? Initially, I was wondering if it might be better to create a single generic hook to fetch multiple permissions in a single API call.

To ensure that I could achieve this, I started a draft that I will share here. Hopefully, this will help in the process.

Thank you for starting this work @Azzerty23 ! Another dream feature and I really appreciate it!

Let me go through the draft PR and share my thoughts there.

nahtnam commented 6 months ago

Love this idea, it would also be useful for writing tests to ensure that user A can't read user B's records and other similar permission tests. One of the many reasons I prefer zenstack over supabase since RLS is much harder to test

ymc9 commented 5 months ago

Implemented in 2.1.0