zenstack icon indicating copy to clipboard operation
zenstack copied to clipboard

[Feature Request] Allow custom messages in policies

Open LilaRest opened this issue 1 year ago • 4 comments

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' }

LilaRest avatar Sep 21 '24 08:09 LilaRest

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.

ymc9 avatar Oct 25 '24 20:10 ymc9

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

LilaRest avatar Oct 26 '24 08:10 LilaRest

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

ymc9 avatar Oct 26 '24 16:10 ymc9

@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?

LilaRest avatar Oct 27 '24 14:10 LilaRest