@dudousxd/nestjs-authz
在NestJS的第一党之上的外加 @nestjs/authorization。 。 。 .
政策, @Can()全球逐一拒绝保护 AuthorizationService 来自
官方软件包:先读其文件。 此套件添加了它没有的:
- 将非洲区域局视为官方政策数字 :
RolePolicy财务报告和审定财务报表PermissionPolicy(纸牌, 由TypeORM / MikroORM / 支持的租户范围角色、直接权限、角色来源 Prisma / Drizzle 适配器,每个请求一次取取. - 官员的助手
@Can()数字 :byId(Post), (中文(简体) ),roles(...), (中文(简体) ),permission(...), (中文(简体) ),ability(...), (中文(简体) ),staticCanArgs(...); 糖类@Roles(...)编号 :@Permission(...)。 。 。 . - 一个全球超级管理员 还有一个 外部政策引擎 (Cerbos, OPA,...):扩展
AuthzPolicy。 。 。 . - A 类
Gate服务:字符串能力,批次(allowsMany),临时大门 (单位:千美元)define),角色,查询范围(scope+适配器'applyScope) (中文(简体) ). - 前端和工具: Inertia道具, 一个已键入
can()从代码源、 客户端/ 反应 帮助者,测试帮助者,和一个允许 + 拒绝 望远镜的决定事件.
见 DESIGN.md 设计与权衡.
安装
pnpm add @nestjs/authorization @dudousxd/nestjs-authz
需要NestJS 12和节点24+. 两个可选的同伴: @nestjs/authentication 给 Gate
其环境用户( e)AuthenticationContext.user(三); @dudousxd/nestjs-context 给它一个也
(在认证范围之外),加上每个请求权限缓存和
租户范围的角色.
@Module({
imports: [
AuthorizationModule.forRoot(), // official: guard, @Can, AuthorizationService
AuthzModule.forRoot({ superAdmin: (user) => user?.isRoot === true }),
],
providers: [PostPolicy],
})
export class AppModule {}
政策(官方)+ @PolicyFor
import { Can, Policy } from '@nestjs/authorization';
import { AuthzPolicy, PolicyFor, authorizedResource, byId } from '@dudousxd/nestjs-authz';
@Policy()
@PolicyFor(Post) // optional: links Post → PostPolicy for the extras
export class PostPolicy extends AuthzPolicy { // optional: honors `superAdmin`
update(user: User | null, post: Post) {
return post.authorId === user?.id;
}
}
@Controller('posts')
export class PostController {
@Patch(':id')
@Can(PostPolicy, 'update', byId(Post)) // byId loads the Post named by :id
update(@Req() req: Request) {
const post = authorizedResource(req, Post); // the record byId loaded, no second query
}
}
byId 使用该 ResourceResolver 授予 AuthzModule.forRoot({ resourceResolver })
(默认:a) { id } 来自路由参数的对象; idParam 重命名为它, byId(Post, { param })
每条路线). 政策运行前的403个记录是找不到的.
@PolicyFor(Post) 让额外人员从资源到政策:
gate.allows('update', post), (中文(简体) ), gate.scope(Post),客户端能力映射. 官方API( API)
从不需要它 @PolicyFor 应用官员 @Policy() 自己来.
角色和权限
@Roles('admin', 'editor') // any of
@Can(RolePolicy, 'hasAll', roles('admin', 'editor')) // all of
@Permission('posts.publish') // any of; posts.* grants it
@Can(PermissionPolicy, 'hasAll', permission('posts.publish', 'posts.delete'))
角色来自 user.roles 编号 : user.role (或 resolveRoles)和来自非洲区域局适配器的作用
存储; 适配器的权限 :
AuthzRbacModule.forRootAsync({
inject: [DataSource],
useFactory: (ds: DataSource) => ({ store: new TypeOrmAuthzStore(ds) }),
});
在一个请求内(一个巢js-context store),用户的权限和适配器的作用是
一次,不管多少次检查 官方警卫的 RolePolicy 编号 : PermissionPolicy
和处理者 Gate 呼叫共享获取。 该条目由用户名行键入
(类别或 type附加时 id; 普通对象没有 type 以身份键入。 补助金
从下一次请求中适用中期请求。 适应器集 exhaustivePermissions,所以一个
允许在不进行其他查询的情况下解答缺少的集.
roles(), (中文(简体) ), permission(), (中文(简体) ), ability() 财务报告和审定财务报表 staticCanArgs() 参数的返回解析器
不取决于电话。 {\fn华文楷体\fs20\bord1\shad1\1cHE0E0E0\2cH000000}被活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活 Symbol.for('@dudousxd/static-can-args')
属性,因此Aviary代理等工具可以无需请求而评价某项要求.
超级管理员
官警只负责每个保单 before()扩展 AuthzPolicy (或打电话)
superAdminBefore(user, ability, resource) 从你的 before())和 superAdmin 钩子
赋予这项政策的一切能力。 钩子接收资源( 能力的第一个)
因此它可以将绕行范围扩大. RolePolicy 财务报告和审定财务报表 PermissionPolicy 扩展它.
这个 Gate
constructor(private readonly gate: Gate) {}
await this.gate.forUser(user).authorize(PostPolicy, 'update', post); // = AuthorizationService.authorize
await this.gate.forUser(user).allows('update', post); // the policy linked to Post
await this.gate.forUser(user).allows('posts.publish'); // a persisted permission
this.gate.define('access-admin', (user) => user.isStaff);
// on a route: @Can(GatePolicy, 'allows', ability('access-admin'))
const results = await this.gate.forUser(user).allowsMany([
{ ability: 'update', resource: post },
{ policy: ReportPolicy, ability: 'export', args: ['csv'] },
]); // one permission fetch
const where = await this.gate.forUser(user).scope(Post); // from PostPolicy.scope(user)
字符串能力询问 superAdmin,然后是外部决定点(以下),然后是政策
链接到资源,然后是持续的权限,然后是临时的门. 政策是
最后一个词: 持有其能力之一的许可(update没有
绕过; 只 superAdmin 已经 权限授予命名的能力没有定义策略
(单位:千美元)posts.publish) (中文(简体) ). 也一样 scope(): 仅当一个实体没有许可时, 才允许其范围
政策方法可以.
政策决定始终是公务部门的. authorize() 丢弃官方
AuthorizationError,正式模块变为401/403.
无 forUser,用户是:
@nestjs/authentication是因为AuthenticationContext.user,当该可选对等点是 已安装并导入其模块(通过 Nest 找到)ModuleRef),其范围是活跃的: 呼叫它处理,或AuthenticationContext.run()答案是最后的:null是一个 宾客,而"巢"字不问. 没错AsyncLocalStorage- 支持,正确处理者 和他们所等待的;- 不属于任何认证范围(排队工人、克龙工作或没有包),
coryjs-context的用户参考文献,水分
resolveUser(按要求)配置时; - 来宾(
null) (中文(简体) ).
决定
每个 Gate 检查,并公布官方警卫执行的每一项要求,允许或
否认,在 aviary:authz:decision 诊断渠道(authzDecisionChannel) (中文(简体) ). 官员
软件包只发布拒绝, 在 nestjs:authorization:denied。 。 。 .
从0.8移走
0.8及更早时拥有自己的发动机. 0.9 运行 @nestjs/authorization和旧的
装饰器已去:将每个都移到正文.
| 0.8 (简体中文) | 0.9 (中文(简体) ) |
|---|---|
@Policy(Post) | @Policy() 从 @nestjs/authorization + 键 @PolicyFor(Post) (或 @PolicyFor(Post) 页:1 |
@Can('update', Post) | @Can(PostPolicy, 'update', byId(Post)), (中文(简体) ), @Can 从 @nestjs/authorization |
@Can('create', Post, { classLevel: true }) | @Can(PostPolicy, 'create') |
@Can('access-admin') | @Can(GatePolicy, 'allows', ability('access-admin')) |
@Roles('admin') + 键 RolesGuard | @Roles('admin')由官方警卫执行 |
CanGuard, (中文(简体) ), RolesGuard, (中文(简体) ), PolicyRegistry | 删除: 官员 AuthorizationGuard· ; PolicyIndex |
AuthzModule.forRoot({ policies }) | 将政策登记为提供者(或 AuthorizationModule.forRoot({ policies })(中文(简体) ) |
superAdmin 在所有地方应用 | 应用 AuthzPolicy 子类、内置政策和 Gate |
after 钩子, { allowed, message } 结果 | 删除: 仅一个字节 true 允许 |
| 当前用户在路由中的 courjs- context | 官员 getUser (默认) request.user(中文(简体) ) |
gate.allows() 来自 nockjs 文本的用户 | AuthenticationContext.user b. 其范围null 外面有客人,外有小鸟 |
| 以政策能力命名的持续许可 | 政策决定(只有 superAdmin 绕过它,在检查和在 scope() |
DecisionProvider 编号 : superAdmin 将客人视为 undefined | null |
(单位:千美元) DecisionProvider 拒绝 message (单位:千美元){ allowed: false, message }403的讯息 | 已删除: 403 是官方 { statusCode: 403, message: 'Forbidden' }。 。 。 . @nestjs/authorization 没有否认的理由(政策回报) true 或否认,或否认,或否认,或否认。 为了说明原因,自己扔 ForbiddenException(reason) 从能力。 一个路线,然后得到你的身体,但是没有 denied 事件被释放,并且 AuthorizationService.can() 抛出而不是返回 false。 。 。 . |
DecisionProvider 每张支票都是门开的 | 请求的 AuthzPolicy.before() 路线和途径 Gate 字符串能力 |
import … from '@dudousxd/nestjs-authz-inertia/client' | import … from '@dudousxd/nestjs-authz-client' (子路径被移除) |
官方警卫带来的行为:
- 默认拒绝. 处理器没有
@Can()拒绝; 添加@Can.Anyone()(或@Public()从@nestjs/authentication任何人都可以称之为它. - 类与方法要求相加. 一级
@Roles不再让位于 方法学. - 来宾以
null。 。 。 . 输入用户User | null;嘉宾否认为401.
一次移动一个控制器 : unannotated: 'allow'
官方的警卫否认任何路线 @Can():该应用的公用路线,和
每个控制器另装一个库。 0.8让他们通过。 开始授权
一个操作中的大型应用程序很少可能,所以这个软件包有迁移模式(官方守护)
没有这样的选择 :
-
关闭官方全球警戒,并切换迁移模式
imports: [ AuthenticationModule.forRoot({ ... }), // or your Passport guard, first AuthorizationModule.forRoot({ globalGuard: false }), AuthzModule.forRoot({ unannotated: 'allow' }), // forRootAsync: next to useFactory ]AuthzModule在全球注册AuthzGuard。带有官方路线@Can()要求(类别或方法,@Roles, (中文(简体) ),@Permission) 已移交 致官员AuthorizationGuard同为:getUser同样的政策 同样的401个客人 和用户的403, 相同denied活动。 一个没有要求的路线被允许通过. 应用程序 如果官方全球卫士还在.启动日志在此模式中有一个预期错误 .
@nestjs/authorization检查 启动一个守卫执行每个@Can(),它只承认自己的AuthorizationGuard它没有看到AuthzGuard因此,它记录:ERROR [Authorization] @Can() is not enforced on PostController.update, … : AuthorizationModule has globalGuard: false, and these handlers have no @UseGuards(AuthorizationGuard).这是安全的:
AuthzGuard执行这些操作员AuthzModule将行记录在后面 它命名他们。 错误在第四步消失. -
放下0.8个后卫,移走老式装饰. 删除
RolesGuard编号 :CanGuard从APP_GUARD财务报告和审定财务报表@UseGuards(),并重写每个字符串@Can编号 :@Policy(Post)与表格 以上(单位:千美元)@Can('page.documents')→@Can(GatePolicy, 'allows', ability('page.documents'))) (中文(简体) ).@Roles('admin')财务报告和审定财务报表@Permission(...)不变。 所有这一切都由AuthzGuard。 。 。 . -
注释开放路线,每次一个控制器 :
@Can.Anyone()任何人都可以打电话 这,@Public()(从)@nestjs/authentication在不需要签入的情况下,a@Can(...)随所出处. -
默认切换为拒绝 当每条路由声明某事时: 删除
unannotated(或设置)'deny'和globalGuard: false官方警卫又全球化了 被遗忘了@Can()是一个403而不是开放的路线.
第3步中滑入的路线在这个模式中是开放的,所以只保留为迁移.
外部政策引擎(Cerbos, OPA,.)
登记a DecisionProvider 下级 DECISION_PROVIDER 将决定交给外部政策
决策点。 与仅提供赠款的非洲区域局接头不同,它能够 允许或拒绝 资源意识检查.
正在返回 undefined 弃权者。 供应商也可以分批 gate.allowsMany,它可以绘制
引擎的查询计划进入 gate.scope()它运行在之后 superAdmin 钩:
- 在路线上,在
before()政策范围AuthzPolicy,具有能力和 其第一个论点byId(Post)'记录'.RolePolicy财务报告和审定财务报表PermissionPolicy不要问它: 他们的能力 指定角色和权限,而不是资源。 打电话decisionProviderBefore()从你自己before()当某项政策不能延长时AuthzPolicy· ; - 输入
Gate,在策略,权限和一个字符串能力的特设门之前.allowsMany询问decideMany一次为批次.
一个客人到达它作为 null箱内一个Cerbos适配器船作为无额外
依赖; 带来自己的 @cerbos/http 或者说 @cerbos/grpc 客户端 :
import { DECISION_PROVIDER } from '@dudousxd/nestjs-authz';
import { CerbosDecisionProvider } from '@dudousxd/nestjs-authz/cerbos';
import { HTTP } from '@cerbos/http';
providers: [{
provide: DECISION_PROVIDER,
useValue: new CerbosDecisionProvider({
client: new HTTP(process.env.CERBOS_URL!), // or (user) => per-tenant client | undefined
principal: (u) => u && { id: u.id, roles: u.roles, attr: { tenantId: u.tenantId } },
resource: (ability, r) => (r instanceof Post ? { kind: 'app:post', id: r.id, attr: { ownerId: r.ownerId } } : undefined),
scope: { resource: (entity) => (entity === Post ? { kind: 'app:post' } : undefined) },
}),
}]
适配器失败 关闭 默认。 无法获取的 PDP 否认和不支持的查询计划
瞄准到无行。 设定 onFailure: 'abstain' 转而回到自己的政策.
许可证
麻省理工学院