zenstack-access-control
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZenStack V3 — Access Control
ZenStack V3——访问控制
ZenStack enforces access policies declared in ZModel at the ORM layer. Policies live next to your
models (see ); enforcement is opt-in via a runtime plugin. For client/query
basics — and for data validation — see .
zenstack-schema-modelingzenstack-queryingAfter editing policies, run to regenerate the client (it also validates the schema,
policy expressions included). If you only want to validate without regenerating, run .
zen generatezen checkZenStack 在 ORM 层强制执行 ZModel 中声明的访问策略。策略与模型放在一起(参见 );可通过运行时插件选择是否启用策略强制执行。有关客户端和查询基础用法以及数据验证,请参阅 。
zenstack-schema-modelingzenstack-querying修改策略后,运行 重新生成客户端(该命令还会验证 schema,包括策略表达式)。如果只想验证而不重新生成,请运行 。
zen generatezen checkRuntime setup (required for policies to apply)
运行时配置(应用策略时必需)
Policies do nothing until you (1) declare the plugin in the schema and (2) install it on the client.
Schema:
zmodel
plugin policy {
provider = '@zenstackhq/plugin-policy'
}Install the package: .
npm install @zenstackhq/plugin-policyClient:
ts
import { ZenStackClient } from '@zenstackhq/orm';
import { PolicyPlugin } from '@zenstackhq/plugin-policy';
const db = new ZenStackClient(schema, { dialect }); // raw, unprotected
export const authDb = db.$use(new PolicyPlugin()); // access control enforcedZenStackClient$use()$setAuth()db在完成以下两项操作之前,策略不会生效:(1) 在 schema 中声明插件;(2) 在客户端上安装插件。
Schema:
zmodel
plugin policy {
provider = '@zenstackhq/plugin-policy'
}安装软件包:。
npm install @zenstackhq/plugin-policy客户端:
ts
import { ZenStackClient } from '@zenstackhq/orm';
import { PolicyPlugin } from '@zenstackhq/plugin-policy';
const db = new ZenStackClient(schema, { dialect }); // raw, unprotected
export const authDb = db.$use(new PolicyPlugin()); // access control enforcedZenStackClient$use()$setAuth()dbThe current user — auth()
and $setAuth()
auth()$setAuth()当前用户——auth()
和 $setAuth()
auth()$setAuth()Bind the authenticated user per request; in policies resolves to it.
auth()ts
const userDb = authDb.$setAuth(user); // user-bound client
await userDb.post.findMany(); // policies evaluated against `user`- No → anonymous mode →
$setAuth()isauth().nullforces anonymous.$setAuth(undefined) - Read the bound user back via .
client.$auth
Define what returns, by priority: a / annotated , otherwise a
model literally named , otherwise a compile error. Using a decouples the auth shape
from your tables:
auth()modeltype@@authUsertypezmodel
type Auth {
id Int
role String
@@auth
}按请求绑定已认证的用户;策略中的 将解析为该用户。
auth()ts
const userDb = authDb.$setAuth(user); // user-bound client
await userDb.post.findMany(); // policies evaluated against `user`- 未调用 → 匿名模式 →
$setAuth()为auth()。null会强制使用匿名模式。$setAuth(undefined) - 可通过 读取当前绑定的用户。
client.$auth
定义 的返回类型,优先级如下:带有 注解的 /;否则为名称恰好为 的模型;两者均不存在则产生编译错误。使用 可以将身份验证的数据结构与表解耦:
auth()@@authmodeltypeUsertypezmodel
type Auth {
id Int
role String
@@auth
}Policy rules — @@allow
/ @@deny
@@allow@@deny策略规则——@@allow
/ @@deny
@@allow@@deny@@allow(operation, condition)
@@deny(operation, condition)Operations (comma-separate to combine): , , , , ,
and (= create+read+update+delete, not post-update).
createreadupdatedeletepost-updateallEvaluation (secure by default): any matching → denied; else any matching →
allowed; else denied. // silently filter out rows that fail, rather than
erroring; is checked before insert.
@@deny@@allowreadupdatedeletecreatezmodel
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
@@allow('create,read', true) // anyone can sign up; profiles public
@@allow('all', auth().id == id) // the user has full access to themselves
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
@@deny('all', auth() == null) // no anonymous access
@@allow('read', published) // published posts readable by anyone
@@allow('all', auth().id == authorId) // author has full access
}@@allow(operation, condition)
@@deny(operation, condition)操作(可使用逗号分隔以组合操作):、、、、 和 (= create+read+update+delete,不包含 post-update)。
createreadupdatedeletepost-updateall求值方式(默认安全):只要匹配任意 就会被拒绝;否则,只要匹配任意 就会允许;两者均未匹配则拒绝。对于 //,未通过检查的行会被静默过滤掉,而不是抛出错误; 会在插入前进行检查。
@@deny@@allowreadupdatedeletecreatezmodel
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
@@allow('create,read', true) // anyone can sign up; profiles public
@@allow('all', auth().id == id) // the user has full access to themselves
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
@@deny('all', auth() == null) // no anonymous access
@@allow('read', published) // published posts readable by anyone
@@allow('all', auth().id == authorId) // author has full access
}Policy expressions
策略表达式
-
Operators:
==!=>>=<,<=&&||, and field references.! -
To-one relations: dot access —.
auth().city == profile.address.city -
To-many relations: collection predicates
- — some match
relation?[cond] - — all match
relation![cond] - — none match
relation^[cond]
zmodel@@deny('delete', posts?[published == true]) // can't delete a user with published posts -
Useto escape the collection scope:
this.comments?[author.verified && this.published] -
rules can only reference owned relations (those whose FK the model holds) — other relations don't exist yet at creation time.
create -
delegates the check to a to-one relation's policies, avoiding duplication:
check(relation [, op])/@@allow('read', check(user)).@@allow('update', check(user, 'update')) -
Helper functions usable in expressions:,
contains,startsWith(each takes an optional case-insensitive flag),endsWith,has,hasSome,hasEvery,isEmpty,currentModel().currentOperation()
-
运算符:
==!=>>=<、<=&&||,以及字段引用。! -
To-one 关系:使用点号访问——。
auth().city == profile.address.city -
To-many 关系:使用集合谓词
- ——部分匹配
relation?[cond] - ——全部匹配
relation![cond] - ——无一项匹配
relation^[cond]
zmodel@@deny('delete', posts?[published == true]) // can't delete a user with published posts -
使用退出集合作用域:
this。comments?[author.verified && this.published] -
规则只能引用所属关系(即模型持有其 FK 的关系)——其他关系在创建时还不存在。
create -
将检查委托给 to-one 关系的策略,从而避免重复:
check(relation [, op])/@@allow('read', check(user))。@@allow('update', check(user, 'update')) -
表达式中可使用的辅助函数:、
contains、startsWith(每个函数均可选择是否忽略大小写)、endsWith、has、hasSome、hasEvery、isEmpty、currentModel()。currentOperation()
Field-level policies (v3.2.0+, preview)
字段级策略(v3.2.0+,预览功能)
Single- / on a field, operations and only.
@@allow@denyreadupdatezmodel
model User {
id Int @id
email String @allow('update', auth() == this) // only the owner can change it
name String @deny('read', auth() == null) // hidden from anonymous readers
}- Failing a read policy nullifies the field (returned as , still usable in SQL computations) — you can't distinguish a real
nullfrom a denied one.null - Failing an update policy rejects the whole update (, reason
ORMError).REJECTED_BY_POLICY
在字段上使用单 的 / ,仅支持 和 操作。
@@allow@denyreadupdatezmodel
model User {
id Int @id
email String @allow('update', auth() == this) // only the owner can change it
name String @deny('read', auth() == null) // hidden from anonymous readers
}- read 策略检查失败会将字段置为空值(返回 ,但仍可用于 SQL 计算)——无法区分真实的
null与因策略拒绝而返回的空值。null - update 策略检查失败会拒绝整个更新(,原因为
ORMError)。REJECTED_BY_POLICY
Post-update rules
Post-update 规则
post-updatebefore()future()zmodel
model Post {
published Boolean @default(false)
authorId Int
@@deny('post-update', published == true && auth().id != authorId) // only author can publish
@@deny('post-update', before().authorId != authorId) // authorId is immutable
}If a model has no rule, an update only needs to pass . If it has any, at least
one must pass.
post-updateupdate@@allow('post-update', ...)post-updatebefore()future()zmodel
model Post {
published Boolean @default(false)
authorId Int
@@deny('post-update', published == true && auth().id != authorId) // only author can publish
@@deny('post-update', before().authorId != authorId) // authorId is immutable
}如果模型没有 规则,更新只需通过 检查。如果模型包含任意 规则,则至少需要有一个 通过。
post-updateupdatepost-update@@allow('post-update', ...)Execution order & error reasons
执行顺序与错误原因
Per mutation, policies are checked before the database is touched (after any input validation —
see ). Policy-related values: (policy) and
(missing or filtered out by a read policy). further carries
: , (mutation allowed but result not readable
back), or .
zenstack-queryingORMError.reasonREJECTED_BY_POLICYNOT_FOUNDREJECTED_BY_POLICYrejectedByPolicyReasonNO_ACCESSCANNOT_READ_BACKOTHER每次 mutation 都会在访问数据库之前检查策略(在任何输入验证之后执行——参见 )。与策略相关的 值包括:(策略拒绝)和 (对象不存在或已被 read 策略过滤掉)。 还会携带 :、(允许执行 mutation,但无法读回结果)或 。
zenstack-queryingORMError.reasonREJECTED_BY_POLICYNOT_FOUNDREJECTED_BY_POLICYrejectedByPolicyReasonNO_ACCESSCANNOT_READ_BACKOTHERLimitations
限制
- Database-level cascade deletes/updates and triggers are not policed by ZenStack.
- Raw SQL (/
$queryRaw) and query-builder access bypass policies. Allow raw SQL while the policy plugin is installed only via$executeRaw(v3.5.0+).new PolicyPlugin({ dangerouslyAllowRawSql: true })
- 数据库级级联删除/更新和触发器不会受到 ZenStack 策略控制。
- 原始 SQL(/
$queryRaw)和 query builder 访问会绕过策略。在安装 policy 插件期间,只有通过$executeRaw才能允许原始 SQL(v3.5.0+)。new PolicyPlugin({ dangerouslyAllowRawSql: true })
Reference docs
参考文档
Full ZenStack documentation for this topic is bundled under :
references/- access-control-overview.md — how access control works
- write-policies.md — /
@@allow,@@deny, expressionsauth() - enforcing-policies.md — policy plugin + runtime
$setAuth - post-update.md — rules and
post-updatebefore() - field-level.md — field-level read/update policies
- zmodel-expression-reference.md — policy expression reference
- policy-plugin-reference.md — policy plugin options
有关此主题的完整 ZenStack 文档已打包在 中:
references/- access-control-overview.md——访问控制的工作原理
- write-policies.md——/
@@allow、@@deny、表达式auth() - enforcing-policies.md——policy 插件和 运行时
$setAuth - post-update.md——规则和
post-updatebefore() - field-level.md——字段级 read/update 策略
- zmodel-expression-reference.md——策略表达式参考
- policy-plugin-reference.md——policy 插件选项