@vafast/permission
v0.1.3
Published
Declarative permission middleware for Vafast - route-level RBAC with pluggable resolvers
Maintainers
Readme
@vafast/permission
声明式权限中间件:路由写 permission: true(或显式 key),业务封装一次 createPermissionMiddleware,挂在认证之后做 RBAC。
设计对齐 @vafast/webhook:路由扩展 + 路径推导 key + 目录查询。与具体「组织 / 租户」模型解耦。
完整文档:Permission
先搞清几个概念
Permission key
点分字符串,建议 {domain}.{module}.{action}。默认从路径推导(同 webhook eventKey):
| 路径 | pathPrefix | 自动 key |
|------|------------|----------|
| /billing/points/adjust | (无) | billing.points.adjust |
| /restfulApi/auth/signIn | /restfulApi | auth.signIn |
Grant 侧可带通配:*、billing.*、billing.points.*。
角色 → grants → 比对
认证(authWithApp)
→ getRole → 角色表 grants
→ getExtraGrants(可选,平台/应用权限)并集
→ 与路由 permission 匹配- 无角色且无额外 grants → 401
- 有主体但 grants 不够 → 403
复合场景(组织 admin 或 平台权限)用 getExtraGrants;条件字段校验(如仅 all_apps)放 handler,复用同一套 grants 判断。
挂载位置(和 webhook 不同)
| | Webhook | Permission |
|--|---------|------------|
| 时机 | next() 之后发事件 | handler 之前拦截 |
| 挂法 | 可以 server.use | 必须在认证后:middleware: [authWithApp, orgPermission] |
不要 server.use(orgPermission):全局中间件早于路由 auth,读不到 userInfo / app。
安装
npm install @vafast/permission快速开始(推荐写法)
import { Server, defineRoute, defineRoutes, serve } from 'vafast'
import { authWithApp } from '@vafast/auth-middleware'
import {
createPermissionMiddleware,
defineRoles,
cacheKeyFromUserAndApp,
} from '@vafast/permission'
export const orgPermission = createPermissionMiddleware({
pathPrefix: '/billingRestfulApi', // 有统一 API 前缀才写,对齐 webhook
roles: defineRoles({
owner: ['*'],
admin: ['billing.*', 'users.*'],
finance: ['billing.points.*'],
member: ['billing.points.read'],
}),
async getRole(req) {
const locals = (req as {
__locals?: { userInfo?: { id: string }; app?: { id: string } }
}).__locals
if (!locals?.userInfo?.id || !locals?.app?.id) return null
// 业务里调 auth / 用户中心
return await getOrgRole(locals.userInfo.id, locals.app.id).then((r) => r.role)
},
// 默认不缓存(角色变更立即生效)。需要时可开:
// cache: { cacheKey: cacheKeyFromUserAndApp, ttlMs: 60_000 },
})
const routes = defineRoutes([
defineRoute({
path: '/billingRestfulApi',
middleware: [authWithApp, orgPermission],
children: [
defineRoute({
method: 'POST',
path: '/billing/points/adjust',
permission: true, // → billing.points.adjust
handler: () => ({ ok: true }),
}),
defineRoute({
method: 'GET',
path: '/billing/points/read',
// 未声明 permission → 只认证,不校验组织权限
handler: () => ({ balance: 100 }),
}),
],
}),
])
const server = new Server(routes)
serve({ fetch: server.fetch, port: 3000 })用法
路由字段
permission: true // 推荐:路径推导
permission: {} // 同 true
permission: 'billing.points.adjust' // 显式单 key
permission: { key: 'billing.points.adjust' }
permission: { anyOf: ['a', 'b'] } // 少见:满足任一
permission: { allOf: ['a', 'b'] } // 少见:必须全有可选缓存
createPermissionMiddleware({
roles,
getRole,
cache: {
cacheKey: cacheKeyFromUserAndApp, // userId:appId
ttlMs: 60_000,
},
})不传 cache = 不缓存。
管理端级联目录
import { getPermissionCatalog, buildPermissionTree } from '@vafast/permission'
const catalog = getPermissionCatalog('/billingRestfulApi')
const tree = buildPermissionTree([
'billing.points.adjust',
'billing.points.read',
'users.invite',
])API
| 导出 | 说明 |
|------|------|
| createPermissionMiddleware({ roles, getRole, pathPrefix?, cache? }) | 推荐:业务封装入口 |
| cacheKeyFromUserAndApp | 常用 cacheKey |
| defineRoles / resolveRoleGrants / mergeGrants | 角色权限包 |
| permission / requirePermission | 底层 API(多数情况不必直接用) |
| createRoleResolver / createLocalsResolver / createStaticResolver | 底层 Resolver |
| createCachedResolver | 底层缓存(优先用 cache: 选项) |
| matchPermission / checkRequirement / generatePermissionKey | 纯函数 |
| getPermissionCatalog / buildPermissionTree | 目录 / 级联树 |
| PermissionRouteExtensions | withContext 类型扩展 |
注意事项
- 授权 ≠ 认证:先挂
authWithApp(或等价),再挂orgPermission。 - 推荐
permission: true;改路径会同步改 key(与 webhook 一样,正常)。 - 通配只支持 grant 侧的
*/prefix.*。 - 不绑定任何云厂商或 Ones 组织模型;
getRole由业务实现。
相关链接
License
MIT
