You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: versioned_docs/version-3.x/orm/09-validation.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,13 +15,13 @@ Input validation is a ZModel feature and doesn't exist in Prisma.
15
15
Input validation is only applied to the ORM APIs like `create`, `update`, etc. It's not enforced at the query builder level.
16
16
:::
17
17
18
-
When defining a model field in ZModel, you specify its type, which provides a basic level of runtime validation - when an incompatible value is passed to such field during creation or update, the underlying database will reject the operation.
18
+
When defining a model field in ZModel, you specify its type, which provides a basic level of runtime validation - when an incompatible value is passed to the field during creation or update, the ORM engine will reject the operation.
19
19
20
-
However, oftentimes you want to expression more fine-grained validation rules, such as field length, string format, or numeric range, etc. ZModel provides a set of built-in field-level validation attributes, like `@length`, `@email`, etc. for this purpose. See [Input Validation](../reference/zmodel/input-validation.md) for more details.
20
+
However, oftentimes you want to express more fine-grained validation rules, such as field length, string format, or numeric range, etc. ZModel provides a set of built-in field-level validation attributes, like `@length`, `@email`, etc., for this purpose. See [Input Validation](../reference/zmodel/input-validation.md) for more details.
21
21
22
-
To support more complex rules, a powerful `@@validate` model-level attribute is provided to allow expression rules using multiple fields and logical operators. Inside the `@@validate` attribute, functions like `length()`, `isEmail()` etc. are available for building up expressions. See [Input Validation](../reference/zmodel/input-validation.md) for the full list of available functions.
22
+
To support more complex rules, a powerful `@@validate` model-level attribute is provided to allow expression rules using multiple fields and logical operators. Inside the `@@validate` attribute, functions like `length()`, `isEmail()`, etc. are available for building up expressions. See [Input Validation](../reference/zmodel/input-validation.md) for the complete list of available functions.
23
23
24
-
Arguments of mutation operations are validated before sending to the database. When a validation rule is violated, a`InputValidationError` exception is thrown, containing details about the violations.
24
+
Arguments of mutation operations are validated before sending them to the database. When a validation rule is violated, an`InputValidationError` exception is thrown, containing details about the violations.
25
25
26
26
Internally, ZenStack uses [Zod](https://zod.dev/) to perform validation. Most of the attributes and functions are 1:1 mapped to Zod APIs.
Copy file name to clipboardExpand all lines: versioned_docs/version-3.x/orm/access-control/01-write-policies.md
+18-16Lines changed: 18 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,9 +18,9 @@ plugin policy {
18
18
19
19
## Rule Types
20
20
21
-
Policies can be defined as whitelist rule using the `@@allow` attribute or blacklist rule using `@@deny`.
21
+
Policies can be defined as whitelist rules using the `@@allow` attribute or blacklist rules using `@@deny`.
22
22
23
-
Here's a quick example. Don't worry about the details yet. We'll cover the CRUD operations and rule expressions in details later.
23
+
Here's a quick example. Don't worry about the details yet. We'll cover the CRUD operations and rule expressions later.
24
24
25
25
```zmodel
26
26
model User {
@@ -55,8 +55,8 @@ model Post {
55
55
You can write as many policy rules as you want for a model. The order of the rules doesn't matter. ZenStack determines whether a CRUD operation is allowed using the following logic:
56
56
57
57
1. If any `@@deny` rule evaluates to true, it's denied.
58
-
1. If any `@@allow` rule evaluates to true, it's allowed.
59
-
1. Otherwise, it's denied (secure by default).
58
+
2. If any `@@allow` rule evaluates to true, it's allowed.
59
+
3. Otherwise, it's denied (secure by default).
60
60
61
61
## Operation Types
62
62
@@ -76,7 +76,7 @@ Policy rules are expressed in terms of what CRUD operations they govern.
76
76
77
77
-`post-update`
78
78
79
-
A special operation type to expression conditions that should hold **after** an entity is updated. See [Post-Update Policies](./post-update.md) for details.
79
+
A special operation type to express conditions that should hold **after** an entity is updated. See [Post-Update Policies](./post-update.md) for details.
80
80
81
81
-`delete`
82
82
@@ -95,16 +95,18 @@ ZModel supports an intuitive expression language that's very similar to JavaScri
95
95
- Literals like strings, numbers, and booleans
96
96
- Comparisons with `==`, `>`, etc.
97
97
- Logical combinations with `&&`, `||`, etc.
98
-
- References to current model's fields like `published`
98
+
- References to the current model's fields, like `published`
99
99
100
-
They provide the basic building blocks for composing complex policy rules. Functions are a extensibility mechanism that allows encapsulating specific semantics (e.g., getting the current user). Function calls are expressions so they can be combined with other expressions using operators.
100
+
They provide the basic building blocks for composing complex policy rules.
101
+
102
+
Functions are an extensibility mechanism that allows encapsulating specific semantics (e.g., getting the current user). Function calls are expressions, so they can be combined with other expressions using operators.
101
103
102
104
The following sections will cover some of the functions and expression types that are specifically designed for writing policy rules. See [Functions](../../reference/zmodel/07-function.md) and [Expression](../../reference/zmodel/08-expression.md) reference for more details. Refer to the [@zenstackhq/plugin-policy](../../reference/plugins/policy.md) documentation for functions available for writing policies.
103
105
104
106
105
107
## Accessing Current User
106
108
107
-
The most common type of access control rules are those that concern the current user. The built-in `auth()` function is designed to access the current user. `auth()` returns an object, and its type is inferred from the ZModel schema with the following rules:
109
+
The most common type of access control rules concerns the current user. The built-in `auth()` function is designed to access the current user. `auth()` returns an object, and its type is inferred from the ZModel schema with the following rules:
108
110
109
111
1. If there's a `model` or `type` annotated with the `@@auth` attribute, it'll be used as the type of `auth()`.
110
112
2. Otherwise, if there's a `model` or `type` named "User" (case sensitive), it'll be used.
@@ -134,25 +136,25 @@ model Post {
134
136
135
137
So the big question becomes: Where does the value of `auth()` come from?
136
138
137
-
ZenStack isn't an authentication framework, so it doesn't know how to fetch the current login user. You are responsible for providing that piece of information. We'll cover it in the next part where we talk about access control's runtime aspect. For now, just assume `auth()` gives you the **validated** current user info.
139
+
ZenStack isn't an authentication library, so it doesn't know how to fetch the current login user. You are responsible for providing that piece of information. We'll cover it in the next part, where we talk about the runtime aspect of access control. For now, assume `auth()` gives you the **validated** current user info.
138
140
139
-
If you don't provide the current user, `auth()` will return `null`, and you can use it to write rules for anonymous users like:
141
+
If you don't provide the current user, `auth()` will return `null`, and you can use it to write rules for anonymous users, like:
140
142
141
143
```zmodel
142
144
@@deny('all', auth() == null)
143
145
```
144
146
145
147
## Accessing Relations
146
148
147
-
You can achieve a lot by writing policies using model's simple fields, however, real-world access control often involves relations. For example:
149
+
You can achieve a lot by writing policies using a model's simple fields; however, real-world access control often involves relations. For example:
148
150
149
151
> A user can only create posts if his profile is verified, where "Profile" is a relation of "User".
150
152
151
153
ZenStack's policy really shines when it comes to how flexible it is in traversing relations.
152
154
153
155
### To-One Relations
154
156
155
-
Accessing to-one relation is straightforward, simply use dot notation use relation's field, and you can chain as deeply as you need:
157
+
Accessing to-one relation is straightforward, simply use dot notation to use the relation's field, and you can chain as deeply as you need:
156
158
157
159
```zmodel
158
160
model User {
@@ -204,9 +206,9 @@ You can nest collection predicate expressions to build deep to-many relation tra
204
206
205
207
## Special Notes About `create`
206
208
207
-
There're limitations with what relations can be accessed in `create` rules, because such rules are evaluated before the record is created, and at that time, relations are not accessible yet.
209
+
There are limitations on what relations can be accessed in `create` rules, because such rules are evaluated before the record is created. At that time, relations are not accessible yet.
208
210
209
-
As a result, `create` rules can only access "owned" relations - those relations that have foreign keys defined in the model where the rule is defined. For example, the following is allowed:
211
+
As a result, `create` rules can only access "owned" relations - those relations that have foreign keys defined in the model where the rule is defined. During create, if the foreign key fields are set, the relations are accessible. For example, the following is allowed:
210
212
211
213
```zmodel
212
214
model Profile {
@@ -250,7 +252,7 @@ model Profile {
250
252
}
251
253
```
252
254
253
-
You can use the `check()` function to directly delegate the policy check to the related entity:
255
+
You can use the `check()` function to delegate the policy check to the related entity directly:
254
256
255
257
```zmodel
256
258
model User {
@@ -267,7 +269,7 @@ model Profile {
267
269
}
268
270
```
269
271
270
-
You can make it even more concise by omitting the second argument and infer it from the context:
272
+
You can make it even more concise by omitting the second argument and inferring it from the context:
Copy file name to clipboardExpand all lines: versioned_docs/version-3.x/orm/access-control/02-query.md
+15-15Lines changed: 15 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ After defining access control policies in ZModel, it's time to enjoy their benef
6
6
7
7
## Installing Runtime Plugin
8
8
9
-
Similar to the schema side, access control's runtime aspect is encapsulated in the `@zenstackhq/plugin-policy` package too, as a Runtime Plugin (more about this topic [later](../plugins/index.md)). You should install it onto the raw ORM client to get a new client instance with access control enforcement.
9
+
Similar to the schema side, access control's runtime aspect is encapsulated in the `@zenstackhq/plugin-policy` package too, as a Runtime Plugin (more about this topic [later](../plugins/index.md)). You should install it on the raw ORM client to get a new client instance with access control enforcement.
As mentioned in the previous part, you can use the `auth()` function in policy rules to refer to the current authenticated user. At runtime, you should use the `$setAuth()` API to provide such information. ZenStack itself is not an authentication library, so you need to determine how to achieve it based on your authentication mechanism.
28
28
29
-
In a web application, the typical pattern is to inspect the incoming request, extract the user information from it and validate, and then call `$setAuth()` to get an ORM client bound to that user.
29
+
In a web application, the typical pattern is to inspect the incoming request, extract and validate the user information from it, and then call `$setAuth()` to get an ORM client bound to that user.
30
30
31
31
```ts
32
32
import { getSessionUser } from'./auth'; // your auth helper
@@ -49,20 +49,20 @@ Use the `$auth` property to get the user info previously set by `$setAuth()`.
49
49
50
50
## Making Queries
51
51
52
-
Access control policies are effective to both the ORM API and the query-builder API. To understand its behavior, the simplest mental model is to think that rows not satisfying the policies "don't exist".
52
+
Access control policies are effective for both the ORM API and the query-builder API. To understand its behavior, the simplest mental model is to think that rows not satisfying the policies "don't exist".
53
53
54
54
### ORM Queries
55
55
56
-
For the most part, the ORM queries behavior is very intuitive.
56
+
For the most part, the ORM query behavior is very intuitive:
57
57
58
-
Read operations like `findMany`, `findUnique`, `count`, etc., only return/involve rows that meet the "read" policies.
58
+
- Read operations like `findMany`, `findUnique`, `count`, etc., only return/involve rows that meet the "read" policies.
59
59
60
-
Mutation operations that affect multiple rows like `updateMany` and `deleteMany` only impact rows that meet the "update" or "delete" policies respectively.
60
+
- Mutation operations that affect multiple rows, like `updateMany` and `deleteMany`, only impact rows that meet the "update" or "delete" policies respectively.
61
61
62
-
Mutation operations that affect a single, unique row like `update` and `delete` will throw an `NotFoundError` if the target row doesn't meet the "update" or "delete" policies respectively.
62
+
- Mutation operations that affect a single, unique row, like `update` and `delete`, will throw an `NotFoundError` if the target row doesn't meet the "update" or "delete" policies respectively.
63
63
64
64
:::info
65
-
Why `NotFoundError` instead of `RejectedByPolicyError`? Remember the rationale is rows that don't satisfy the policies "don't exist".
65
+
Why `NotFoundError` instead of `RejectedByPolicyError`? Because the rationale is rows that don't satisfy the policies "don't exist".
66
66
:::
67
67
68
68
There are some complications when "read" and "write" policies affect the same query. It's ubiquitous because most mutation APIs involve reading the post-mutation entity to return to the caller. When the mutation succeeds but the post-mutation entity cannot be read, a `RejectedByPolicyError` is thrown, even though the mutation is persisted.
@@ -78,31 +78,31 @@ await db.post.update({
78
78
```
79
79
80
80
:::info
81
-
Why throwing an error instead of returning `null`? Because it'll compromise type-safety. The `create`, `update`, and `delete` APIs don't have a nullable return type.
81
+
Why throw an error instead of returning `null`? Because it'll compromise type-safety. The `create`, `update`, and `delete` APIs don't have a nullable return type.
82
82
:::
83
83
84
84
### Query-Builder Queries
85
85
86
-
The low-level Kysely-query-builder API is also subject to access control enforcement. Its behavior is intuitive:
86
+
The low-level Kyselyquery-builder API is also subject to access control enforcement. Its behavior is intuitive:
- When you call `$qb.insertInto()`, `RejectedByPolicyError` will be thrown if the inserted row doesn't satisfy the "create" policies. Similar for `update` and `delete`.
90
-
- Calling `$qb.update()` and `$qb.delete()` only affects rows that satisfy the "update" and "delete" policies respectively.
90
+
- Calling `$qb.update()` and `$qb.delete()` only affects rows that satisfy the "update" and "delete" policies, respectively.
91
91
- When you join tables, the joined table will be filtered to readable rows only.
92
-
- When you use sub-queries, the sub-query will be filtered to readable rows only.
92
+
- When you use sub-queries, the sub-queries will be filtered to readable rows only.
93
93
94
94
## Limitations
95
95
96
96
Here are some **IMPORTANT LIMITATIONS** about access control enforcement:
97
97
98
-
1. Mutations caused by cascade deletes/updates and database triggers are completely internal to the database, so ZenStack cannot enforce access control on them.
98
+
1. Mutations caused by cascade deletes/updates and database triggers are entirely internal to the database, so ZenStack cannot enforce access control on them.
99
99
2. Raw SQL queries executed via `$executeRaw()` and `$queryRaw()` are not subject to access control enforcement.
100
100
3. Similarly, raw queries made with query-builder API using the `sql` tag are not subject to access control enforcement.
ZenStack v3's ORM is built above Kysely. Regardless you use the ORM API or the query-builder one, queries are eventually transformed into Kysely's SQL AST, and then compiled down to SQL and sent to the database for execution. The access control enforcement is implemented by transforming the AST and injecting proper filters.
108
+
ZenStack v3's ORM is built on top of Kysely. Regardless of whether you use the ORM API or the query-builder one, queries are eventually transformed into Kysely's SQL AST and then compiled down to SQL and sent to the database for execution. The access control enforcement is implemented by transforming the AST and injecting proper filters.
Copy file name to clipboardExpand all lines: versioned_docs/version-3.x/orm/access-control/03-post-update.md
+16-1Lines changed: 16 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,16 @@
1
1
# Post-Update Rules
2
2
3
-
Among the operation types, "update" is a special one because it has a "pre" state and "post" state. The "update" policies we've seen in the previous parts refer to the "pre" state, meaning that if your polices refer to the model's fields, the fields are evaluated to their values before the update happens.
3
+
import StackBlitzGithub from '@site/src/components/StackBlitzGithub';
4
+
5
+
:::info
6
+
7
+
In ZenStack v2, post-update rules were implicitly defined with the "update" operation by using the `future()` function to refer to the post-update values. We found this approach to be unclean and error-prone. V3 made a breaking change to introduce a separate "post-update" operation.
8
+
9
+
:::
10
+
11
+
## Overview
12
+
13
+
Among the CRUD operations, "update" is a special one because it has a "pre" state and "post" state. The "update" policies we've seen in the previous parts refer to the "pre" state, meaning that if your polices refer to the model's fields, the fields are evaluated to their values before the update happens.
4
14
5
15
However, sometimes you want to express conditions that should hold after the update happens. For example, you may want to ensure that after an update, a post's `published` field cannot be set to true unless the current user is the author. Post-update policies are designed for such scenarios.
6
16
@@ -24,3 +34,8 @@ model Post {
24
34
}
25
35
```
26
36
37
+
When post-update policies are violated, a `RejectedByPolicyError` is thrown.
0 commit comments