Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Allow custom messages in policies #1723

Open
LilaRest opened this issue Sep 21, 2024 · 4 comments
Open

[Feature Request] Allow custom messages in policies #1723

LilaRest opened this issue Sep 21, 2024 · 4 comments

Comments

@LilaRest
Copy link

Is your feature request related to a problem? Please describe.
Actually, once a policy is violated, ZenStack returns the following error object, which is not super descriptive for consumers. That becomes an even more important problem if the ZenStack-generated API is to be exposed to other developers as a public API or in an SDK.

denied by policy: user entities failed 'create' check
Code: P2004
Meta: { reason: 'ACCESS_POLICY_VIOLATION' }

Describe the solution you'd like.
It'd be gorgeous to allow a third message parameter into @@allow and @@deny clauses. Something like that:

@@allow("create", organization.memberships?[actor == auth() && role == "Admin"], "Only admins can create memberships")

Then, on policy violation, ZenStack would return the following:

denied by policy: user entities failed 'create' check
Code: P2004
Meta: { reason: 'ACCESS_POLICY_VIOLATION', message: 'Only admins can create memberships' }
@ymc9
Copy link
Member

ymc9 commented Oct 25, 2024

I think this will be a very useful feature, but not straightforward to achieve. The reason behind is when evaluating access policies, ZenStack combines all rules in a model together and injects into a Prisma query (for better performance); so when a violation happens, it doesn't really know which rule caused the violation.

Maybe we can consider introducing a "debug" mode just for diagnosis purposes. When that mode is ON, when a violation occurs, it automatically retries the same operation by applying the policy rules one by one to know which one allowed/denied it.

@LilaRest
Copy link
Author

ZenStack combines all rules in a model together and injects into a Prisma query (for better performance); so when a violation happens, it doesn't really know which rule caused the violation.

Doesn't Zod provide paths inside of error's issues? That could help linking the Zod output to the specific field that led to the error.

https://zod.dev/ERROR_HANDLING?id=zodissue

@ymc9
Copy link
Member

ymc9 commented Oct 26, 2024

ZenStack combines all rules in a model together and injects into a Prisma query (for better performance); so when a violation happens, it doesn't really know which rule caused the violation.

Doesn't Zod provide paths inside of error's issues? That could help linking the Zod output to the specific field that led to the error.

https://zod.dev/ERROR_HANDLING?id=zodissue

Most of ZenStack's access control enforcement is not done via Zod in the Node runtime, but through injecting into database queries (and thus evaluated by the database, through Prisma). For example, when evaluating an "update" rule during an updateMany call, instead of pulling the records out of the database and checking which rows meet the update condition, ZenStack injects the "update" policies into the where clause of updateMany, so the filtering and updating can be efficiently done by the db altogether.

This is even the case for "create". You may have a policy like this:

model Post {
  owner User @relation(...)

  @@allow('create', !owner.banned)
}

To evaluate the "create" condition, we actually need to access its owner relation. ZenStack's general approach is to, inside a transaction, create the entity and then see if we can read it back with the "create" policies as filter; if not, revert. In some cases, e.g., when the create policies don't involve any relation, we internally short-circuit the database read with an in-memory check. But it's considered an internal optimization, and the general approach is to rely on the database-side evaluation.

So given this approach, generally speaking we don't really know which specific policy failed a mutation, as they are evaluated as a whole on the db side ... In some specific cases we can, but I'm not sure how to wrap it into an intuitive feature 😂

This documentation has a bit more background information: https://zenstack.dev/docs/the-complete-guide/part1/under-the-hood

@LilaRest
Copy link
Author

@ymc9 That's super interesting, and makes total sense, thanks for taking time to explain.

I don't neither see a simple solution to precisely solve that problem, but an intermediary way could be to replace the obscure ACCESS_POLICY_VIOLATION with a list of potential causes for the failure, e.g.,

Permission Error: One of the following conditions wasn't met and reverted the query:
- Only post owners can update their posts
- Banned users cannot read their posts

And the list of "possible causes" would be simply built by filtering available messages on a model with the query action (create, read, update, delete).

Would that make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants