mcpField
Z
zenstack-access-control

zenstack-access-control

github.com/zenstackhq安全3463 次安装1 天前更新
代码仓库

关于 zenstack-access-control

面向 ZenStack 用户的访问控制技能,说明如何在 ZModel 中编写策略,并通过运行时插件在 ORM 层执行。涵盖用户身份绑定、字段策略及更新后检查。

主要功能

  1. 01在 ZModel 中声明允许与拒绝策略
  2. 02按请求绑定当前用户
  3. 03定义字段级读写策略
  4. 04检查更新后的数据状态
  5. 05使用关系条件表达式

适用场景

  1. 01限制用户只能访问自己的记录
  2. 02隐藏匿名用户不可读的字段
  3. 03阻止不符合策略的记录更新

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zenstackhq/skills --skill zenstack-access-control

安装后在 Claude Code 中输入/zenstack-access-control确认已加载。

详细介绍

ZenStack V3 — 访问控制

ZenStack 在 ORM 层强制执行 ZModel 中声明的访问策略。策略与模型放在一起(参见 zenstack-schema-modeling);只有通过运行时插件启用后才会执行。有关客户端/查询基础知识以及数据验证,请参见 zenstack-querying。

编辑策略后,运行 zen generate 重新生成客户端(它也会验证架构,包括策略表达式)。如果只想验证而不重新生成,请运行 zen check。

运行时设置(策略生效的必要条件)

在(1)于架构中声明插件并(2)在客户端上安装插件之前,策略不会生效。

架构:

plugin policy {
    provider = '@zenstackhq/plugin-policy'
}

安装软件包:npm install @zenstackhq/plugin-policy。

客户端:

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 enforced

ZenStackClient 实例是不可变的——$use() 和 $setAuth() 会返回新的(开销很低、浅克隆的)实例,且绝不会修改原实例。使用 db 查询可绕过策略(例如受信任的服务端代码),面向用户的访问则使用受策略强制执行的客户端。

当前用户 — auth() 和 $setAuth()

按请求绑定经过身份验证的用户;策略中的 auth() 会解析为该用户。

const userDb = authDb.$setAuth(user);   // user-bound client
await userDb.post.findMany();           // policies evaluated against `user`
  • 不调用 $setAuth() → 匿名模式 → auth() 为 null。$setAuth(undefined) 会强制进入匿名模式。
  • 通过 client.$auth 读取已绑定的用户。

定义 auth() 的返回值,优先级依次为:带有 @@auth 注解的 model/type,否则为字面名称为 User 的模型,否则编译错误。使用 type 可将身份验证数据结构与表解耦:

type Auth {
    id   Int
    role String
    @@auth
}

策略规则 — @@allow / @@deny

@@allow(operation, condition)
@@deny(operation, condition)

操作(用逗号分隔以组合):create、read、update、delete、post-update 和 all(= create+read+update+delete,不包括 post-update)。

求值(默认安全):任何匹配的 @@deny → 拒绝;否则,任何匹配的 @@allow → 允许;否则拒绝。read/update/delete 会静默地过滤掉不符合条件的行,而非报错;create 则在插入前检查。

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
}

策略表达式

  • 运算符:== != > >= < <=、&& || !,以及字段引用。

  • 一对一关系:使用点号访问 — auth().city == profile.address.city。

  • 一对多关系:集合谓词

    • relation?[cond] — 至少有一个匹配
    • relation![cond] — 全部匹配
    • relation^[cond] — 没有匹配
    @@deny('delete', posts?[published == true])   // can't delete a user with published posts
    
  • 使用 this 跳出集合作用域:comments?[author.verified && this.published]。

  • create 规则只能引用归属关系(即模型持有其外键的关系)——创建时其他关系尚不存在。

  • check(relation [, op]) 将检查委托给一对一关系的策略,避免重复编写:@@allow('read', check(user)) / @@allow('update', check(user, 'update'))。

  • 可在表达式中使用的辅助函数:contains、startsWith、endsWith(均可接受可选的忽略大小写标志)、has、hasSome、hasEvery、isEmpty、currentModel()、currentOperation()。

字段级策略(v3.2.0+,预览版)

字段上的单个 @ @allow / @deny,仅支持 read 和 update 操作。

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
}
  • 读取策略未通过时,字段会被置为 null(返回值为 null,但仍可用于 SQL 计算)——无法区分真实的 null 和被策略拒绝的值。
  • 更新策略未通过时,整个更新会被拒绝(ORMError,原因是 REJECTED_BY_POLICY)。

更新后规则

post-update 表示更新之后必须满足的条件;字段引用指向新值,而 before() 读取旧值。(这取代了 V2 的 future()。)

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-update 规则,更新只需通过 update 检查。如果模型有任意 post-update 规则,则至少有一个 @@allow('post-update', ...) 必须通过。

执行顺序和错误原因

每次变更都会在访问数据库之前检查策略(在完成任何输入验证之后 —— 请参阅 zenstack-querying)。与策略相关的 ORMError.reason 值:REJECTED_BY_POLICY(策略)和 NOT_FOUND(不存在,或被读取策略过滤掉)。REJECTED_BY_POLICY 还包含 rejectedByPolicyReason:NO_ACCESS、CANNOT_READ_BACK(允许变更,但无法读取回结果)或 OTHER。

限制

  • ZenStack 不会对数据库级联删除/更新和触发器进行策略检查。
  • 原始 SQL($queryRaw/$executeRaw)和查询构建器访问会绕过策略。仅当策略插件已安装时,才能通过 new PolicyPlugin({ dangerouslyAllowRawSql: true }) 允许原始 SQL (v3.5.0+)。

参考文档

此主题的完整 ZenStack 文档位于 references/ 下:

评论

支持 Markdown