@lark-apaas/nestjs-authzpaas 0.1.0-alpha.11 → 0.1.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,20 +18,14 @@
18
18
  本模块采用以下权限模型:
19
19
 
20
20
  - **角色 (Role)**: 用户所属的角色,如 `admin`、`editor`、`viewer`
21
- - **权限 (Permission)**: 对特定资源的操作权限,由 `actions` + `subject` 组成
22
- - `actions`: 操作类型,如 `create`、`read`、`update`、`delete`、`manage`
23
- - `subject`: 资源类型,如 `User`、`Task`、`Article`
24
21
 
25
22
  ### 核心组件
26
23
 
27
24
  | 组件 | 类型 | 说明 |
28
25
  |------|------|------|
29
26
  | `AuthZPaasGuard` | 全局守卫 | 自动拦截所有请求,执行鉴权检查 |
30
- | `PermissionService` | 服务 | 提供权限获取、检查、缓存管理等功能 |
31
27
  | `RolesMiddleware` | 中间件 | 自动获取并注入用户角色信息到请求上下文 |
32
28
  | `CanRole` | 装饰器 | 声明式角色检查 |
33
- | `CanPermission` | 装饰器 | 声明式权限检查 |
34
- | `UserId` | 参数装饰器 | 获取当前用户ID |
35
29
 
36
30
  ## 快速开始
37
31
 
@@ -57,39 +51,13 @@ import { UserContextMiddleware } from '@lark-apaas/fullstack-nestjs-core';
57
51
  // 配置 AuthZPaas 模块
58
52
  AuthZPaasModule.forRoot({
59
53
  permissionApi: {
60
- // 权限 API 配置
61
- baseUrl: 'http://localhost:3000',
62
- endpoint: '/mock-api/users/:userId/permissions',
63
- timeout: 5000,
54
+ timeout: 5000, // 权限 API 超时时间(毫秒), 默认 5000
64
55
  },
65
- cache: {
66
- ttl: 60, // 缓存时间(秒)
67
- max: 100, // 最大缓存数量
68
- enabled: true, // 启用缓存
69
- },
70
- // 高级配置选项
71
- enableMockRole: false, // 是否启用角色模拟功能(默认 false)
72
- isGlobal: true, // 全局模块
73
56
  }),
74
57
  ],
75
58
  controllers: [/* ... */],
76
59
  providers: [/* ... */],
77
60
  })
78
- export class AppModule implements NestModule {
79
- configure(consumer: MiddlewareConsumer) {
80
- // 1. UserContextMiddleware 必须在最前面,用于解析用户信息
81
- consumer
82
- .apply(UserContextMiddleware)
83
- .forRoutes('*');
84
-
85
- // 2. RolesMiddleware 用于自动获取用户角色
86
- // 注意:对于不需要鉴权的接口可以配置排除
87
- consumer
88
- .apply(RolesMiddleware)
89
- .exclude('mock-api/(.*)') // 排除权限权限 MOCK API
90
- .forRoutes('*');
91
- }
92
- }
93
61
  ```
94
62
 
95
63
  ### 3. 异步配置(可选)
@@ -107,15 +75,8 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
107
75
  inject: [ConfigService],
108
76
  useFactory: async (configService: ConfigService) => ({
109
77
  permissionApi: {
110
- baseUrl: configService.get('PERMISSION_API_URL'),
111
- endpoint: configService.get('PERMISSION_API_ENDPOINT'),
112
78
  timeout: 5000,
113
79
  },
114
- cache: {
115
- ttl: configService.get('CACHE_TTL', 300),
116
- max: configService.get('CACHE_MAX', 1000),
117
- enabled: true,
118
- },
119
80
  }),
120
81
  }),
121
82
  ],
@@ -129,50 +90,7 @@ export class AppModule {}
129
90
 
130
91
  | 配置项 | 类型 | 默认值 | 说明 |
131
92
  |--------|------|--------|------|
132
- | `permissionApi` | `PermissionApiConfig` | - | 权限 API 配置(必需) |
133
- | `cache` | `CacheConfig` | `{ ttl: 300, max: 1000, enabled: true }` | 缓存配置 |
134
- | `isGlobal` | `boolean` | `true` | 是否注册为全局模块 |
135
-
136
- ### 高级配置
137
-
138
- | 配置项 | 类型 | 默认值 | 说明 |
139
- |--------|------|--------|------|
140
- | `enableMockRole` | `boolean` | `false` | 是否启用角色模拟功能 |
141
-
142
- #### enableMockRole
143
-
144
- 启用角色模拟功能,允许通过 Cookie 模拟用户角色,主要用于开发和测试环境。
145
-
146
- ```typescript
147
- AuthZPaasModule.forRoot({
148
- enableMockRole: true, // 启用角色模拟
149
- })
150
- ```
151
-
152
- **功能特性**
153
-
154
- - 通过 Cookie `mockRoles` 设置模拟角色
155
- - 支持多个角色:`admin,editor,viewer`
156
- - 仅在开发/测试环境使用,生产环境应关闭
157
-
158
- **使用示例**
159
-
160
- ```typescript
161
- // 1. 开启角色模拟
162
- POST /api/permissions/mock/enable
163
- {
164
- "roles": ["admin", "editor"]
165
- }
166
-
167
- // 2. 使用 @MockRoles 装饰器获取模拟角色
168
- @Get('test')
169
- testMockRoles(@MockRoles() mockRoles: string[]) {
170
- return { mockRoles };
171
- }
172
-
173
- // 3. 关闭角色模拟
174
- POST /api/permissions/mock/disable
175
- ```
93
+ | `permissionApi` | `PermissionApiConfig` | - | 权限 API 配置 |
176
94
 
177
95
  ## 装饰器使用
178
96
 
@@ -202,7 +120,7 @@ export class DemoController {
202
120
  }
203
121
  ```
204
122
 
205
- #### 示例 2: 多个角色(OR 逻辑,默认)
123
+ #### 示例 2: 多个角色(OR 逻辑)
206
124
 
207
125
  ```typescript
208
126
  @Controller('content')
@@ -216,20 +134,6 @@ export class ContentController {
216
134
  }
217
135
  ```
218
136
 
219
- #### 示例 3: 多个角色(AND 逻辑)
220
-
221
- ```typescript
222
- @Controller('sensitive')
223
- export class SensitiveController {
224
- // 需要同时拥有 admin 和 superuser 角色
225
- @CanRole(['admin', 'superuser'], true)
226
- @Get('dangerous')
227
- dangerousOperation() {
228
- return '危险操作';
229
- }
230
- }
231
- ```
232
-
233
137
  ## 核心组件
234
138
 
235
139
  > 📚 **核心组件是装饰器的底层实现**,了解核心组件有助于深入理解权限系统的工作原理。
@@ -307,17 +211,6 @@ export class YourService {
307
211
  }
308
212
  ```
309
213
 
310
- #### 权限缓存机制
311
-
312
- - **自动缓存**: 权限数据和 Ability 实例会自动缓存
313
- - **防重复请求**: 同时发起的相同请求会共享同一个 Promise
314
- - **TTL 控制**: 通过配置的 `ttl` 自动过期
315
- - **手动清除**: 可以调用 `clearUserCache()` 或 `clearAllCache()`
316
-
317
- ### RolesMiddleware
318
-
319
- `RolesMiddleware` 是一个中间件,用于在请求处理前自动获取用户角色信息。
320
-
321
214
  #### 工作原理
322
215
 
323
216
  1. 从 `req.userContext.userId` 中提取用户ID
@@ -325,38 +218,6 @@ export class YourService {
325
218
  3. 将角色列表注入到 `req.userContext.userRoles`
326
219
  4. 如果获取失败,记录警告但不阻塞请求(由 Guard 处理)
327
220
 
328
- #### 配置示例
329
-
330
- ```typescript
331
- export class AppModule implements NestModule {
332
- configure(consumer: MiddlewareConsumer) {
333
- consumer
334
- .apply(RolesMiddleware)
335
- .exclude('mock-api/(.*)') // 排除特定路由
336
- .forRoutes('*');
337
- }
338
- }
339
- ```
340
-
341
- #### 类型扩展
342
-
343
- `RolesMiddleware` 会扩展 Express Request 类型:
344
-
345
- ```typescript
346
- declare global {
347
- namespace Express {
348
- interface Request {
349
- userContext: {
350
- userId?: string;
351
- tenantId?: number;
352
- appId?: string;
353
- userRoles?: string[]; // RolesMiddleware 注入
354
- }
355
- }
356
- }
357
- }
358
- ```
359
-
360
221
  ## 高级用法
361
222
 
362
223
  ### 1. 手动权限检查
@@ -426,60 +287,6 @@ export class ArticleService {
426
287
  }
427
288
  ```
428
289
 
429
- ### 3. 权限缓存管理
430
-
431
- 当用户权限发生变更时,需要清除缓存:
432
-
433
- ```typescript
434
- import { Injectable } from '@nestjs/common';
435
- import { PermissionService } from '@lark-apaas/nestjs-authzpaas';
436
-
437
- @Injectable()
438
- export class UserManagementService {
439
- constructor(private readonly permissionService: PermissionService) {}
440
-
441
- async updateUserRole(userId: string, newRole: string) {
442
- // 1. 更新用户角色(调用你的业务逻辑)
443
- await this.updateRoleInDatabase(userId, newRole);
444
-
445
- // 2. 清除该用户的权限缓存
446
- this.permissionService.clearUserCache(userId);
447
-
448
- return '角色更新成功';
449
- }
450
-
451
- async batchUpdatePermissions() {
452
- // 批量更新权限后,清除所有缓存
453
- this.permissionService.clearAllCache();
454
-
455
- return '权限批量更新成功';
456
- }
457
-
458
- private async updateRoleInDatabase(userId: string, newRole: string) {
459
- // 实现你的数据库更新逻辑
460
- }
461
- }
462
- ```
463
-
464
- ### 4. 自定义权限 API 响应处理
465
-
466
- 如果你的权限 API 返回的数据格式不同,可以通过自定义逻辑转换:
467
-
468
- ```typescript
469
- // 你的权限 API 应该返回以下格式:
470
- interface PermissionApiResponse {
471
- userId: string;
472
- roles: string[]; // 角色列表,如 ['admin', 'editor']
473
- permissions: Array<{
474
- id: string;
475
- name: string;
476
- sub: string; // 资源类型,如 'User', 'Task'
477
- actions: string[]; // 操作列表,如 ['create', 'read', 'update']
478
- }>;
479
- fetchedAt?: Date;
480
- }
481
- ```
482
-
483
290
  ## 权限 API 规范
484
291
 
485
292
  ### 请求格式
@@ -489,8 +296,6 @@ interface PermissionApiResponse {
489
296
  ```typescript
490
297
  AuthZPaasModule.forRoot({
491
298
  permissionApi: {
492
- baseUrl: 'http://localhost:3000',
493
- endpoint: '/mock-api/users/:userId/permissions', // :userId 会被替换
494
299
  timeout: 5000,
495
300
  },
496
301
  })
@@ -502,28 +307,7 @@ AuthZPaasModule.forRoot({
502
307
 
503
308
  ```json
504
309
  {
505
- "userId": "user-admin",
506
- "roles": ["admin_role", "editor_role"],
507
- "permissions": [
508
- {
509
- "id": "1",
510
- "name": "user.create",
511
- "sub": "User",
512
- "actions": ["create"]
513
- },
514
- {
515
- "id": "2",
516
- "name": "user.read",
517
- "sub": "User",
518
- "actions": ["read"]
519
- },
520
- {
521
- "id": "3",
522
- "name": "task.manage",
523
- "sub": "Task",
524
- "actions": ["manage"]
525
- }
526
- ],
310
+ "roles": ["role_admin", "role_editor"],
527
311
  "fetchedAt": "2024-01-01T00:00:00.000Z"
528
312
  }
529
313
  ```
@@ -532,110 +316,12 @@ AuthZPaasModule.forRoot({
532
316
 
533
317
  - `userId`: 用户ID(必需)
534
318
  - `roles`: 角色名称列表(必需)
535
- - 可以是字符串数组 `['admin', 'editor']`
536
- - 或角色对象数组(会自动提取 `name` 字段)
537
- - `permissions`: 权限列表(必需)
538
- - `id`: 权限ID
539
- - `name`: 权限名称
540
- - `sub`: 资源类型(如 `User`、`Task`、`Article`)
541
- - `actions`: 操作列表(如 `['create', 'read', 'update', 'delete']` 或 `['manage']`)
319
+ - 字符串数组 `['role_admin', 'role_editor']`
542
320
  - `fetchedAt`: 获取时间(可选,SDK 会自动添加)
543
321
 
544
- ### Mock 权限 API 示例
545
-
546
- 参考 `node-demo` 中的实现:
547
-
548
- ```typescript
549
- import { Controller, Get, Param } from '@nestjs/common';
550
- import { Public, Permission, UserRole } from '@lark-apaas/nestjs-authzpaas';
551
-
552
- @Controller('mock-api/users')
553
- @Public() // 重要:跳过 AuthZPaasGuard,避免循环调用
554
- export class MockPermissionController {
555
- // 模拟用户权限数据库
556
- private readonly mockPermissions: Record<string, {
557
- roles: UserRole[],
558
- permissions: Permission[]
559
- }> = {
560
- 'user-admin': {
561
- roles: [
562
- { id: '1', name: 'admin', description: '管理员' },
563
- ],
564
- permissions: [
565
- { id: '1', name: 'user.create', sub: 'User', actions: ['create'] },
566
- { id: '2', name: 'user.read', sub: 'User', actions: ['read'] },
567
- { id: '3', name: 'task.manage', sub: 'Task', actions: ['manage'] },
568
- ],
569
- },
570
- 'user-normal': {
571
- roles: [
572
- { id: '2', name: 'reader', description: '只读用户' },
573
- ],
574
- permissions: [
575
- { id: '4', name: 'user.read', sub: 'User', actions: ['read'] },
576
- { id: '5', name: 'task.read', sub: 'Task', actions: ['read'] },
577
- ],
578
- },
579
- };
580
-
581
- @Get(':userId/permissions')
582
- getUserPermissions(@Param('userId') userId: string) {
583
- const permissions = this.mockPermissions[userId] || this.mockPermissions['user-guest'];
584
-
585
- return {
586
- userId,
587
- ...permissions,
588
- fetchedAt: new Date(),
589
- };
590
- }
591
- }
592
- ```
593
-
594
322
  ## 错误处理
595
323
 
596
- ### 1. 使用内置异常过滤器
597
-
598
- 模块提供了 `AuthZPaasExceptionFilter` 用于统一处理权限异常:
599
-
600
- ```typescript
601
- import { Module } from '@nestjs/common';
602
- import { APP_FILTER } from '@nestjs/core';
603
- import { AuthZPaasExceptionFilter } from '@lark-apaas/nestjs-authzpaas';
604
-
605
- @Module({
606
- providers: [
607
- {
608
- provide: APP_FILTER,
609
- useClass: AuthZPaasExceptionFilter,
610
- },
611
- ],
612
- })
613
- export class AppModule {}
614
- ```
615
-
616
- ### 2. 自定义异常处理
617
-
618
- ```typescript
619
- import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
620
- import { PermissionDeniedException } from '@lark-apaas/nestjs-authzpaas';
621
-
622
- @Catch(PermissionDeniedException)
623
- export class CustomPermissionExceptionFilter implements ExceptionFilter {
624
- catch(exception: PermissionDeniedException, host: ArgumentsHost) {
625
- const ctx = host.switchToHttp();
626
- const response = ctx.getResponse();
627
-
628
- response.status(403).json({
629
- statusCode: 403,
630
- message: exception.message,
631
- error: 'Forbidden',
632
- timestamp: new Date().toISOString(),
633
- });
634
- }
635
- }
636
- ```
637
-
638
- ### 3. 常见异常类型
324
+ ### 1. 常见异常类型
639
325
 
640
326
  | 异常 | 说明 | HTTP 状态码 |
641
327
  |------|------|------------|
@@ -646,156 +332,7 @@ export class CustomPermissionExceptionFilter implements ExceptionFilter {
646
332
 
647
333
  ## 最佳实践
648
334
 
649
- ### 1. 使用有意义的资源和操作名称
650
-
651
- ```typescript
652
- // ✅ 好的做法:清晰的资源和操作命名
653
- @CanPermission([{ actions: ['create'], subject: 'Article' }])
654
- @CanPermission([{ actions: ['update'], subject: 'User' }])
655
- @CanPermission([{ actions: ['delete'], subject: 'Task' }])
656
-
657
- // ❌ 避免使用:模糊的命名
658
- @CanPermission([{ actions: ['action1'], subject: 'resource1' }])
659
- ```
660
-
661
- ### 2. 遵循 CRUD 标准操作
662
-
663
- 建议使用标准的 CRUD 操作名称:
664
-
665
- - `create`: 创建资源
666
- - `read`: 读取资源
667
- - `update`: 更新资源
668
- - `delete`: 删除资源
669
- - `manage`: 完全管理权限(包含所有操作)
670
-
671
- ```typescript
672
- // 推荐的权限设计
673
- @CanPermission([{ actions: ['read'], subject: 'User' }])
674
- @CanPermission([{ actions: ['manage'], subject: 'Task' }]) // manage 包含所有操作
675
- ```
676
-
677
- ### 3. 合理使用 requireAll
678
-
679
- - **默认 `requireAll: true`**: 适用于需要同时满足多个权限的场景(AND 逻辑)
680
- - **`requireAll: false`**: 适用于满足任一权限即可的场景(OR 逻辑)
681
-
682
- ```typescript
683
- // AND 逻辑:需要同时拥有 read 和 update 权限
684
- @CanPermission([{
685
- actions: ['read', 'update'],
686
- subject: 'Article',
687
- requireAll: true // 默认值,可省略
688
- }])
689
-
690
- // OR 逻辑:拥有 read 或 preview 任一权限即可
691
- @CanPermission([{
692
- actions: ['read', 'preview'],
693
- subject: 'Article',
694
- requireAll: false
695
- }])
696
- ```
697
-
698
- ### 4. 权限粒度设计
699
-
700
- 细粒度权限优于粗粒度权限:
701
-
702
- ```typescript
703
- // ✅ 推荐:细粒度权限,更精确的控制
704
- @CanPermission([{ actions: ['publish'], subject: 'Article' }])
705
- @CanPermission([{ actions: ['archive'], subject: 'Article' }])
706
-
707
- // ⚠️ 谨慎使用:粗粒度权限,权限范围过大
708
- @CanPermission([{ actions: ['manage'], subject: 'Article' }])
709
- ```
710
-
711
- ### 5. 缓存策略
712
-
713
- 根据业务需求调整缓存配置:
714
-
715
- ```typescript
716
- AuthZPaasModule.forRoot({
717
- cache: {
718
- ttl: 60, // 开发环境:较短的缓存时间,便于测试
719
- // ttl: 300, // 生产环境:较长的缓存时间,提升性能
720
- max: 1000, // 根据用户量调整
721
- enabled: true, // 生产环境建议开启
722
- },
723
- })
724
- ```
725
-
726
- **重要**:权限变更后务必清除缓存
727
-
728
- ```typescript
729
- // 单用户权限变更
730
- this.permissionService.clearUserCache(userId);
731
-
732
- // 批量权限变更
733
- this.permissionService.clearAllCache();
734
- ```
735
-
736
- ### 6. 中间件配置顺序
737
-
738
- 确保中间件按正确的顺序配置:
739
-
740
- ```typescript
741
- export class AppModule implements NestModule {
742
- configure(consumer: MiddlewareConsumer) {
743
- // 1️⃣ 第一步:UserContextMiddleware 提取用户信息
744
- consumer.apply(UserContextMiddleware).forRoutes('*');
745
-
746
- // 2️⃣ 第二步:RolesMiddleware 获取用户角色
747
- // 注意:必须排除权限 API 路由
748
- consumer
749
- .apply(RolesMiddleware)
750
- .exclude('mock-api/(.*)')
751
- .forRoutes('*');
752
- }
753
- }
754
- ```
755
-
756
- ### 7. 公开接口使用 @Public()
757
-
758
- 对于不需要鉴权的接口,务必使用 `@Public()` 装饰器:
759
-
760
- ```typescript
761
- // ✅ 好的做法
762
- @Controller('health')
763
- export class HealthController {
764
- @Get()
765
- @Public()
766
- check() {
767
- return { status: 'ok' };
768
- }
769
- }
770
-
771
- // ❌ 避免:未标记 @Public(),会触发鉴权检查
772
- @Controller('health')
773
- export class HealthController {
774
- @Get()
775
- check() {
776
- return { status: 'ok' };
777
- }
778
- }
779
- ```
780
-
781
- ### 8. 组合使用装饰器
782
-
783
- 对于复杂的权限需求,可以组合使用多个装饰器:
784
-
785
- ```typescript
786
- @Controller('admin')
787
- export class AdminController {
788
- // 角色检查 + 权限检查
789
- @CanRole(['admin'])
790
- @CanPermission([{ actions: ['manage'], subject: 'System' }])
791
- @Get('system')
792
- manageSystem() {
793
- return '系统管理';
794
- }
795
- }
796
- ```
797
-
798
- ### 9. 使用 TypeScript 类型提示
335
+ ### 1. 使用 TypeScript 类型提示
799
336
 
800
337
  充分利用 TypeScript 的类型系统:
801
338
 
@@ -810,33 +347,7 @@ import {
810
347
  // TypeScript 会自动提供代码提示和类型检查
811
348
  ```
812
349
 
813
- ### 10. 高级配置使用建议
814
-
815
- #### enableMockRole 使用场景
816
-
817
- ```typescript
818
- // ✅ 开发环境:启用角色模拟
819
- AuthZPaasModule.forRoot({
820
- enableMockRole: process.env.NODE_ENV === 'development',
821
- })
822
-
823
- // ✅ 测试环境:便于自动化测试
824
- AuthZPaasModule.forRoot({
825
- enableMockRole: process.env.NODE_ENV === 'test',
826
- })
827
-
828
- // ❌ 生产环境:必须关闭
829
- AuthZPaasModule.forRoot({
830
- enableMockRole: false, // 生产环境安全考虑
831
- })
832
- ```
833
-
834
- **使用建议**:
835
- - 开发环境:便于测试不同角色权限
836
- - 测试环境:自动化测试时模拟不同用户
837
- - 生产环境:必须关闭,避免安全风险
838
-
839
- ### 11. 测试建议
350
+ ### 2. 测试建议
840
351
 
841
352
  编写单元测试时,可以 mock `PermissionService`:
842
353
 
@@ -895,38 +406,6 @@ curl -H "x-user-id: user-admin" http://localhost:3000/demo/admin-only
895
406
  curl -H "x-user-id: user-normal" http://localhost:3000/demo/reader-only
896
407
  ```
897
408
 
898
- ## 常见问题
899
-
900
- ### Q: 如何自定义提取 userId 的逻辑?
901
-
902
- A: 可以自定义 `UserContextMiddleware` 或扩展 `AuthZPaasGuard`。通常在 `UserContextMiddleware` 中从 JWT、Session 或 Header 中提取用户信息并设置到 `req.userContext.userId`。
903
-
904
- ### Q: manage 权限和其他权限的关系?
905
-
906
- A: `manage` 是特殊的操作类型,在 CASL 中表示"所有权限"。如果用户有某个资源的 `manage` 权限,则自动拥有该资源的所有操作权限(`create`、`read`、`update`、`delete` 等)。
907
-
908
- ### Q: 如何实现数据级权限控制(如只能访问自己创建的数据)?
909
-
910
- A: 可以结合 CASL 的 conditions 功能和业务逻辑实现。在 `PermissionService` 中手动检查时,可以传入资源实例进行细粒度验证。
911
-
912
- ### Q: 性能优化建议?
913
-
914
- A:
915
-
916
- - 启用缓存(默认启用)
917
- - 根据业务调整 TTL(生产环境可设置 5-15 分钟)
918
- - 权限变更时及时清除缓存
919
- - 对于高频接口,考虑将权限检查前置到网关层
920
-
921
- ### Q: enableMockRole 的安全风险?
922
-
923
- A:
924
-
925
- - 角色模拟功能仅在开发/测试环境使用
926
- - 生产环境必须设置 `enableMockRole: false`
927
- - 模拟角色通过 Cookie 传递,存在被恶意利用的风险
928
- - 建议在环境变量中控制:`enableMockRole: process.env.NODE_ENV !== 'production'`
929
-
930
409
  ## License
931
410
 
932
411
  MIT
package/dist/index.cjs CHANGED
@@ -91,8 +91,8 @@ var AuthNPaasGuard = class {
91
91
  context.getClass()
92
92
  ]);
93
93
  if (needLoginMeta && !userId && loginUrl) {
94
- response.redirect(302, loginUrl);
95
- return false;
94
+ response.setHeader("x-login-url", loginUrl);
95
+ throw new import_common2.UnauthorizedException("\u672A\u767B\u5F55");
96
96
  }
97
97
  return true;
98
98
  }
@@ -238,15 +238,12 @@ var PermissionDeniedException = class _PermissionDeniedException extends import_
238
238
  /**
239
239
  * 创建角色不足异常
240
240
  */
241
- static roleRequired(requiredRoles, and = false) {
242
- const message = and ? `\u9700\u8981\u6240\u6709\u89D2\u8272: ${requiredRoles.join(", ")}` : `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
241
+ static roleRequired(requiredRoles) {
242
+ const message = `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
243
243
  return new _PermissionDeniedException({
244
244
  type: "ROLE_REQUIRED",
245
245
  message,
246
- requiredRoles,
247
- metadata: {
248
- and
249
- }
246
+ requiredRoles
250
247
  });
251
248
  }
252
249
  /**
@@ -434,13 +431,13 @@ var PermissionService = class _PermissionService {
434
431
  }, import_common7.HttpStatus.BAD_REQUEST);
435
432
  }
436
433
  const ability = this.abilityFactory.createForUser(permissionData);
437
- const { roles, and } = requirement;
434
+ const { roles } = requirement;
438
435
  const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
439
- const hasRole = and ? checkResults.every((result) => result) : checkResults.some((result) => result);
436
+ const hasRole = checkResults.some((result) => result);
440
437
  if (!hasRole) {
441
438
  const userRoles = permissionData.roles;
442
439
  this.logger.warn(`\u89D2\u8272\u68C0\u67E5\u5931\u8D25: \u7528\u6237 ${userId}, \u7528\u6237\u89D2\u8272 [${userRoles.join(", ")}], \u9700\u8981 [${roles.join(", ")}]`);
443
- throw PermissionDeniedException.roleRequired(roles, and);
440
+ throw PermissionDeniedException.roleRequired(roles);
444
441
  }
445
442
  return permissionData;
446
443
  }
@@ -634,19 +631,17 @@ AuthZPaasModule = _ts_decorate6([
634
631
 
635
632
  // src/decorators/can-role.decorator.ts
636
633
  var import_common10 = require("@nestjs/common");
637
- var CanRole = /* @__PURE__ */ __name((role, and = false) => {
634
+ var CanRole = /* @__PURE__ */ __name((role) => {
638
635
  let requirement;
639
636
  if (!Array.isArray(role) && typeof role === "string") {
640
637
  requirement = {
641
638
  roles: [
642
639
  role
643
- ],
644
- and
640
+ ]
645
641
  };
646
642
  } else if (Array.isArray(role) && role.some((role2) => typeof role2 === "string")) {
647
643
  requirement = {
648
- roles: role,
649
- and
644
+ roles: role
650
645
  };
651
646
  } else {
652
647
  throw new Error("Invalid CanRole parameter: " + JSON.stringify(role));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/authzpaas.module.ts","../src/services/permission.service.ts","../../nestjs-authnpaas/src/authnpaas.module.ts","../../nestjs-authnpaas/src/const.ts","../../nestjs-authnpaas/src/guards/authnpaas.guard.ts","../../nestjs-authnpaas/src/decorators/public.decorator.ts","../../nestjs-authnpaas/src/decorators/need-login.decorator.ts","../src/const.ts","../src/casl/ability.factory.ts","../src/exceptions/permission-denied.exception.ts","../src/guards/authzpaas.guard.ts","../src/decorators/can-role.decorator.ts"],"sourcesContent":["/**\n * @lark-apaas/nestjs-authzpaas\n *\n * 基于 CASL 的 NestJS 权限管理模块\n * 提供角色、权限点位、环境等多维度的鉴权能力\n */\n\n// 主模块\nexport { AuthZPaasModule } from './authzpaas.module';\n\n// 装饰器\nexport * from './decorators';\n\n// 守卫\nexport * from './guards';\n\n// 异常类\nexport {\n PermissionDeniedException,\n PermissionDeniedType,\n type PermissionDeniedDetails,\n} from './exceptions/permission-denied.exception';\n\n// 服务\nexport { PermissionService } from './services/permission.service';\n\n// CASL 工厂\nexport { AbilityFactory, ROLE_SUBJECT } from './casl/ability.factory';\nexport type { AppAbility } from './casl/ability.factory';\n\n// 类型定义\nexport * from './types';\n\n// 常量\nexport * from './const';\n","import { DynamicModule, Module, Type } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\n\nimport { PermissionService } from './services/permission.service';\nimport { AbilityFactory } from './casl/ability.factory';\nimport { AuthZPaasGuard } from './guards/authzpaas.guard';\nimport { AuthZPaasModuleOptions } from './types';\nimport { AUTHZPAAS_MODULE_OPTIONS, PERMISSION_API_CONFIG_TOKEN } from './const';\n\nexport interface AuthZPaasModuleAsyncOptions {\n imports?: Type<unknown>[];\n inject?: (string | symbol | Type<unknown>)[];\n useFactory: (\n ...args: unknown[]\n ) => Promise<AuthZPaasModuleOptions> | AuthZPaasModuleOptions;\n}\n\n@Module({})\nexport class AuthZPaasModule {\n static forRoot(options?: AuthZPaasModuleOptions): DynamicModule {\n const { permissionApi = {} } = options || {};\n return {\n module: AuthZPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useValue: { ...options },\n },\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useValue: permissionApi,\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n\n /**\n * 异步注册 AuthZPaas 模块(根模块)\n * 用于需要从配置服务获取设置的场景\n *\n * @param options 异步配置选项\n * @returns 动态模块\n *\n * @example\n * ```typescript\n * @Module({\n * imports: [\n * AuthZPaasModule.forRootAsync({\n * imports: [ConfigModule],\n * inject: [ConfigService],\n * useFactory: async (configService: ConfigService) => ({\n * permissionApi: {\n * baseUrl: configService.get('PERMISSION_API_URL'),\n * apiToken: configService.get('PERMISSION_API_TOKEN'),\n * },\n * cache: {\n * ttl: configService.get('CACHE_TTL', 300),\n * max: configService.get('CACHE_MAX', 1000),\n * },\n * }),\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: AuthZPaasModuleAsyncOptions): DynamicModule {\n const { imports = [], inject = [], useFactory } = options;\n\n return {\n module: AuthZPaasModule,\n global: true,\n imports,\n controllers: [],\n providers: [\n // 异步配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useFactory,\n inject,\n },\n // 权限 API 配置提供者\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useFactory: (moduleOptions: AuthZPaasModuleOptions) => {\n return moduleOptions.permissionApi;\n },\n inject: [AUTHZPAAS_MODULE_OPTIONS],\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n}\n","import { Injectable, Logger, HttpStatus, Inject } from '@nestjs/common';\nimport { Public } from '@lark-apaas/nestjs-authnpaas';\n\nimport type {\n UserPermissionData,\n UserRolesDTO,\n UserContext,\n PermissionApiConfig,\n} from '../types';\nimport { ANONYMOUS_USER_ID, PERMISSION_API_CONFIG_TOKEN } from '../const';\nimport { AbilityFactory, ROLE_SUBJECT } from '../casl/ability.factory';\nimport {\n PermissionDeniedException,\n PermissionDeniedType,\n} from '../exceptions/permission-denied.exception';\nimport type { RoleRequirement } from '../decorators';\n\n/**\n * 权限服务\n * 内置权限获取和缓存逻辑,以及权限检查逻辑\n */\n@Injectable()\n@Public() // 跳过 Guard 的鉴权检查,避免循环调用\nexport class PermissionService {\n private readonly logger = new Logger(PermissionService.name);\n\n constructor(\n @Inject(PERMISSION_API_CONFIG_TOKEN)\n private readonly apiConfig: PermissionApiConfig,\n private readonly abilityFactory: AbilityFactory\n ) {}\n\n /**\n * 获取用户权限数据\n */\n async getUserPermissions(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData | null> {\n // 创建新的请求 Promise\n const requestPromise = (async () => {\n const userId = requestDto.userId || ANONYMOUS_USER_ID;\n try {\n const permissionData = await this.fetchFromApi(requestDto);\n // 添加获取时间\n const dataWithTimestamp: UserPermissionData = {\n ...permissionData,\n fetchedAt: new Date(),\n };\n\n return dataWithTimestamp;\n } catch (error) {\n this.logger.error(\n `Failed to fetch permissions for user ${userId}:`,\n error\n );\n throw error;\n }\n })();\n\n return requestPromise;\n }\n\n /**\n * 从 API 获取权限数据\n * 内置实现,用户无需配置\n */\n private async fetchFromApi(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData> {\n const { timeout = 5000 } = this.apiConfig || {};\n const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/spark/app/${appId}/runtime/api/v1/permissions/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookie) {\n requestHeaders.Cookie = cookie;\n }\n\n if (csrfToken) {\n requestHeaders['X-Suda-Csrf-Token'] = csrfToken;\n }\n\n // 发起请求(带超时控制)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: requestHeaders,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(\n `Permission API returned ${response.status}: ${response.statusText}`\n );\n throw new PermissionDeniedException(\n {\n cause: error,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: error.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n\n const data = (await response.json()) as {\n data?: { roleList?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.roleList || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\n console.log('error', error);\n clearTimeout(timeoutId);\n let err = error as Error;\n\n if ((error as { name?: string }).name === 'AbortError') {\n err = new Error(`Permission API request timeout after ${timeout}ms`);\n }\n\n throw new PermissionDeniedException(\n {\n cause: err,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: err.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n }\n\n // /**\n // * 获取用户的 Ability 实例(带缓存)\n // * @param userId 用户ID\n // * @returns CASL Ability 实例\n // */\n // private async getUserAbility(\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<AppAbility> {\n // // 计算缓存 key\n // const key = this.buildCacheKey(userId, mockRoles);\n\n // // 尝试从缓存获取\n // const cached = this.cache.get(key);\n // if (cached) {\n // return cached.ability;\n // }\n\n // // 缓存未命中,调用 getUserPermissions 会创建并缓存\n // await this.getUserPermissions(userId, mockRoles);\n\n // // 再次从缓存获取(此时一定存在)\n // const newCached = this.cache.get(key);\n // return newCached!.ability;\n // }\n\n /**\n * 检查角色要求\n * 使用 CASL Ability 统一鉴权方式\n * @param requirement 角色要求\n * @param userId 用户ID,匿名用户时为空\n * @returns 用户权限数据\n * @throws PermissionDeniedException 当角色不满足时\n */\n async checkRoles(\n requirement: RoleRequirement,\n userContext?: UserContext,\n cookie?: string,\n csrfToken?: string\n ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n if (!csrfToken) {\n throw new PermissionDeniedException(\n {\n cause: new Error('CSRF token is required'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'CSRF token is required',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken,\n cookie,\n });\n\n if (!permissionData) {\n throw new PermissionDeniedException(\n {\n cause: new Error('Permission data fetch api is not configured'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'Permission data fetch api is not configured',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n // 创建 Ability 实例\n const ability = this.abilityFactory.createForUser(permissionData);\n\n const { roles, and } = requirement;\n\n // 使用 CASL 统一检查角色权限\n // 角色作为 action,'@role' 作为 subject\n const checkResults = roles.map(role => ability.can(role, ROLE_SUBJECT));\n\n const hasRole = and\n ? checkResults.every(result => result)\n : checkResults.some(result => result);\n\n if (!hasRole) {\n const userRoles = permissionData.roles;\n this.logger.warn(\n `角色检查失败: 用户 ${userId}, 用户角色 [${userRoles.join(', ')}], 需要 [${roles.join(', ')}]`\n );\n throw PermissionDeniedException.roleRequired(roles, and);\n }\n\n return permissionData;\n }\n\n // /**\n // * 检查权限要求\n // * @param requirements 权限要求列表\n // * @param userId 用户ID\n // * @returns 用户权限数据\n // * @throws PermissionDeniedException 当权限不满足时\n // */\n // async checkPermissions(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<UserPermissionData> {\n // // 获取权限数据(用于返回)\n // const permissionData = await this.getUserPermissions(userId, mockRoles);\n // if (!permissionData) {\n // throw new PermissionDeniedException(\n // {\n // cause: new Error('Permission data fetch api is not configured'),\n // type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n // message: 'Permission data fetch api is not configured',\n // },\n // HttpStatus.BAD_REQUEST\n // );\n // }\n // const { requirements, or } = params;\n // if (!requirements || requirements.length === 0) {\n // return permissionData;\n // }\n\n // // 获取缓存的 Ability 实例\n // const ability = await this.getUserAbility(userId, mockRoles);\n\n // // 收集所有失败的权限要求\n // const failedRequirements: Array<{\n // actions: string[];\n // subject: string;\n // or: boolean;\n // }> = [];\n\n // // 检查每个权限要求\n // for (const requirement of requirements) {\n // const { actions, subject, or = false } = requirement;\n\n // // 使用缓存的 Ability 实例检查权限\n // const checkResults = actions.map(action => ability.can(action, subject));\n\n // // 根据 or 决定是 AND 还是 OR 逻辑\n // const hasPermission = or\n // ? checkResults.some(result => result)\n // : checkResults.every(result => result);\n\n // if (!hasPermission) {\n // failedRequirements.push({\n // actions,\n // subject: String(subject),\n // or,\n // });\n // }\n // }\n\n // // 如果有失败的权限要求,抛出异常\n // if (failedRequirements.length > 0) {\n // if (or && failedRequirements.length === requirements.length) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // true\n // );\n // } else if (!or) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // false\n // );\n // }\n // }\n // return permissionData;\n // }\n}\n","import { DynamicModule, Module } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\nimport { AuthNPaasModuleOptions } from './types';\nimport { AUTHNPAAS_MODULE_OPTIONS } from './const';\nimport { AuthNPaasGuard } from './guards/authnpaas.guard';\n\n@Module({})\nexport class AuthNPaasModule {\n static forRoot(options?: AuthNPaasModuleOptions): DynamicModule {\n return {\n module: AuthNPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHNPAAS_MODULE_OPTIONS,\n useValue: {\n ...(options || {}),\n },\n },\n // 核心服务\n Reflector,\n // 服务提供者\n AuthNPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthNPaasGuard,\n },\n ],\n exports: [],\n };\n }\n}\n","/**\n * 常量\n */\n\n/** AuthNPaas 模块选项 Token */\nexport const AUTHNPAAS_MODULE_OPTIONS = Symbol('AUTHNPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authnpaas:needLogin';\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Reflector } from '@nestjs/core';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * AuthNPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthNPaasGuard implements CanActivate {\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const response = http.getResponse<Response>();\n\n const { userId, loginUrl } = request.userContext || {};\n\n // 读取 NeedLogin 元数据并标记到请求对象,供异常过滤器使用\n const needLoginMeta = this.reflector.getAllAndOverride<{\n loginPath?: string;\n }>(NEED_LOGIN_KEY, [context.getHandler(), context.getClass()]);\n\n // NeedLogin 且无 userId -> 在守卫内直接重定向并拦截\n if (needLoginMeta && !userId && loginUrl) {\n response.redirect(302, loginUrl);\n return false;\n }\n\n return true;\n }\n}\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public 装饰器的元数据键\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * 标记接口为公开接口,跳过 AuthNPaasGuard 的鉴权检查\n * \n * @example\n * ```typescript\n * @Controller('mock-api/users')\n * @Public() // 控制器级别\n * export class MockPermissionController {\n * @Get(':userId/permissions')\n * getUserPermissions() {}\n * }\n * \n * // 或者在方法级别\n * @Controller('api')\n * export class ApiController {\n * @Get('public-info')\n * @Public() // 方法级别\n * getPublicInfo() {}\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n","import { SetMetadata } from '@nestjs/common';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * NeedLogin 装饰器\n * 标记接口需要登录。如果鉴权失败(如 401/403),异常过滤器会将请求重定向到登录页。\n * 可选地传入登录页路径,默认 '/login'。\n */\nexport const NeedLogin = (loginPath?: string): MethodDecorator & ClassDecorator => {\n return SetMetadata(NEED_LOGIN_KEY, { loginPath });\n};\n\n\n","/**\n * 常量\n */\n\n/** 匿名用户 ID */\nexport const ANONYMOUS_USER_ID = 'anonymous_user_id';\n\n/**\n * 依赖注入 Token\n */\n\n/** 权限 API 配置 Token */\nexport const PERMISSION_API_CONFIG_TOKEN = Symbol('PERMISSION_API_CONFIG');\n\n/** AuthZPaas 模块选项 Token */\nexport const AUTHZPAAS_MODULE_OPTIONS = Symbol('AUTHZPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要的角色元数据键 */\nexport const ROLES_KEY = 'authzpaas:roles';\n\n/** 需要的权限元数据键 */\n// export const PERMISSIONS_KEY = 'authzpaas:permissions';\n","import { Injectable } from '@nestjs/common';\nimport { AbilityBuilder, PureAbility, AbilityClass } from '@casl/ability';\nimport { UserPermissionData, Action, Subject } from '../types';\n\n/**\n * CASL Ability 类型\n */\nexport type AppAbility = PureAbility<[Action, Subject]>;\n\n/**\n * 角色检查的特殊 Subject\n * 用于统一角色鉴权和权限点位鉴权\n * \n * 使用方式:\n * - 权限点位鉴权:ability.can('read', 'Todo')\n * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')\n */\nexport const ROLE_SUBJECT = '@role';\n\n/**\n * Ability 工厂\n * 负责根据用户权限数据创建 CASL Ability 实例\n * \n * 统一了两种鉴权方式:\n * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject\n * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式\n */\n@Injectable()\nexport class AbilityFactory {\n /**\n * 为用户创建 Ability\n */\n createForUser(permissionData: UserPermissionData): AppAbility {\n const { can, build } = new AbilityBuilder<AppAbility>(\n PureAbility as AbilityClass<AppAbility>,\n );\n\n // TODO: 基于权限点位设置能力\n // for (const permission of permissionData.permissions) {\n // const { sub, actions } = permission;\n \n // // 为每个 action 添加权限\n // for (const action of actions) {\n // can(action as Action, sub);\n // }\n // }\n\n // 基于角色设置能力\n for (const role of permissionData.roles) {\n can(role as Action, ROLE_SUBJECT);\n }\n\n return build();\n }\n}\n","import { HttpException } from '@nestjs/common';\n\n/**\n * 权限拒绝异常类型\n */\nexport enum PermissionDeniedType {\n /** 用户未认证 */\n UNAUTHENTICATED = 'UNAUTHENTICATED',\n /** 缺少角色 */\n ROLE_REQUIRED = 'ROLE_REQUIRED',\n /** 缺少权限 */\n PERMISSION_REQUIRED = 'PERMISSION_REQUIRED',\n /** 权限配置查询失败 */\n PERMISSION_CONFIG_QUERY_FAILED = 'PERMISSION_CONFIG_QUERY_FAILED',\n}\n\n/**\n * 权限拒绝异常详情\n */\nexport interface PermissionDeniedDetails {\n /** 错误堆栈 */\n cause?: Error;\n /** 异常类型 */\n type: PermissionDeniedType;\n /** 错误消息 */\n message: string;\n /** 需要的角色(如果适用) */\n requiredRoles?: string[];\n /** 需要的权限(如果适用) */\n requiredPermissions?: Array<{\n actions: string[];\n subject: string;\n }>;\n /** 环境要求(如果适用) */\n environmentRequirement?: string;\n /** 额外信息 */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * 权限拒绝异常\n * 专门用于 AuthZPaas 模块的权限检查失败场景\n */\nexport class PermissionDeniedException extends HttpException {\n public readonly type: PermissionDeniedType;\n public readonly details: PermissionDeniedDetails;\n\n constructor(details: PermissionDeniedDetails, httpStatusCode: number = 403) {\n super(\n {\n statusCode: httpStatusCode,\n cause: details.cause,\n type: details.type,\n message: details.message,\n ...(details.requiredRoles && { requiredRoles: details.requiredRoles }),\n ...(details.requiredPermissions && {\n requiredPermissions: details.requiredPermissions,\n }),\n ...(details.environmentRequirement && {\n environmentRequirement: details.environmentRequirement,\n }),\n ...(details.metadata && { metadata: details.metadata }),\n },\n httpStatusCode\n );\n\n this.type = details.type;\n this.details = details;\n this.name = 'PermissionDeniedException';\n }\n\n /**\n * 创建用户未认证异常\n */\n static unauthenticated(\n message: string = '用户未认证'\n ): PermissionDeniedException {\n return new PermissionDeniedException({\n type: PermissionDeniedType.UNAUTHENTICATED,\n message,\n });\n }\n\n /**\n * 创建角色不足异常\n */\n static roleRequired(\n requiredRoles: string[],\n and: boolean = false\n ): PermissionDeniedException {\n const message = and\n ? `需要所有角色: ${requiredRoles.join(', ')}`\n : `需要以下任一角色: ${requiredRoles.join(', ')}`;\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.ROLE_REQUIRED,\n message,\n requiredRoles,\n metadata: { and },\n });\n }\n\n /**\n * 创建权限不足异常\n */\n static permissionRequired(\n requiredPermissions: Array<{ actions: string[]; subject: string }>,\n or: boolean = false,\n customMessage?: string\n ): PermissionDeniedException {\n let message: string;\n\n if (customMessage) {\n message = customMessage;\n } else if (requiredPermissions.length === 1) {\n const perm = requiredPermissions[0];\n message = or\n ? `缺少权限: 需要对 ${perm.subject} 执行以下任一操作 [${perm.actions.join(', ')}]`\n : `缺少权限: 需要对 ${perm.subject} 执行所有操作 [${perm.actions.join(', ')}]`;\n } else {\n message = or\n ? `缺少权限: 需要满足以下任一权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行以下任一操作 [${actions.join(', ')}]`).join(', ')}`\n : `缺少权限: 需要满足以下所有权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行所有操作 [${actions.join(', ')}]`).join(', ')}`;\n }\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.PERMISSION_REQUIRED,\n message,\n requiredPermissions,\n metadata: { or },\n });\n }\n}\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { PermissionService } from '../services/permission.service';\nimport { ROLES_KEY } from '../const';\nimport { CheckRoleRequirement } from '../decorators';\nimport { UserContext } from '../types';\n\n/**\n * AuthZPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthZPaasGuard implements CanActivate {\n constructor(\n private reflector: Reflector,\n private permissionService: PermissionService\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const cookie = request.headers.cookie as string;\n const csrfToken = request.csrfToken as string;\n\n // FIXME: use new api to get base url\n const baseUrl = `${request.protocol}://${request.get('host')}`;\n\n const userContext = request.userContext as UserContext;\n userContext.baseUrl = baseUrl;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n // 检查角色要求\n await this.permissionService.checkRoles(\n checkRoleRequirement,\n userContext,\n cookie,\n csrfToken\n );\n }\n\n // // 检查权限要求\n // const checkPermissionParams =\n // this.reflector.getAllAndOverride<CheckPermissionsParams>(\n // PERMISSIONS_KEY,\n // [context.getHandler(), context.getClass()]\n // );\n\n // if (checkPermissionParams) {\n // await this.checkPermissionRequirement(\n // checkPermissionParams,\n // userId,\n // mockRoles\n // );\n // }\n\n return true;\n }\n\n // /**\n // * 检查权限要求\n // */\n // private async checkPermissionRequirement(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ) {\n // return this.permissionService.checkPermissions(params, userId, mockRoles);\n // }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { ROLES_KEY } from '../const';\n\n/**\n * 角色要求配置\n */\nexport interface RoleRequirement {\n /** 需要的角色列表 */\n roles: string[];\n /** 是否需要所有角色(AND),默认 false(OR) */\n and?: boolean;\n}\n\nexport type CheckRoleRequirement = RoleRequirement;\n\n/**\n * 要求用户拥有指定角色\n * \n * @example\n * ```typescript\n * // 需要任一角色\n * @CanRole(['admin', 'moderator'])\n * async deleteUser() {}\n * \n * // 需要所有角色\n * @CanRole(['admin', 'superuser'], true)\n * async criticalOperation() {}\n * ```\n */\nexport const CanRole = (\n role: string[] | string,\n and: boolean = false,\n): MethodDecorator => {\n // 解析参数\n let requirement: RoleRequirement;\n\n if (!Array.isArray(role) && typeof role === 'string') {\n // 对象形式\n requirement = {\n roles: [role],\n and: and,\n };\n } else if (Array.isArray(role) && role.some((role) => typeof role === 'string')) {\n // 字符串列表形式\n requirement = {\n roles: role as string[],\n and: and,\n };\n } else {\n throw new Error('Invalid CanRole parameter: ' + JSON.stringify(role));\n }\n\n return SetMetadata(ROLES_KEY, requirement);\n};\n"],"mappings":"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","names":["import_common","import_core","import_common","import_common","import_core","AUTHNPAAS_MODULE_OPTIONS","Symbol","NEED_LOGIN_KEY","AuthNPaasGuard","reflector","canActivate","context","http","switchToHttp","request","getRequest","response","getResponse","userId","loginUrl","userContext","needLoginMeta","getAllAndOverride","getHandler","getClass","redirect","AuthNPaasModule","forRoot","options","module","global","controllers","providers","provide","useValue","Reflector","APP_GUARD","useClass","exports","IS_PUBLIC_KEY","Public","__name","SetMetadata","ANONYMOUS_USER_ID","PERMISSION_API_CONFIG_TOKEN","Symbol","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","import_common","ROLE_SUBJECT","AbilityFactory","createForUser","permissionData","can","build","AbilityBuilder","PureAbility","role","roles","import_common","PermissionDeniedType","PermissionDeniedException","HttpException","type","details","httpStatusCode","statusCode","cause","message","requiredRoles","requiredPermissions","environmentRequirement","metadata","name","unauthenticated","roleRequired","and","join","permissionRequired","or","customMessage","length","perm","subject","actions","map","PermissionService","logger","Logger","name","apiConfig","abilityFactory","getUserPermissions","requestDto","requestPromise","userId","ANONYMOUS_USER_ID","permissionData","fetchFromApi","dataWithTimestamp","fetchedAt","Date","error","timeout","baseUrl","appId","cookie","csrfToken","url","requestHeaders","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","credentials","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","roleList","console","log","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","and","checkResults","map","role","can","ROLE_SUBJECT","hasRole","every","result","some","userRoles","warn","join","roleRequired","import_common","import_core","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","cookie","headers","csrfToken","baseUrl","protocol","get","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","checkRoles","AuthZPaasModule","forRoot","options","permissionApi","module","global","controllers","providers","provide","AUTHZPAAS_MODULE_OPTIONS","useValue","PERMISSION_API_CONFIG_TOKEN","Reflector","PermissionService","AbilityFactory","AuthZPaasGuard","APP_GUARD","useClass","exports","forRootAsync","imports","inject","useFactory","moduleOptions","import_common","CanRole","role","and","requirement","Array","isArray","roles","some","Error","JSON","stringify","SetMetadata","ROLES_KEY"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/authzpaas.module.ts","../src/services/permission.service.ts","../../nestjs-authnpaas/src/authnpaas.module.ts","../../nestjs-authnpaas/src/const.ts","../../nestjs-authnpaas/src/guards/authnpaas.guard.ts","../../nestjs-authnpaas/src/decorators/public.decorator.ts","../../nestjs-authnpaas/src/decorators/need-login.decorator.ts","../src/const.ts","../src/casl/ability.factory.ts","../src/exceptions/permission-denied.exception.ts","../src/guards/authzpaas.guard.ts","../src/decorators/can-role.decorator.ts"],"sourcesContent":["/**\n * @lark-apaas/nestjs-authzpaas\n *\n * 基于 CASL 的 NestJS 权限管理模块\n * 提供角色、权限点位、环境等多维度的鉴权能力\n */\n\n// 主模块\nexport { AuthZPaasModule } from './authzpaas.module';\n\n// 装饰器\nexport * from './decorators';\n\n// 守卫\nexport * from './guards';\n\n// 异常类\nexport {\n PermissionDeniedException,\n PermissionDeniedType,\n type PermissionDeniedDetails,\n} from './exceptions/permission-denied.exception';\n\n// 服务\nexport { PermissionService } from './services/permission.service';\n\n// CASL 工厂\nexport { AbilityFactory, ROLE_SUBJECT } from './casl/ability.factory';\nexport type { AppAbility } from './casl/ability.factory';\n\n// 类型定义\nexport * from './types';\n\n// 常量\nexport * from './const';\n","import { DynamicModule, Module, Type } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\n\nimport { PermissionService } from './services/permission.service';\nimport { AbilityFactory } from './casl/ability.factory';\nimport { AuthZPaasGuard } from './guards/authzpaas.guard';\nimport { AuthZPaasModuleOptions } from './types';\nimport { AUTHZPAAS_MODULE_OPTIONS, PERMISSION_API_CONFIG_TOKEN } from './const';\n\nexport interface AuthZPaasModuleAsyncOptions {\n imports?: Type<unknown>[];\n inject?: (string | symbol | Type<unknown>)[];\n useFactory: (\n ...args: unknown[]\n ) => Promise<AuthZPaasModuleOptions> | AuthZPaasModuleOptions;\n}\n\n@Module({})\nexport class AuthZPaasModule {\n static forRoot(options?: AuthZPaasModuleOptions): DynamicModule {\n const { permissionApi = {} } = options || {};\n return {\n module: AuthZPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useValue: { ...options },\n },\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useValue: permissionApi,\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n\n /**\n * 异步注册 AuthZPaas 模块(根模块)\n * 用于需要从配置服务获取设置的场景\n *\n * @param options 异步配置选项\n * @returns 动态模块\n *\n * @example\n * ```typescript\n * @Module({\n * imports: [\n * AuthZPaasModule.forRootAsync({\n * imports: [ConfigModule],\n * inject: [ConfigService],\n * useFactory: async (configService: ConfigService) => ({\n * permissionApi: {\n * baseUrl: configService.get('PERMISSION_API_URL'),\n * apiToken: configService.get('PERMISSION_API_TOKEN'),\n * },\n * cache: {\n * ttl: configService.get('CACHE_TTL', 300),\n * max: configService.get('CACHE_MAX', 1000),\n * },\n * }),\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: AuthZPaasModuleAsyncOptions): DynamicModule {\n const { imports = [], inject = [], useFactory } = options;\n\n return {\n module: AuthZPaasModule,\n global: true,\n imports,\n controllers: [],\n providers: [\n // 异步配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useFactory,\n inject,\n },\n // 权限 API 配置提供者\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useFactory: (moduleOptions: AuthZPaasModuleOptions) => {\n return moduleOptions.permissionApi;\n },\n inject: [AUTHZPAAS_MODULE_OPTIONS],\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n}\n","import { Injectable, Logger, HttpStatus, Inject } from '@nestjs/common';\nimport { Public } from '@lark-apaas/nestjs-authnpaas';\n\nimport type {\n UserPermissionData,\n UserRolesDTO,\n UserContext,\n PermissionApiConfig,\n} from '../types';\nimport { ANONYMOUS_USER_ID, PERMISSION_API_CONFIG_TOKEN } from '../const';\nimport { AbilityFactory, ROLE_SUBJECT } from '../casl/ability.factory';\nimport {\n PermissionDeniedException,\n PermissionDeniedType,\n} from '../exceptions/permission-denied.exception';\nimport type { RoleRequirement } from '../decorators';\n\n/**\n * 权限服务\n * 内置权限获取和缓存逻辑,以及权限检查逻辑\n */\n@Injectable()\n@Public() // 跳过 Guard 的鉴权检查,避免循环调用\nexport class PermissionService {\n private readonly logger = new Logger(PermissionService.name);\n\n constructor(\n @Inject(PERMISSION_API_CONFIG_TOKEN)\n private readonly apiConfig: PermissionApiConfig,\n private readonly abilityFactory: AbilityFactory\n ) {}\n\n /**\n * 获取用户权限数据\n */\n async getUserPermissions(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData | null> {\n // 创建新的请求 Promise\n const requestPromise = (async () => {\n const userId = requestDto.userId || ANONYMOUS_USER_ID;\n try {\n const permissionData = await this.fetchFromApi(requestDto);\n // 添加获取时间\n const dataWithTimestamp: UserPermissionData = {\n ...permissionData,\n fetchedAt: new Date(),\n };\n\n return dataWithTimestamp;\n } catch (error) {\n this.logger.error(\n `Failed to fetch permissions for user ${userId}:`,\n error\n );\n throw error;\n }\n })();\n\n return requestPromise;\n }\n\n /**\n * 从 API 获取权限数据\n * 内置实现,用户无需配置\n */\n private async fetchFromApi(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData> {\n const { timeout = 5000 } = this.apiConfig || {};\n const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/spark/app/${appId}/runtime/api/v1/permissions/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookie) {\n requestHeaders.Cookie = cookie;\n }\n\n if (csrfToken) {\n requestHeaders['X-Suda-Csrf-Token'] = csrfToken;\n }\n\n // 发起请求(带超时控制)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: requestHeaders,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(\n `Permission API returned ${response.status}: ${response.statusText}`\n );\n throw new PermissionDeniedException(\n {\n cause: error,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: error.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n\n const data = (await response.json()) as {\n data?: { roleList?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.roleList || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\n console.log('error', error);\n clearTimeout(timeoutId);\n let err = error as Error;\n\n if ((error as { name?: string }).name === 'AbortError') {\n err = new Error(`Permission API request timeout after ${timeout}ms`);\n }\n\n throw new PermissionDeniedException(\n {\n cause: err,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: err.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n }\n\n // /**\n // * 获取用户的 Ability 实例(带缓存)\n // * @param userId 用户ID\n // * @returns CASL Ability 实例\n // */\n // private async getUserAbility(\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<AppAbility> {\n // // 计算缓存 key\n // const key = this.buildCacheKey(userId, mockRoles);\n\n // // 尝试从缓存获取\n // const cached = this.cache.get(key);\n // if (cached) {\n // return cached.ability;\n // }\n\n // // 缓存未命中,调用 getUserPermissions 会创建并缓存\n // await this.getUserPermissions(userId, mockRoles);\n\n // // 再次从缓存获取(此时一定存在)\n // const newCached = this.cache.get(key);\n // return newCached!.ability;\n // }\n\n /**\n * 检查角色要求\n * 使用 CASL Ability 统一鉴权方式\n * @param requirement 角色要求\n * @param userId 用户ID,匿名用户时为空\n * @returns 用户权限数据\n * @throws PermissionDeniedException 当角色不满足时\n */\n async checkRoles(\n requirement: RoleRequirement,\n userContext?: UserContext,\n cookie?: string,\n csrfToken?: string\n ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n if (!csrfToken) {\n throw new PermissionDeniedException(\n {\n cause: new Error('CSRF token is required'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'CSRF token is required',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken,\n cookie,\n });\n\n if (!permissionData) {\n throw new PermissionDeniedException(\n {\n cause: new Error('Permission data fetch api is not configured'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'Permission data fetch api is not configured',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n // 创建 Ability 实例\n const ability = this.abilityFactory.createForUser(permissionData);\n\n const { roles } = requirement;\n\n // 使用 CASL 统一检查角色权限\n // 角色作为 action,'@role' 作为 subject\n const checkResults = roles.map(role => ability.can(role, ROLE_SUBJECT));\n\n const hasRole = checkResults.some(result => result);\n\n if (!hasRole) {\n const userRoles = permissionData.roles;\n this.logger.warn(\n `角色检查失败: 用户 ${userId}, 用户角色 [${userRoles.join(', ')}], 需要 [${roles.join(', ')}]`\n );\n throw PermissionDeniedException.roleRequired(roles);\n }\n\n return permissionData;\n }\n\n // /**\n // * 检查权限要求\n // * @param requirements 权限要求列表\n // * @param userId 用户ID\n // * @returns 用户权限数据\n // * @throws PermissionDeniedException 当权限不满足时\n // */\n // async checkPermissions(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<UserPermissionData> {\n // // 获取权限数据(用于返回)\n // const permissionData = await this.getUserPermissions(userId, mockRoles);\n // if (!permissionData) {\n // throw new PermissionDeniedException(\n // {\n // cause: new Error('Permission data fetch api is not configured'),\n // type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n // message: 'Permission data fetch api is not configured',\n // },\n // HttpStatus.BAD_REQUEST\n // );\n // }\n // const { requirements, or } = params;\n // if (!requirements || requirements.length === 0) {\n // return permissionData;\n // }\n\n // // 获取缓存的 Ability 实例\n // const ability = await this.getUserAbility(userId, mockRoles);\n\n // // 收集所有失败的权限要求\n // const failedRequirements: Array<{\n // actions: string[];\n // subject: string;\n // or: boolean;\n // }> = [];\n\n // // 检查每个权限要求\n // for (const requirement of requirements) {\n // const { actions, subject, or = false } = requirement;\n\n // // 使用缓存的 Ability 实例检查权限\n // const checkResults = actions.map(action => ability.can(action, subject));\n\n // // 根据 or 决定是 AND 还是 OR 逻辑\n // const hasPermission = or\n // ? checkResults.some(result => result)\n // : checkResults.every(result => result);\n\n // if (!hasPermission) {\n // failedRequirements.push({\n // actions,\n // subject: String(subject),\n // or,\n // });\n // }\n // }\n\n // // 如果有失败的权限要求,抛出异常\n // if (failedRequirements.length > 0) {\n // if (or && failedRequirements.length === requirements.length) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // true\n // );\n // } else if (!or) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // false\n // );\n // }\n // }\n // return permissionData;\n // }\n}\n","import { DynamicModule, Module } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\nimport { AuthNPaasModuleOptions } from './types';\nimport { AUTHNPAAS_MODULE_OPTIONS } from './const';\nimport { AuthNPaasGuard } from './guards/authnpaas.guard';\n\n@Module({})\nexport class AuthNPaasModule {\n static forRoot(options?: AuthNPaasModuleOptions): DynamicModule {\n return {\n module: AuthNPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHNPAAS_MODULE_OPTIONS,\n useValue: {\n ...(options || {}),\n },\n },\n // 核心服务\n Reflector,\n // 服务提供者\n AuthNPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthNPaasGuard,\n },\n ],\n exports: [],\n };\n }\n}\n","/**\n * 常量\n */\n\n/** AuthNPaas 模块选项 Token */\nexport const AUTHNPAAS_MODULE_OPTIONS = Symbol('AUTHNPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authnpaas:needLogin';\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Reflector } from '@nestjs/core';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * AuthNPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthNPaasGuard implements CanActivate {\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const response = http.getResponse<Response>();\n\n const { userId, loginUrl } = request.userContext || {};\n\n // 读取 NeedLogin 元数据并标记到请求对象,供异常过滤器使用\n const needLoginMeta = this.reflector.getAllAndOverride<{\n loginPath?: string;\n }>(NEED_LOGIN_KEY, [context.getHandler(), context.getClass()]);\n\n // NeedLogin 且无 userId -> 在守卫内透出跳转 url\n if (needLoginMeta && !userId && loginUrl) {\n response.setHeader('x-login-url', loginUrl);\n throw new UnauthorizedException('未登录');\n }\n\n return true;\n }\n}\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public 装饰器的元数据键\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * 标记接口为公开接口,跳过 AuthNPaasGuard 的鉴权检查\n * \n * @example\n * ```typescript\n * @Controller('mock-api/users')\n * @Public() // 控制器级别\n * export class MockPermissionController {\n * @Get(':userId/permissions')\n * getUserPermissions() {}\n * }\n * \n * // 或者在方法级别\n * @Controller('api')\n * export class ApiController {\n * @Get('public-info')\n * @Public() // 方法级别\n * getPublicInfo() {}\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n","import { SetMetadata } from '@nestjs/common';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * NeedLogin 装饰器\n * 标记接口需要登录。如果鉴权失败(如 401/403),异常过滤器会将请求重定向到登录页。\n * 可选地传入登录页路径,默认 '/login'。\n */\nexport const NeedLogin = (loginPath?: string): MethodDecorator & ClassDecorator => {\n return SetMetadata(NEED_LOGIN_KEY, { loginPath });\n};\n\n\n","/**\n * 常量\n */\n\n/** 匿名用户 ID */\nexport const ANONYMOUS_USER_ID = 'anonymous_user_id';\n\n/**\n * 依赖注入 Token\n */\n\n/** 权限 API 配置 Token */\nexport const PERMISSION_API_CONFIG_TOKEN = Symbol('PERMISSION_API_CONFIG');\n\n/** AuthZPaas 模块选项 Token */\nexport const AUTHZPAAS_MODULE_OPTIONS = Symbol('AUTHZPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要的角色元数据键 */\nexport const ROLES_KEY = 'authzpaas:roles';\n\n/** 需要的权限元数据键 */\n// export const PERMISSIONS_KEY = 'authzpaas:permissions';\n","import { Injectable } from '@nestjs/common';\nimport { AbilityBuilder, PureAbility, AbilityClass } from '@casl/ability';\nimport { UserPermissionData, Action, Subject } from '../types';\n\n/**\n * CASL Ability 类型\n */\nexport type AppAbility = PureAbility<[Action, Subject]>;\n\n/**\n * 角色检查的特殊 Subject\n * 用于统一角色鉴权和权限点位鉴权\n * \n * 使用方式:\n * - 权限点位鉴权:ability.can('read', 'Todo')\n * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')\n */\nexport const ROLE_SUBJECT = '@role';\n\n/**\n * Ability 工厂\n * 负责根据用户权限数据创建 CASL Ability 实例\n * \n * 统一了两种鉴权方式:\n * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject\n * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式\n */\n@Injectable()\nexport class AbilityFactory {\n /**\n * 为用户创建 Ability\n */\n createForUser(permissionData: UserPermissionData): AppAbility {\n const { can, build } = new AbilityBuilder<AppAbility>(\n PureAbility as AbilityClass<AppAbility>,\n );\n\n // TODO: 基于权限点位设置能力\n // for (const permission of permissionData.permissions) {\n // const { sub, actions } = permission;\n \n // // 为每个 action 添加权限\n // for (const action of actions) {\n // can(action as Action, sub);\n // }\n // }\n\n // 基于角色设置能力\n for (const role of permissionData.roles) {\n can(role as Action, ROLE_SUBJECT);\n }\n\n return build();\n }\n}\n","import { HttpException } from '@nestjs/common';\n\n/**\n * 权限拒绝异常类型\n */\nexport enum PermissionDeniedType {\n /** 用户未认证 */\n UNAUTHENTICATED = 'UNAUTHENTICATED',\n /** 缺少角色 */\n ROLE_REQUIRED = 'ROLE_REQUIRED',\n /** 缺少权限 */\n PERMISSION_REQUIRED = 'PERMISSION_REQUIRED',\n /** 权限配置查询失败 */\n PERMISSION_CONFIG_QUERY_FAILED = 'PERMISSION_CONFIG_QUERY_FAILED',\n}\n\n/**\n * 权限拒绝异常详情\n */\nexport interface PermissionDeniedDetails {\n /** 错误堆栈 */\n cause?: Error;\n /** 异常类型 */\n type: PermissionDeniedType;\n /** 错误消息 */\n message: string;\n /** 需要的角色(如果适用) */\n requiredRoles?: string[];\n /** 需要的权限(如果适用) */\n requiredPermissions?: Array<{\n actions: string[];\n subject: string;\n }>;\n /** 环境要求(如果适用) */\n environmentRequirement?: string;\n /** 额外信息 */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * 权限拒绝异常\n * 专门用于 AuthZPaas 模块的权限检查失败场景\n */\nexport class PermissionDeniedException extends HttpException {\n public readonly type: PermissionDeniedType;\n public readonly details: PermissionDeniedDetails;\n\n constructor(details: PermissionDeniedDetails, httpStatusCode: number = 403) {\n super(\n {\n statusCode: httpStatusCode,\n cause: details.cause,\n type: details.type,\n message: details.message,\n ...(details.requiredRoles && { requiredRoles: details.requiredRoles }),\n ...(details.requiredPermissions && {\n requiredPermissions: details.requiredPermissions,\n }),\n ...(details.environmentRequirement && {\n environmentRequirement: details.environmentRequirement,\n }),\n ...(details.metadata && { metadata: details.metadata }),\n },\n httpStatusCode\n );\n\n this.type = details.type;\n this.details = details;\n this.name = 'PermissionDeniedException';\n }\n\n /**\n * 创建用户未认证异常\n */\n static unauthenticated(\n message: string = '用户未认证'\n ): PermissionDeniedException {\n return new PermissionDeniedException({\n type: PermissionDeniedType.UNAUTHENTICATED,\n message,\n });\n }\n\n /**\n * 创建角色不足异常\n */\n static roleRequired(\n requiredRoles: string[]\n ): PermissionDeniedException {\n const message = `需要以下任一角色: ${requiredRoles.join(', ')}`;\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.ROLE_REQUIRED,\n message,\n requiredRoles,\n });\n }\n\n /**\n * 创建权限不足异常\n */\n static permissionRequired(\n requiredPermissions: Array<{ actions: string[]; subject: string }>,\n or: boolean = false,\n customMessage?: string\n ): PermissionDeniedException {\n let message: string;\n\n if (customMessage) {\n message = customMessage;\n } else if (requiredPermissions.length === 1) {\n const perm = requiredPermissions[0];\n message = or\n ? `缺少权限: 需要对 ${perm.subject} 执行以下任一操作 [${perm.actions.join(', ')}]`\n : `缺少权限: 需要对 ${perm.subject} 执行所有操作 [${perm.actions.join(', ')}]`;\n } else {\n message = or\n ? `缺少权限: 需要满足以下任一权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行以下任一操作 [${actions.join(', ')}]`).join(', ')}`\n : `缺少权限: 需要满足以下所有权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行所有操作 [${actions.join(', ')}]`).join(', ')}`;\n }\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.PERMISSION_REQUIRED,\n message,\n requiredPermissions,\n metadata: { or },\n });\n }\n}\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { PermissionService } from '../services/permission.service';\nimport { ROLES_KEY } from '../const';\nimport { CheckRoleRequirement } from '../decorators';\nimport { UserContext } from '../types';\n\n/**\n * AuthZPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthZPaasGuard implements CanActivate {\n constructor(\n private reflector: Reflector,\n private permissionService: PermissionService\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const cookie = request.headers.cookie as string;\n const csrfToken = request.csrfToken as string;\n\n // FIXME: use new api to get base url\n const baseUrl = `${request.protocol}://${request.get('host')}`;\n\n const userContext = request.userContext as UserContext;\n userContext.baseUrl = baseUrl;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n // 检查角色要求\n await this.permissionService.checkRoles(\n checkRoleRequirement,\n userContext,\n cookie,\n csrfToken\n );\n }\n\n // // 检查权限要求\n // const checkPermissionParams =\n // this.reflector.getAllAndOverride<CheckPermissionsParams>(\n // PERMISSIONS_KEY,\n // [context.getHandler(), context.getClass()]\n // );\n\n // if (checkPermissionParams) {\n // await this.checkPermissionRequirement(\n // checkPermissionParams,\n // userId,\n // mockRoles\n // );\n // }\n\n return true;\n }\n\n // /**\n // * 检查权限要求\n // */\n // private async checkPermissionRequirement(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ) {\n // return this.permissionService.checkPermissions(params, userId, mockRoles);\n // }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { ROLES_KEY } from '../const';\n\n/**\n * 角色要求配置\n */\nexport interface RoleRequirement {\n /** 需要的角色列表 */\n roles: string[];\n}\n\nexport type CheckRoleRequirement = RoleRequirement;\n\n/**\n * 要求用户拥有指定角色\n *\n * @example\n * ```typescript\n * 单一角色\n * @CanRole(['admin'])\n * async deleteUser() {}\n *\n * 任一角色\n * @CanRole(['admin', 'superuser'])\n * async criticalOperation() {}\n * ```\n */\nexport const CanRole = (role: string[] | string): MethodDecorator => {\n // 解析参数\n let requirement: RoleRequirement;\n\n if (!Array.isArray(role) && typeof role === 'string') {\n // 对象形式\n requirement = {\n roles: [role],\n };\n } else if (\n Array.isArray(role) &&\n role.some(role => typeof role === 'string')\n ) {\n // 字符串列表形式\n requirement = {\n roles: role as string[],\n };\n } else {\n throw new Error('Invalid CanRole parameter: ' + JSON.stringify(role));\n }\n\n return SetMetadata(ROLES_KEY, requirement);\n};\n"],"mappings":"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","names":["import_common","import_core","import_common","import_common","import_core","AUTHNPAAS_MODULE_OPTIONS","Symbol","NEED_LOGIN_KEY","AuthNPaasGuard","reflector","canActivate","context","http","switchToHttp","request","getRequest","response","getResponse","userId","loginUrl","userContext","needLoginMeta","getAllAndOverride","getHandler","getClass","setHeader","UnauthorizedException","AuthNPaasModule","forRoot","options","module","global","controllers","providers","provide","useValue","Reflector","APP_GUARD","useClass","exports","IS_PUBLIC_KEY","Public","__name","SetMetadata","ANONYMOUS_USER_ID","PERMISSION_API_CONFIG_TOKEN","Symbol","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","import_common","ROLE_SUBJECT","AbilityFactory","createForUser","permissionData","can","build","AbilityBuilder","PureAbility","role","roles","import_common","PermissionDeniedType","PermissionDeniedException","HttpException","type","details","httpStatusCode","statusCode","cause","message","requiredRoles","requiredPermissions","environmentRequirement","metadata","name","unauthenticated","roleRequired","join","permissionRequired","or","customMessage","length","perm","subject","actions","map","PermissionService","logger","Logger","name","apiConfig","abilityFactory","getUserPermissions","requestDto","requestPromise","userId","ANONYMOUS_USER_ID","permissionData","fetchFromApi","dataWithTimestamp","fetchedAt","Date","error","timeout","baseUrl","appId","cookie","csrfToken","url","requestHeaders","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","credentials","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","roleList","console","log","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","checkResults","map","role","can","ROLE_SUBJECT","hasRole","some","result","userRoles","warn","join","roleRequired","import_common","import_core","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","cookie","headers","csrfToken","baseUrl","protocol","get","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","checkRoles","AuthZPaasModule","forRoot","options","permissionApi","module","global","controllers","providers","provide","AUTHZPAAS_MODULE_OPTIONS","useValue","PERMISSION_API_CONFIG_TOKEN","Reflector","PermissionService","AbilityFactory","AuthZPaasGuard","APP_GUARD","useClass","exports","forRootAsync","imports","inject","useFactory","moduleOptions","import_common","CanRole","role","requirement","Array","isArray","roles","some","Error","JSON","stringify","SetMetadata","ROLES_KEY"]}
package/dist/index.d.cts CHANGED
@@ -115,8 +115,6 @@ declare class AuthZPaasModule {
115
115
  interface RoleRequirement {
116
116
  /** 需要的角色列表 */
117
117
  roles: string[];
118
- /** 是否需要所有角色(AND),默认 false(OR) */
119
- and?: boolean;
120
118
  }
121
119
  type CheckRoleRequirement = RoleRequirement;
122
120
  /**
@@ -124,16 +122,16 @@ type CheckRoleRequirement = RoleRequirement;
124
122
  *
125
123
  * @example
126
124
  * ```typescript
127
- * // 需要任一角色
128
- * @CanRole(['admin', 'moderator'])
125
+ * 单一角色
126
+ * @CanRole(['admin'])
129
127
  * async deleteUser() {}
130
128
  *
131
- * // 需要所有角色
132
- * @CanRole(['admin', 'superuser'], true)
129
+ * 任一角色
130
+ * @CanRole(['admin', 'superuser'])
133
131
  * async criticalOperation() {}
134
132
  * ```
135
133
  */
136
- declare const CanRole: (role: string[] | string, and?: boolean) => MethodDecorator;
134
+ declare const CanRole: (role: string[] | string) => MethodDecorator;
137
135
 
138
136
  /**
139
137
  * CASL Ability 类型
@@ -253,7 +251,7 @@ declare class PermissionDeniedException extends HttpException {
253
251
  /**
254
252
  * 创建角色不足异常
255
253
  */
256
- static roleRequired(requiredRoles: string[], and?: boolean): PermissionDeniedException;
254
+ static roleRequired(requiredRoles: string[]): PermissionDeniedException;
257
255
  /**
258
256
  * 创建权限不足异常
259
257
  */
package/dist/index.d.ts CHANGED
@@ -115,8 +115,6 @@ declare class AuthZPaasModule {
115
115
  interface RoleRequirement {
116
116
  /** 需要的角色列表 */
117
117
  roles: string[];
118
- /** 是否需要所有角色(AND),默认 false(OR) */
119
- and?: boolean;
120
118
  }
121
119
  type CheckRoleRequirement = RoleRequirement;
122
120
  /**
@@ -124,16 +122,16 @@ type CheckRoleRequirement = RoleRequirement;
124
122
  *
125
123
  * @example
126
124
  * ```typescript
127
- * // 需要任一角色
128
- * @CanRole(['admin', 'moderator'])
125
+ * 单一角色
126
+ * @CanRole(['admin'])
129
127
  * async deleteUser() {}
130
128
  *
131
- * // 需要所有角色
132
- * @CanRole(['admin', 'superuser'], true)
129
+ * 任一角色
130
+ * @CanRole(['admin', 'superuser'])
133
131
  * async criticalOperation() {}
134
132
  * ```
135
133
  */
136
- declare const CanRole: (role: string[] | string, and?: boolean) => MethodDecorator;
134
+ declare const CanRole: (role: string[] | string) => MethodDecorator;
137
135
 
138
136
  /**
139
137
  * CASL Ability 类型
@@ -253,7 +251,7 @@ declare class PermissionDeniedException extends HttpException {
253
251
  /**
254
252
  * 创建角色不足异常
255
253
  */
256
- static roleRequired(requiredRoles: string[], and?: boolean): PermissionDeniedException;
254
+ static roleRequired(requiredRoles: string[]): PermissionDeniedException;
257
255
  /**
258
256
  * 创建权限不足异常
259
257
  */
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { Injectable as Injectable3, Logger, HttpStatus, Inject } from "@nestjs/c
11
11
  // ../nestjs-authnpaas/dist/index.js
12
12
  import { Module } from "@nestjs/common";
13
13
  import { APP_GUARD, Reflector as Reflector2 } from "@nestjs/core";
14
- import { Injectable } from "@nestjs/common";
14
+ import { Injectable, UnauthorizedException } from "@nestjs/common";
15
15
  import { Reflector } from "@nestjs/core";
16
16
  import { SetMetadata } from "@nestjs/common";
17
17
  import { SetMetadata as SetMetadata2 } from "@nestjs/common";
@@ -56,8 +56,8 @@ var AuthNPaasGuard = class {
56
56
  context.getClass()
57
57
  ]);
58
58
  if (needLoginMeta && !userId && loginUrl) {
59
- response.redirect(302, loginUrl);
60
- return false;
59
+ response.setHeader("x-login-url", loginUrl);
60
+ throw new UnauthorizedException("\u672A\u767B\u5F55");
61
61
  }
62
62
  return true;
63
63
  }
@@ -203,15 +203,12 @@ var PermissionDeniedException = class _PermissionDeniedException extends HttpExc
203
203
  /**
204
204
  * 创建角色不足异常
205
205
  */
206
- static roleRequired(requiredRoles, and = false) {
207
- const message = and ? `\u9700\u8981\u6240\u6709\u89D2\u8272: ${requiredRoles.join(", ")}` : `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
206
+ static roleRequired(requiredRoles) {
207
+ const message = `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
208
208
  return new _PermissionDeniedException({
209
209
  type: "ROLE_REQUIRED",
210
210
  message,
211
- requiredRoles,
212
- metadata: {
213
- and
214
- }
211
+ requiredRoles
215
212
  });
216
213
  }
217
214
  /**
@@ -399,13 +396,13 @@ var PermissionService = class _PermissionService {
399
396
  }, HttpStatus.BAD_REQUEST);
400
397
  }
401
398
  const ability = this.abilityFactory.createForUser(permissionData);
402
- const { roles, and } = requirement;
399
+ const { roles } = requirement;
403
400
  const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
404
- const hasRole = and ? checkResults.every((result) => result) : checkResults.some((result) => result);
401
+ const hasRole = checkResults.some((result) => result);
405
402
  if (!hasRole) {
406
403
  const userRoles = permissionData.roles;
407
404
  this.logger.warn(`\u89D2\u8272\u68C0\u67E5\u5931\u8D25: \u7528\u6237 ${userId}, \u7528\u6237\u89D2\u8272 [${userRoles.join(", ")}], \u9700\u8981 [${roles.join(", ")}]`);
408
- throw PermissionDeniedException.roleRequired(roles, and);
405
+ throw PermissionDeniedException.roleRequired(roles);
409
406
  }
410
407
  return permissionData;
411
408
  }
@@ -599,19 +596,17 @@ AuthZPaasModule = _ts_decorate6([
599
596
 
600
597
  // src/decorators/can-role.decorator.ts
601
598
  import { SetMetadata as SetMetadata3 } from "@nestjs/common";
602
- var CanRole = /* @__PURE__ */ __name((role, and = false) => {
599
+ var CanRole = /* @__PURE__ */ __name((role) => {
603
600
  let requirement;
604
601
  if (!Array.isArray(role) && typeof role === "string") {
605
602
  requirement = {
606
603
  roles: [
607
604
  role
608
- ],
609
- and
605
+ ]
610
606
  };
611
607
  } else if (Array.isArray(role) && role.some((role2) => typeof role2 === "string")) {
612
608
  requirement = {
613
- roles: role,
614
- and
609
+ roles: role
615
610
  };
616
611
  } else {
617
612
  throw new Error("Invalid CanRole parameter: " + JSON.stringify(role));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/authzpaas.module.ts","../src/services/permission.service.ts","../../nestjs-authnpaas/src/authnpaas.module.ts","../../nestjs-authnpaas/src/const.ts","../../nestjs-authnpaas/src/guards/authnpaas.guard.ts","../../nestjs-authnpaas/src/decorators/public.decorator.ts","../../nestjs-authnpaas/src/decorators/need-login.decorator.ts","../src/const.ts","../src/casl/ability.factory.ts","../src/exceptions/permission-denied.exception.ts","../src/guards/authzpaas.guard.ts","../src/decorators/can-role.decorator.ts"],"sourcesContent":["import { DynamicModule, Module, Type } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\n\nimport { PermissionService } from './services/permission.service';\nimport { AbilityFactory } from './casl/ability.factory';\nimport { AuthZPaasGuard } from './guards/authzpaas.guard';\nimport { AuthZPaasModuleOptions } from './types';\nimport { AUTHZPAAS_MODULE_OPTIONS, PERMISSION_API_CONFIG_TOKEN } from './const';\n\nexport interface AuthZPaasModuleAsyncOptions {\n imports?: Type<unknown>[];\n inject?: (string | symbol | Type<unknown>)[];\n useFactory: (\n ...args: unknown[]\n ) => Promise<AuthZPaasModuleOptions> | AuthZPaasModuleOptions;\n}\n\n@Module({})\nexport class AuthZPaasModule {\n static forRoot(options?: AuthZPaasModuleOptions): DynamicModule {\n const { permissionApi = {} } = options || {};\n return {\n module: AuthZPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useValue: { ...options },\n },\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useValue: permissionApi,\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n\n /**\n * 异步注册 AuthZPaas 模块(根模块)\n * 用于需要从配置服务获取设置的场景\n *\n * @param options 异步配置选项\n * @returns 动态模块\n *\n * @example\n * ```typescript\n * @Module({\n * imports: [\n * AuthZPaasModule.forRootAsync({\n * imports: [ConfigModule],\n * inject: [ConfigService],\n * useFactory: async (configService: ConfigService) => ({\n * permissionApi: {\n * baseUrl: configService.get('PERMISSION_API_URL'),\n * apiToken: configService.get('PERMISSION_API_TOKEN'),\n * },\n * cache: {\n * ttl: configService.get('CACHE_TTL', 300),\n * max: configService.get('CACHE_MAX', 1000),\n * },\n * }),\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: AuthZPaasModuleAsyncOptions): DynamicModule {\n const { imports = [], inject = [], useFactory } = options;\n\n return {\n module: AuthZPaasModule,\n global: true,\n imports,\n controllers: [],\n providers: [\n // 异步配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useFactory,\n inject,\n },\n // 权限 API 配置提供者\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useFactory: (moduleOptions: AuthZPaasModuleOptions) => {\n return moduleOptions.permissionApi;\n },\n inject: [AUTHZPAAS_MODULE_OPTIONS],\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n}\n","import { Injectable, Logger, HttpStatus, Inject } from '@nestjs/common';\nimport { Public } from '@lark-apaas/nestjs-authnpaas';\n\nimport type {\n UserPermissionData,\n UserRolesDTO,\n UserContext,\n PermissionApiConfig,\n} from '../types';\nimport { ANONYMOUS_USER_ID, PERMISSION_API_CONFIG_TOKEN } from '../const';\nimport { AbilityFactory, ROLE_SUBJECT } from '../casl/ability.factory';\nimport {\n PermissionDeniedException,\n PermissionDeniedType,\n} from '../exceptions/permission-denied.exception';\nimport type { RoleRequirement } from '../decorators';\n\n/**\n * 权限服务\n * 内置权限获取和缓存逻辑,以及权限检查逻辑\n */\n@Injectable()\n@Public() // 跳过 Guard 的鉴权检查,避免循环调用\nexport class PermissionService {\n private readonly logger = new Logger(PermissionService.name);\n\n constructor(\n @Inject(PERMISSION_API_CONFIG_TOKEN)\n private readonly apiConfig: PermissionApiConfig,\n private readonly abilityFactory: AbilityFactory\n ) {}\n\n /**\n * 获取用户权限数据\n */\n async getUserPermissions(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData | null> {\n // 创建新的请求 Promise\n const requestPromise = (async () => {\n const userId = requestDto.userId || ANONYMOUS_USER_ID;\n try {\n const permissionData = await this.fetchFromApi(requestDto);\n // 添加获取时间\n const dataWithTimestamp: UserPermissionData = {\n ...permissionData,\n fetchedAt: new Date(),\n };\n\n return dataWithTimestamp;\n } catch (error) {\n this.logger.error(\n `Failed to fetch permissions for user ${userId}:`,\n error\n );\n throw error;\n }\n })();\n\n return requestPromise;\n }\n\n /**\n * 从 API 获取权限数据\n * 内置实现,用户无需配置\n */\n private async fetchFromApi(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData> {\n const { timeout = 5000 } = this.apiConfig || {};\n const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/spark/app/${appId}/runtime/api/v1/permissions/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookie) {\n requestHeaders.Cookie = cookie;\n }\n\n if (csrfToken) {\n requestHeaders['X-Suda-Csrf-Token'] = csrfToken;\n }\n\n // 发起请求(带超时控制)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: requestHeaders,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(\n `Permission API returned ${response.status}: ${response.statusText}`\n );\n throw new PermissionDeniedException(\n {\n cause: error,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: error.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n\n const data = (await response.json()) as {\n data?: { roleList?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.roleList || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\n console.log('error', error);\n clearTimeout(timeoutId);\n let err = error as Error;\n\n if ((error as { name?: string }).name === 'AbortError') {\n err = new Error(`Permission API request timeout after ${timeout}ms`);\n }\n\n throw new PermissionDeniedException(\n {\n cause: err,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: err.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n }\n\n // /**\n // * 获取用户的 Ability 实例(带缓存)\n // * @param userId 用户ID\n // * @returns CASL Ability 实例\n // */\n // private async getUserAbility(\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<AppAbility> {\n // // 计算缓存 key\n // const key = this.buildCacheKey(userId, mockRoles);\n\n // // 尝试从缓存获取\n // const cached = this.cache.get(key);\n // if (cached) {\n // return cached.ability;\n // }\n\n // // 缓存未命中,调用 getUserPermissions 会创建并缓存\n // await this.getUserPermissions(userId, mockRoles);\n\n // // 再次从缓存获取(此时一定存在)\n // const newCached = this.cache.get(key);\n // return newCached!.ability;\n // }\n\n /**\n * 检查角色要求\n * 使用 CASL Ability 统一鉴权方式\n * @param requirement 角色要求\n * @param userId 用户ID,匿名用户时为空\n * @returns 用户权限数据\n * @throws PermissionDeniedException 当角色不满足时\n */\n async checkRoles(\n requirement: RoleRequirement,\n userContext?: UserContext,\n cookie?: string,\n csrfToken?: string\n ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n if (!csrfToken) {\n throw new PermissionDeniedException(\n {\n cause: new Error('CSRF token is required'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'CSRF token is required',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken,\n cookie,\n });\n\n if (!permissionData) {\n throw new PermissionDeniedException(\n {\n cause: new Error('Permission data fetch api is not configured'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'Permission data fetch api is not configured',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n // 创建 Ability 实例\n const ability = this.abilityFactory.createForUser(permissionData);\n\n const { roles, and } = requirement;\n\n // 使用 CASL 统一检查角色权限\n // 角色作为 action,'@role' 作为 subject\n const checkResults = roles.map(role => ability.can(role, ROLE_SUBJECT));\n\n const hasRole = and\n ? checkResults.every(result => result)\n : checkResults.some(result => result);\n\n if (!hasRole) {\n const userRoles = permissionData.roles;\n this.logger.warn(\n `角色检查失败: 用户 ${userId}, 用户角色 [${userRoles.join(', ')}], 需要 [${roles.join(', ')}]`\n );\n throw PermissionDeniedException.roleRequired(roles, and);\n }\n\n return permissionData;\n }\n\n // /**\n // * 检查权限要求\n // * @param requirements 权限要求列表\n // * @param userId 用户ID\n // * @returns 用户权限数据\n // * @throws PermissionDeniedException 当权限不满足时\n // */\n // async checkPermissions(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<UserPermissionData> {\n // // 获取权限数据(用于返回)\n // const permissionData = await this.getUserPermissions(userId, mockRoles);\n // if (!permissionData) {\n // throw new PermissionDeniedException(\n // {\n // cause: new Error('Permission data fetch api is not configured'),\n // type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n // message: 'Permission data fetch api is not configured',\n // },\n // HttpStatus.BAD_REQUEST\n // );\n // }\n // const { requirements, or } = params;\n // if (!requirements || requirements.length === 0) {\n // return permissionData;\n // }\n\n // // 获取缓存的 Ability 实例\n // const ability = await this.getUserAbility(userId, mockRoles);\n\n // // 收集所有失败的权限要求\n // const failedRequirements: Array<{\n // actions: string[];\n // subject: string;\n // or: boolean;\n // }> = [];\n\n // // 检查每个权限要求\n // for (const requirement of requirements) {\n // const { actions, subject, or = false } = requirement;\n\n // // 使用缓存的 Ability 实例检查权限\n // const checkResults = actions.map(action => ability.can(action, subject));\n\n // // 根据 or 决定是 AND 还是 OR 逻辑\n // const hasPermission = or\n // ? checkResults.some(result => result)\n // : checkResults.every(result => result);\n\n // if (!hasPermission) {\n // failedRequirements.push({\n // actions,\n // subject: String(subject),\n // or,\n // });\n // }\n // }\n\n // // 如果有失败的权限要求,抛出异常\n // if (failedRequirements.length > 0) {\n // if (or && failedRequirements.length === requirements.length) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // true\n // );\n // } else if (!or) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // false\n // );\n // }\n // }\n // return permissionData;\n // }\n}\n","import { DynamicModule, Module } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\nimport { AuthNPaasModuleOptions } from './types';\nimport { AUTHNPAAS_MODULE_OPTIONS } from './const';\nimport { AuthNPaasGuard } from './guards/authnpaas.guard';\n\n@Module({})\nexport class AuthNPaasModule {\n static forRoot(options?: AuthNPaasModuleOptions): DynamicModule {\n return {\n module: AuthNPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHNPAAS_MODULE_OPTIONS,\n useValue: {\n ...(options || {}),\n },\n },\n // 核心服务\n Reflector,\n // 服务提供者\n AuthNPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthNPaasGuard,\n },\n ],\n exports: [],\n };\n }\n}\n","/**\n * 常量\n */\n\n/** AuthNPaas 模块选项 Token */\nexport const AUTHNPAAS_MODULE_OPTIONS = Symbol('AUTHNPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authnpaas:needLogin';\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Reflector } from '@nestjs/core';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * AuthNPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthNPaasGuard implements CanActivate {\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const response = http.getResponse<Response>();\n\n const { userId, loginUrl } = request.userContext || {};\n\n // 读取 NeedLogin 元数据并标记到请求对象,供异常过滤器使用\n const needLoginMeta = this.reflector.getAllAndOverride<{\n loginPath?: string;\n }>(NEED_LOGIN_KEY, [context.getHandler(), context.getClass()]);\n\n // NeedLogin 且无 userId -> 在守卫内直接重定向并拦截\n if (needLoginMeta && !userId && loginUrl) {\n response.redirect(302, loginUrl);\n return false;\n }\n\n return true;\n }\n}\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public 装饰器的元数据键\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * 标记接口为公开接口,跳过 AuthNPaasGuard 的鉴权检查\n * \n * @example\n * ```typescript\n * @Controller('mock-api/users')\n * @Public() // 控制器级别\n * export class MockPermissionController {\n * @Get(':userId/permissions')\n * getUserPermissions() {}\n * }\n * \n * // 或者在方法级别\n * @Controller('api')\n * export class ApiController {\n * @Get('public-info')\n * @Public() // 方法级别\n * getPublicInfo() {}\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n","import { SetMetadata } from '@nestjs/common';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * NeedLogin 装饰器\n * 标记接口需要登录。如果鉴权失败(如 401/403),异常过滤器会将请求重定向到登录页。\n * 可选地传入登录页路径,默认 '/login'。\n */\nexport const NeedLogin = (loginPath?: string): MethodDecorator & ClassDecorator => {\n return SetMetadata(NEED_LOGIN_KEY, { loginPath });\n};\n\n\n","/**\n * 常量\n */\n\n/** 匿名用户 ID */\nexport const ANONYMOUS_USER_ID = 'anonymous_user_id';\n\n/**\n * 依赖注入 Token\n */\n\n/** 权限 API 配置 Token */\nexport const PERMISSION_API_CONFIG_TOKEN = Symbol('PERMISSION_API_CONFIG');\n\n/** AuthZPaas 模块选项 Token */\nexport const AUTHZPAAS_MODULE_OPTIONS = Symbol('AUTHZPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要的角色元数据键 */\nexport const ROLES_KEY = 'authzpaas:roles';\n\n/** 需要的权限元数据键 */\n// export const PERMISSIONS_KEY = 'authzpaas:permissions';\n","import { Injectable } from '@nestjs/common';\nimport { AbilityBuilder, PureAbility, AbilityClass } from '@casl/ability';\nimport { UserPermissionData, Action, Subject } from '../types';\n\n/**\n * CASL Ability 类型\n */\nexport type AppAbility = PureAbility<[Action, Subject]>;\n\n/**\n * 角色检查的特殊 Subject\n * 用于统一角色鉴权和权限点位鉴权\n * \n * 使用方式:\n * - 权限点位鉴权:ability.can('read', 'Todo')\n * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')\n */\nexport const ROLE_SUBJECT = '@role';\n\n/**\n * Ability 工厂\n * 负责根据用户权限数据创建 CASL Ability 实例\n * \n * 统一了两种鉴权方式:\n * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject\n * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式\n */\n@Injectable()\nexport class AbilityFactory {\n /**\n * 为用户创建 Ability\n */\n createForUser(permissionData: UserPermissionData): AppAbility {\n const { can, build } = new AbilityBuilder<AppAbility>(\n PureAbility as AbilityClass<AppAbility>,\n );\n\n // TODO: 基于权限点位设置能力\n // for (const permission of permissionData.permissions) {\n // const { sub, actions } = permission;\n \n // // 为每个 action 添加权限\n // for (const action of actions) {\n // can(action as Action, sub);\n // }\n // }\n\n // 基于角色设置能力\n for (const role of permissionData.roles) {\n can(role as Action, ROLE_SUBJECT);\n }\n\n return build();\n }\n}\n","import { HttpException } from '@nestjs/common';\n\n/**\n * 权限拒绝异常类型\n */\nexport enum PermissionDeniedType {\n /** 用户未认证 */\n UNAUTHENTICATED = 'UNAUTHENTICATED',\n /** 缺少角色 */\n ROLE_REQUIRED = 'ROLE_REQUIRED',\n /** 缺少权限 */\n PERMISSION_REQUIRED = 'PERMISSION_REQUIRED',\n /** 权限配置查询失败 */\n PERMISSION_CONFIG_QUERY_FAILED = 'PERMISSION_CONFIG_QUERY_FAILED',\n}\n\n/**\n * 权限拒绝异常详情\n */\nexport interface PermissionDeniedDetails {\n /** 错误堆栈 */\n cause?: Error;\n /** 异常类型 */\n type: PermissionDeniedType;\n /** 错误消息 */\n message: string;\n /** 需要的角色(如果适用) */\n requiredRoles?: string[];\n /** 需要的权限(如果适用) */\n requiredPermissions?: Array<{\n actions: string[];\n subject: string;\n }>;\n /** 环境要求(如果适用) */\n environmentRequirement?: string;\n /** 额外信息 */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * 权限拒绝异常\n * 专门用于 AuthZPaas 模块的权限检查失败场景\n */\nexport class PermissionDeniedException extends HttpException {\n public readonly type: PermissionDeniedType;\n public readonly details: PermissionDeniedDetails;\n\n constructor(details: PermissionDeniedDetails, httpStatusCode: number = 403) {\n super(\n {\n statusCode: httpStatusCode,\n cause: details.cause,\n type: details.type,\n message: details.message,\n ...(details.requiredRoles && { requiredRoles: details.requiredRoles }),\n ...(details.requiredPermissions && {\n requiredPermissions: details.requiredPermissions,\n }),\n ...(details.environmentRequirement && {\n environmentRequirement: details.environmentRequirement,\n }),\n ...(details.metadata && { metadata: details.metadata }),\n },\n httpStatusCode\n );\n\n this.type = details.type;\n this.details = details;\n this.name = 'PermissionDeniedException';\n }\n\n /**\n * 创建用户未认证异常\n */\n static unauthenticated(\n message: string = '用户未认证'\n ): PermissionDeniedException {\n return new PermissionDeniedException({\n type: PermissionDeniedType.UNAUTHENTICATED,\n message,\n });\n }\n\n /**\n * 创建角色不足异常\n */\n static roleRequired(\n requiredRoles: string[],\n and: boolean = false\n ): PermissionDeniedException {\n const message = and\n ? `需要所有角色: ${requiredRoles.join(', ')}`\n : `需要以下任一角色: ${requiredRoles.join(', ')}`;\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.ROLE_REQUIRED,\n message,\n requiredRoles,\n metadata: { and },\n });\n }\n\n /**\n * 创建权限不足异常\n */\n static permissionRequired(\n requiredPermissions: Array<{ actions: string[]; subject: string }>,\n or: boolean = false,\n customMessage?: string\n ): PermissionDeniedException {\n let message: string;\n\n if (customMessage) {\n message = customMessage;\n } else if (requiredPermissions.length === 1) {\n const perm = requiredPermissions[0];\n message = or\n ? `缺少权限: 需要对 ${perm.subject} 执行以下任一操作 [${perm.actions.join(', ')}]`\n : `缺少权限: 需要对 ${perm.subject} 执行所有操作 [${perm.actions.join(', ')}]`;\n } else {\n message = or\n ? `缺少权限: 需要满足以下任一权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行以下任一操作 [${actions.join(', ')}]`).join(', ')}`\n : `缺少权限: 需要满足以下所有权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行所有操作 [${actions.join(', ')}]`).join(', ')}`;\n }\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.PERMISSION_REQUIRED,\n message,\n requiredPermissions,\n metadata: { or },\n });\n }\n}\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { PermissionService } from '../services/permission.service';\nimport { ROLES_KEY } from '../const';\nimport { CheckRoleRequirement } from '../decorators';\nimport { UserContext } from '../types';\n\n/**\n * AuthZPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthZPaasGuard implements CanActivate {\n constructor(\n private reflector: Reflector,\n private permissionService: PermissionService\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const cookie = request.headers.cookie as string;\n const csrfToken = request.csrfToken as string;\n\n // FIXME: use new api to get base url\n const baseUrl = `${request.protocol}://${request.get('host')}`;\n\n const userContext = request.userContext as UserContext;\n userContext.baseUrl = baseUrl;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n // 检查角色要求\n await this.permissionService.checkRoles(\n checkRoleRequirement,\n userContext,\n cookie,\n csrfToken\n );\n }\n\n // // 检查权限要求\n // const checkPermissionParams =\n // this.reflector.getAllAndOverride<CheckPermissionsParams>(\n // PERMISSIONS_KEY,\n // [context.getHandler(), context.getClass()]\n // );\n\n // if (checkPermissionParams) {\n // await this.checkPermissionRequirement(\n // checkPermissionParams,\n // userId,\n // mockRoles\n // );\n // }\n\n return true;\n }\n\n // /**\n // * 检查权限要求\n // */\n // private async checkPermissionRequirement(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ) {\n // return this.permissionService.checkPermissions(params, userId, mockRoles);\n // }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { ROLES_KEY } from '../const';\n\n/**\n * 角色要求配置\n */\nexport interface RoleRequirement {\n /** 需要的角色列表 */\n roles: string[];\n /** 是否需要所有角色(AND),默认 false(OR) */\n and?: boolean;\n}\n\nexport type CheckRoleRequirement = RoleRequirement;\n\n/**\n * 要求用户拥有指定角色\n * \n * @example\n * ```typescript\n * // 需要任一角色\n * @CanRole(['admin', 'moderator'])\n * async deleteUser() {}\n * \n * // 需要所有角色\n * @CanRole(['admin', 'superuser'], true)\n * async criticalOperation() {}\n * ```\n */\nexport const CanRole = (\n role: string[] | string,\n and: boolean = false,\n): MethodDecorator => {\n // 解析参数\n let requirement: RoleRequirement;\n\n if (!Array.isArray(role) && typeof role === 'string') {\n // 对象形式\n requirement = {\n roles: [role],\n and: and,\n };\n } else if (Array.isArray(role) && role.some((role) => typeof role === 'string')) {\n // 字符串列表形式\n requirement = {\n roles: role as string[],\n and: and,\n };\n } else {\n throw new Error('Invalid CanRole parameter: ' + JSON.stringify(role));\n }\n\n return SetMetadata(ROLES_KEY, requirement);\n};\n"],"mappings":"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","names":["Module","APP_GUARD","Reflector","Injectable","Logger","HttpStatus","Inject","Module","APP_GUARD","Reflector","Injectable","SetMetadata","AUTHNPAAS_MODULE_OPTIONS","Symbol","NEED_LOGIN_KEY","AuthNPaasGuard","reflector","canActivate","context","http","switchToHttp","request","getRequest","response","getResponse","userId","loginUrl","userContext","needLoginMeta","getAllAndOverride","getHandler","getClass","redirect","AuthNPaasModule","forRoot","options","module","global","controllers","providers","provide","useValue","Reflector","APP_GUARD","useClass","exports","IS_PUBLIC_KEY","Public","__name","SetMetadata","ANONYMOUS_USER_ID","PERMISSION_API_CONFIG_TOKEN","Symbol","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","Injectable","AbilityBuilder","PureAbility","ROLE_SUBJECT","AbilityFactory","createForUser","permissionData","can","build","AbilityBuilder","PureAbility","role","roles","HttpException","PermissionDeniedType","PermissionDeniedException","HttpException","type","details","httpStatusCode","statusCode","cause","message","requiredRoles","requiredPermissions","environmentRequirement","metadata","name","unauthenticated","roleRequired","and","join","permissionRequired","or","customMessage","length","perm","subject","actions","map","PermissionService","logger","Logger","name","apiConfig","abilityFactory","getUserPermissions","requestDto","requestPromise","userId","ANONYMOUS_USER_ID","permissionData","fetchFromApi","dataWithTimestamp","fetchedAt","Date","error","timeout","baseUrl","appId","cookie","csrfToken","url","requestHeaders","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","credentials","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","roleList","console","log","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","and","checkResults","map","role","can","ROLE_SUBJECT","hasRole","every","result","some","userRoles","warn","join","roleRequired","Injectable","Reflector","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","cookie","headers","csrfToken","baseUrl","protocol","get","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","checkRoles","AuthZPaasModule","forRoot","options","permissionApi","module","global","controllers","providers","provide","AUTHZPAAS_MODULE_OPTIONS","useValue","PERMISSION_API_CONFIG_TOKEN","Reflector","PermissionService","AbilityFactory","AuthZPaasGuard","APP_GUARD","useClass","exports","forRootAsync","imports","inject","useFactory","moduleOptions","SetMetadata","CanRole","role","and","requirement","Array","isArray","roles","some","Error","JSON","stringify","SetMetadata","ROLES_KEY"]}
1
+ {"version":3,"sources":["../src/authzpaas.module.ts","../src/services/permission.service.ts","../../nestjs-authnpaas/src/authnpaas.module.ts","../../nestjs-authnpaas/src/const.ts","../../nestjs-authnpaas/src/guards/authnpaas.guard.ts","../../nestjs-authnpaas/src/decorators/public.decorator.ts","../../nestjs-authnpaas/src/decorators/need-login.decorator.ts","../src/const.ts","../src/casl/ability.factory.ts","../src/exceptions/permission-denied.exception.ts","../src/guards/authzpaas.guard.ts","../src/decorators/can-role.decorator.ts"],"sourcesContent":["import { DynamicModule, Module, Type } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\n\nimport { PermissionService } from './services/permission.service';\nimport { AbilityFactory } from './casl/ability.factory';\nimport { AuthZPaasGuard } from './guards/authzpaas.guard';\nimport { AuthZPaasModuleOptions } from './types';\nimport { AUTHZPAAS_MODULE_OPTIONS, PERMISSION_API_CONFIG_TOKEN } from './const';\n\nexport interface AuthZPaasModuleAsyncOptions {\n imports?: Type<unknown>[];\n inject?: (string | symbol | Type<unknown>)[];\n useFactory: (\n ...args: unknown[]\n ) => Promise<AuthZPaasModuleOptions> | AuthZPaasModuleOptions;\n}\n\n@Module({})\nexport class AuthZPaasModule {\n static forRoot(options?: AuthZPaasModuleOptions): DynamicModule {\n const { permissionApi = {} } = options || {};\n return {\n module: AuthZPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useValue: { ...options },\n },\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useValue: permissionApi,\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n\n /**\n * 异步注册 AuthZPaas 模块(根模块)\n * 用于需要从配置服务获取设置的场景\n *\n * @param options 异步配置选项\n * @returns 动态模块\n *\n * @example\n * ```typescript\n * @Module({\n * imports: [\n * AuthZPaasModule.forRootAsync({\n * imports: [ConfigModule],\n * inject: [ConfigService],\n * useFactory: async (configService: ConfigService) => ({\n * permissionApi: {\n * baseUrl: configService.get('PERMISSION_API_URL'),\n * apiToken: configService.get('PERMISSION_API_TOKEN'),\n * },\n * cache: {\n * ttl: configService.get('CACHE_TTL', 300),\n * max: configService.get('CACHE_MAX', 1000),\n * },\n * }),\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n static forRootAsync(options: AuthZPaasModuleAsyncOptions): DynamicModule {\n const { imports = [], inject = [], useFactory } = options;\n\n return {\n module: AuthZPaasModule,\n global: true,\n imports,\n controllers: [],\n providers: [\n // 异步配置提供者\n {\n provide: AUTHZPAAS_MODULE_OPTIONS,\n useFactory,\n inject,\n },\n // 权限 API 配置提供者\n {\n provide: PERMISSION_API_CONFIG_TOKEN,\n useFactory: (moduleOptions: AuthZPaasModuleOptions) => {\n return moduleOptions.permissionApi;\n },\n inject: [AUTHZPAAS_MODULE_OPTIONS],\n },\n // 核心服务\n Reflector,\n // 服务提供者\n PermissionService,\n AbilityFactory,\n AuthZPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthZPaasGuard,\n },\n ],\n exports: [PermissionService, AbilityFactory],\n };\n }\n}\n","import { Injectable, Logger, HttpStatus, Inject } from '@nestjs/common';\nimport { Public } from '@lark-apaas/nestjs-authnpaas';\n\nimport type {\n UserPermissionData,\n UserRolesDTO,\n UserContext,\n PermissionApiConfig,\n} from '../types';\nimport { ANONYMOUS_USER_ID, PERMISSION_API_CONFIG_TOKEN } from '../const';\nimport { AbilityFactory, ROLE_SUBJECT } from '../casl/ability.factory';\nimport {\n PermissionDeniedException,\n PermissionDeniedType,\n} from '../exceptions/permission-denied.exception';\nimport type { RoleRequirement } from '../decorators';\n\n/**\n * 权限服务\n * 内置权限获取和缓存逻辑,以及权限检查逻辑\n */\n@Injectable()\n@Public() // 跳过 Guard 的鉴权检查,避免循环调用\nexport class PermissionService {\n private readonly logger = new Logger(PermissionService.name);\n\n constructor(\n @Inject(PERMISSION_API_CONFIG_TOKEN)\n private readonly apiConfig: PermissionApiConfig,\n private readonly abilityFactory: AbilityFactory\n ) {}\n\n /**\n * 获取用户权限数据\n */\n async getUserPermissions(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData | null> {\n // 创建新的请求 Promise\n const requestPromise = (async () => {\n const userId = requestDto.userId || ANONYMOUS_USER_ID;\n try {\n const permissionData = await this.fetchFromApi(requestDto);\n // 添加获取时间\n const dataWithTimestamp: UserPermissionData = {\n ...permissionData,\n fetchedAt: new Date(),\n };\n\n return dataWithTimestamp;\n } catch (error) {\n this.logger.error(\n `Failed to fetch permissions for user ${userId}:`,\n error\n );\n throw error;\n }\n })();\n\n return requestPromise;\n }\n\n /**\n * 从 API 获取权限数据\n * 内置实现,用户无需配置\n */\n private async fetchFromApi(\n requestDto: UserRolesDTO\n ): Promise<UserPermissionData> {\n const { timeout = 5000 } = this.apiConfig || {};\n const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/spark/app/${appId}/runtime/api/v1/permissions/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookie) {\n requestHeaders.Cookie = cookie;\n }\n\n if (csrfToken) {\n requestHeaders['X-Suda-Csrf-Token'] = csrfToken;\n }\n\n // 发起请求(带超时控制)\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n credentials: 'include',\n headers: requestHeaders,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const error = new Error(\n `Permission API returned ${response.status}: ${response.statusText}`\n );\n throw new PermissionDeniedException(\n {\n cause: error,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: error.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n\n const data = (await response.json()) as {\n data?: { roleList?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.roleList || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\n console.log('error', error);\n clearTimeout(timeoutId);\n let err = error as Error;\n\n if ((error as { name?: string }).name === 'AbortError') {\n err = new Error(`Permission API request timeout after ${timeout}ms`);\n }\n\n throw new PermissionDeniedException(\n {\n cause: err,\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: err.message,\n },\n HttpStatus.INTERNAL_SERVER_ERROR\n );\n }\n }\n\n // /**\n // * 获取用户的 Ability 实例(带缓存)\n // * @param userId 用户ID\n // * @returns CASL Ability 实例\n // */\n // private async getUserAbility(\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<AppAbility> {\n // // 计算缓存 key\n // const key = this.buildCacheKey(userId, mockRoles);\n\n // // 尝试从缓存获取\n // const cached = this.cache.get(key);\n // if (cached) {\n // return cached.ability;\n // }\n\n // // 缓存未命中,调用 getUserPermissions 会创建并缓存\n // await this.getUserPermissions(userId, mockRoles);\n\n // // 再次从缓存获取(此时一定存在)\n // const newCached = this.cache.get(key);\n // return newCached!.ability;\n // }\n\n /**\n * 检查角色要求\n * 使用 CASL Ability 统一鉴权方式\n * @param requirement 角色要求\n * @param userId 用户ID,匿名用户时为空\n * @returns 用户权限数据\n * @throws PermissionDeniedException 当角色不满足时\n */\n async checkRoles(\n requirement: RoleRequirement,\n userContext?: UserContext,\n cookie?: string,\n csrfToken?: string\n ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n if (!csrfToken) {\n throw new PermissionDeniedException(\n {\n cause: new Error('CSRF token is required'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'CSRF token is required',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken,\n cookie,\n });\n\n if (!permissionData) {\n throw new PermissionDeniedException(\n {\n cause: new Error('Permission data fetch api is not configured'),\n type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n message: 'Permission data fetch api is not configured',\n },\n HttpStatus.BAD_REQUEST\n );\n }\n\n // 创建 Ability 实例\n const ability = this.abilityFactory.createForUser(permissionData);\n\n const { roles } = requirement;\n\n // 使用 CASL 统一检查角色权限\n // 角色作为 action,'@role' 作为 subject\n const checkResults = roles.map(role => ability.can(role, ROLE_SUBJECT));\n\n const hasRole = checkResults.some(result => result);\n\n if (!hasRole) {\n const userRoles = permissionData.roles;\n this.logger.warn(\n `角色检查失败: 用户 ${userId}, 用户角色 [${userRoles.join(', ')}], 需要 [${roles.join(', ')}]`\n );\n throw PermissionDeniedException.roleRequired(roles);\n }\n\n return permissionData;\n }\n\n // /**\n // * 检查权限要求\n // * @param requirements 权限要求列表\n // * @param userId 用户ID\n // * @returns 用户权限数据\n // * @throws PermissionDeniedException 当权限不满足时\n // */\n // async checkPermissions(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ): Promise<UserPermissionData> {\n // // 获取权限数据(用于返回)\n // const permissionData = await this.getUserPermissions(userId, mockRoles);\n // if (!permissionData) {\n // throw new PermissionDeniedException(\n // {\n // cause: new Error('Permission data fetch api is not configured'),\n // type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,\n // message: 'Permission data fetch api is not configured',\n // },\n // HttpStatus.BAD_REQUEST\n // );\n // }\n // const { requirements, or } = params;\n // if (!requirements || requirements.length === 0) {\n // return permissionData;\n // }\n\n // // 获取缓存的 Ability 实例\n // const ability = await this.getUserAbility(userId, mockRoles);\n\n // // 收集所有失败的权限要求\n // const failedRequirements: Array<{\n // actions: string[];\n // subject: string;\n // or: boolean;\n // }> = [];\n\n // // 检查每个权限要求\n // for (const requirement of requirements) {\n // const { actions, subject, or = false } = requirement;\n\n // // 使用缓存的 Ability 实例检查权限\n // const checkResults = actions.map(action => ability.can(action, subject));\n\n // // 根据 or 决定是 AND 还是 OR 逻辑\n // const hasPermission = or\n // ? checkResults.some(result => result)\n // : checkResults.every(result => result);\n\n // if (!hasPermission) {\n // failedRequirements.push({\n // actions,\n // subject: String(subject),\n // or,\n // });\n // }\n // }\n\n // // 如果有失败的权限要求,抛出异常\n // if (failedRequirements.length > 0) {\n // if (or && failedRequirements.length === requirements.length) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // true\n // );\n // } else if (!or) {\n // throw PermissionDeniedException.permissionRequired(\n // failedRequirements.map(({ actions, subject }) => ({\n // actions,\n // subject: String(subject),\n // })),\n // false\n // );\n // }\n // }\n // return permissionData;\n // }\n}\n","import { DynamicModule, Module } from '@nestjs/common';\nimport { APP_GUARD, Reflector } from '@nestjs/core';\nimport { AuthNPaasModuleOptions } from './types';\nimport { AUTHNPAAS_MODULE_OPTIONS } from './const';\nimport { AuthNPaasGuard } from './guards/authnpaas.guard';\n\n@Module({})\nexport class AuthNPaasModule {\n static forRoot(options?: AuthNPaasModuleOptions): DynamicModule {\n return {\n module: AuthNPaasModule,\n global: true,\n controllers: [],\n providers: [\n // 配置提供者\n {\n provide: AUTHNPAAS_MODULE_OPTIONS,\n useValue: {\n ...(options || {}),\n },\n },\n // 核心服务\n Reflector,\n // 服务提供者\n AuthNPaasGuard,\n // 守卫提供者\n {\n provide: APP_GUARD,\n useClass: AuthNPaasGuard,\n },\n ],\n exports: [],\n };\n }\n}\n","/**\n * 常量\n */\n\n/** AuthNPaas 模块选项 Token */\nexport const AUTHNPAAS_MODULE_OPTIONS = Symbol('AUTHNPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authnpaas:needLogin';\n","import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Reflector } from '@nestjs/core';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * AuthNPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthNPaasGuard implements CanActivate {\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const response = http.getResponse<Response>();\n\n const { userId, loginUrl } = request.userContext || {};\n\n // 读取 NeedLogin 元数据并标记到请求对象,供异常过滤器使用\n const needLoginMeta = this.reflector.getAllAndOverride<{\n loginPath?: string;\n }>(NEED_LOGIN_KEY, [context.getHandler(), context.getClass()]);\n\n // NeedLogin 且无 userId -> 在守卫内透出跳转 url\n if (needLoginMeta && !userId && loginUrl) {\n response.setHeader('x-login-url', loginUrl);\n throw new UnauthorizedException('未登录');\n }\n\n return true;\n }\n}\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public 装饰器的元数据键\n */\nexport const IS_PUBLIC_KEY = 'isPublic';\n\n/**\n * 标记接口为公开接口,跳过 AuthNPaasGuard 的鉴权检查\n * \n * @example\n * ```typescript\n * @Controller('mock-api/users')\n * @Public() // 控制器级别\n * export class MockPermissionController {\n * @Get(':userId/permissions')\n * getUserPermissions() {}\n * }\n * \n * // 或者在方法级别\n * @Controller('api')\n * export class ApiController {\n * @Get('public-info')\n * @Public() // 方法级别\n * getPublicInfo() {}\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n","import { SetMetadata } from '@nestjs/common';\nimport { NEED_LOGIN_KEY } from '../const';\n\n/**\n * NeedLogin 装饰器\n * 标记接口需要登录。如果鉴权失败(如 401/403),异常过滤器会将请求重定向到登录页。\n * 可选地传入登录页路径,默认 '/login'。\n */\nexport const NeedLogin = (loginPath?: string): MethodDecorator & ClassDecorator => {\n return SetMetadata(NEED_LOGIN_KEY, { loginPath });\n};\n\n\n","/**\n * 常量\n */\n\n/** 匿名用户 ID */\nexport const ANONYMOUS_USER_ID = 'anonymous_user_id';\n\n/**\n * 依赖注入 Token\n */\n\n/** 权限 API 配置 Token */\nexport const PERMISSION_API_CONFIG_TOKEN = Symbol('PERMISSION_API_CONFIG');\n\n/** AuthZPaas 模块选项 Token */\nexport const AUTHZPAAS_MODULE_OPTIONS = Symbol('AUTHZPAAS_MODULE_OPTIONS');\n\n/**\n * 元数据键\n */\n\n/** 需要的角色元数据键 */\nexport const ROLES_KEY = 'authzpaas:roles';\n\n/** 需要的权限元数据键 */\n// export const PERMISSIONS_KEY = 'authzpaas:permissions';\n","import { Injectable } from '@nestjs/common';\nimport { AbilityBuilder, PureAbility, AbilityClass } from '@casl/ability';\nimport { UserPermissionData, Action, Subject } from '../types';\n\n/**\n * CASL Ability 类型\n */\nexport type AppAbility = PureAbility<[Action, Subject]>;\n\n/**\n * 角色检查的特殊 Subject\n * 用于统一角色鉴权和权限点位鉴权\n * \n * 使用方式:\n * - 权限点位鉴权:ability.can('read', 'Todo')\n * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')\n */\nexport const ROLE_SUBJECT = '@role';\n\n/**\n * Ability 工厂\n * 负责根据用户权限数据创建 CASL Ability 实例\n * \n * 统一了两种鉴权方式:\n * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject\n * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式\n */\n@Injectable()\nexport class AbilityFactory {\n /**\n * 为用户创建 Ability\n */\n createForUser(permissionData: UserPermissionData): AppAbility {\n const { can, build } = new AbilityBuilder<AppAbility>(\n PureAbility as AbilityClass<AppAbility>,\n );\n\n // TODO: 基于权限点位设置能力\n // for (const permission of permissionData.permissions) {\n // const { sub, actions } = permission;\n \n // // 为每个 action 添加权限\n // for (const action of actions) {\n // can(action as Action, sub);\n // }\n // }\n\n // 基于角色设置能力\n for (const role of permissionData.roles) {\n can(role as Action, ROLE_SUBJECT);\n }\n\n return build();\n }\n}\n","import { HttpException } from '@nestjs/common';\n\n/**\n * 权限拒绝异常类型\n */\nexport enum PermissionDeniedType {\n /** 用户未认证 */\n UNAUTHENTICATED = 'UNAUTHENTICATED',\n /** 缺少角色 */\n ROLE_REQUIRED = 'ROLE_REQUIRED',\n /** 缺少权限 */\n PERMISSION_REQUIRED = 'PERMISSION_REQUIRED',\n /** 权限配置查询失败 */\n PERMISSION_CONFIG_QUERY_FAILED = 'PERMISSION_CONFIG_QUERY_FAILED',\n}\n\n/**\n * 权限拒绝异常详情\n */\nexport interface PermissionDeniedDetails {\n /** 错误堆栈 */\n cause?: Error;\n /** 异常类型 */\n type: PermissionDeniedType;\n /** 错误消息 */\n message: string;\n /** 需要的角色(如果适用) */\n requiredRoles?: string[];\n /** 需要的权限(如果适用) */\n requiredPermissions?: Array<{\n actions: string[];\n subject: string;\n }>;\n /** 环境要求(如果适用) */\n environmentRequirement?: string;\n /** 额外信息 */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * 权限拒绝异常\n * 专门用于 AuthZPaas 模块的权限检查失败场景\n */\nexport class PermissionDeniedException extends HttpException {\n public readonly type: PermissionDeniedType;\n public readonly details: PermissionDeniedDetails;\n\n constructor(details: PermissionDeniedDetails, httpStatusCode: number = 403) {\n super(\n {\n statusCode: httpStatusCode,\n cause: details.cause,\n type: details.type,\n message: details.message,\n ...(details.requiredRoles && { requiredRoles: details.requiredRoles }),\n ...(details.requiredPermissions && {\n requiredPermissions: details.requiredPermissions,\n }),\n ...(details.environmentRequirement && {\n environmentRequirement: details.environmentRequirement,\n }),\n ...(details.metadata && { metadata: details.metadata }),\n },\n httpStatusCode\n );\n\n this.type = details.type;\n this.details = details;\n this.name = 'PermissionDeniedException';\n }\n\n /**\n * 创建用户未认证异常\n */\n static unauthenticated(\n message: string = '用户未认证'\n ): PermissionDeniedException {\n return new PermissionDeniedException({\n type: PermissionDeniedType.UNAUTHENTICATED,\n message,\n });\n }\n\n /**\n * 创建角色不足异常\n */\n static roleRequired(\n requiredRoles: string[]\n ): PermissionDeniedException {\n const message = `需要以下任一角色: ${requiredRoles.join(', ')}`;\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.ROLE_REQUIRED,\n message,\n requiredRoles,\n });\n }\n\n /**\n * 创建权限不足异常\n */\n static permissionRequired(\n requiredPermissions: Array<{ actions: string[]; subject: string }>,\n or: boolean = false,\n customMessage?: string\n ): PermissionDeniedException {\n let message: string;\n\n if (customMessage) {\n message = customMessage;\n } else if (requiredPermissions.length === 1) {\n const perm = requiredPermissions[0];\n message = or\n ? `缺少权限: 需要对 ${perm.subject} 执行以下任一操作 [${perm.actions.join(', ')}]`\n : `缺少权限: 需要对 ${perm.subject} 执行所有操作 [${perm.actions.join(', ')}]`;\n } else {\n message = or\n ? `缺少权限: 需要满足以下任一权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行以下任一操作 [${actions.join(', ')}]`).join(', ')}`\n : `缺少权限: 需要满足以下所有权限要求: ${requiredPermissions.map(({ actions, subject }) => `对 ${subject} 执行所有操作 [${actions.join(', ')}]`).join(', ')}`;\n }\n\n return new PermissionDeniedException({\n type: PermissionDeniedType.PERMISSION_REQUIRED,\n message,\n requiredPermissions,\n metadata: { or },\n });\n }\n}\n","import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { PermissionService } from '../services/permission.service';\nimport { ROLES_KEY } from '../const';\nimport { CheckRoleRequirement } from '../decorators';\nimport { UserContext } from '../types';\n\n/**\n * AuthZPaas 守卫\n * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService\n */\n@Injectable()\nexport class AuthZPaasGuard implements CanActivate {\n constructor(\n private reflector: Reflector,\n private permissionService: PermissionService\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const http = context.switchToHttp();\n const request = http.getRequest();\n const cookie = request.headers.cookie as string;\n const csrfToken = request.csrfToken as string;\n\n // FIXME: use new api to get base url\n const baseUrl = `${request.protocol}://${request.get('host')}`;\n\n const userContext = request.userContext as UserContext;\n userContext.baseUrl = baseUrl;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n // 检查角色要求\n await this.permissionService.checkRoles(\n checkRoleRequirement,\n userContext,\n cookie,\n csrfToken\n );\n }\n\n // // 检查权限要求\n // const checkPermissionParams =\n // this.reflector.getAllAndOverride<CheckPermissionsParams>(\n // PERMISSIONS_KEY,\n // [context.getHandler(), context.getClass()]\n // );\n\n // if (checkPermissionParams) {\n // await this.checkPermissionRequirement(\n // checkPermissionParams,\n // userId,\n // mockRoles\n // );\n // }\n\n return true;\n }\n\n // /**\n // * 检查权限要求\n // */\n // private async checkPermissionRequirement(\n // params: CheckPermissionsParams,\n // userId?: string,\n // mockRoles?: string[]\n // ) {\n // return this.permissionService.checkPermissions(params, userId, mockRoles);\n // }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { ROLES_KEY } from '../const';\n\n/**\n * 角色要求配置\n */\nexport interface RoleRequirement {\n /** 需要的角色列表 */\n roles: string[];\n}\n\nexport type CheckRoleRequirement = RoleRequirement;\n\n/**\n * 要求用户拥有指定角色\n *\n * @example\n * ```typescript\n * 单一角色\n * @CanRole(['admin'])\n * async deleteUser() {}\n *\n * 任一角色\n * @CanRole(['admin', 'superuser'])\n * async criticalOperation() {}\n * ```\n */\nexport const CanRole = (role: string[] | string): MethodDecorator => {\n // 解析参数\n let requirement: RoleRequirement;\n\n if (!Array.isArray(role) && typeof role === 'string') {\n // 对象形式\n requirement = {\n roles: [role],\n };\n } else if (\n Array.isArray(role) &&\n role.some(role => typeof role === 'string')\n ) {\n // 字符串列表形式\n requirement = {\n roles: role as string[],\n };\n } else {\n throw new Error('Invalid CanRole parameter: ' + JSON.stringify(role));\n }\n\n return SetMetadata(ROLES_KEY, requirement);\n};\n"],"mappings":"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","names":["Module","APP_GUARD","Reflector","Injectable","Logger","HttpStatus","Inject","Module","APP_GUARD","Reflector","Injectable","UnauthorizedException","SetMetadata","AUTHNPAAS_MODULE_OPTIONS","Symbol","NEED_LOGIN_KEY","AuthNPaasGuard","reflector","canActivate","context","http","switchToHttp","request","getRequest","response","getResponse","userId","loginUrl","userContext","needLoginMeta","getAllAndOverride","getHandler","getClass","setHeader","UnauthorizedException","AuthNPaasModule","forRoot","options","module","global","controllers","providers","provide","useValue","Reflector","APP_GUARD","useClass","exports","IS_PUBLIC_KEY","Public","__name","SetMetadata","ANONYMOUS_USER_ID","PERMISSION_API_CONFIG_TOKEN","Symbol","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","Injectable","AbilityBuilder","PureAbility","ROLE_SUBJECT","AbilityFactory","createForUser","permissionData","can","build","AbilityBuilder","PureAbility","role","roles","HttpException","PermissionDeniedType","PermissionDeniedException","HttpException","type","details","httpStatusCode","statusCode","cause","message","requiredRoles","requiredPermissions","environmentRequirement","metadata","name","unauthenticated","roleRequired","join","permissionRequired","or","customMessage","length","perm","subject","actions","map","PermissionService","logger","Logger","name","apiConfig","abilityFactory","getUserPermissions","requestDto","requestPromise","userId","ANONYMOUS_USER_ID","permissionData","fetchFromApi","dataWithTimestamp","fetchedAt","Date","error","timeout","baseUrl","appId","cookie","csrfToken","url","requestHeaders","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","credentials","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","roleList","console","log","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","checkResults","map","role","can","ROLE_SUBJECT","hasRole","some","result","userRoles","warn","join","roleRequired","Injectable","Reflector","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","cookie","headers","csrfToken","baseUrl","protocol","get","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","checkRoles","AuthZPaasModule","forRoot","options","permissionApi","module","global","controllers","providers","provide","AUTHZPAAS_MODULE_OPTIONS","useValue","PERMISSION_API_CONFIG_TOKEN","Reflector","PermissionService","AbilityFactory","AuthZPaasGuard","APP_GUARD","useClass","exports","forRootAsync","imports","inject","useFactory","moduleOptions","SetMetadata","CanRole","role","requirement","Array","isArray","roles","some","Error","JSON","stringify","SetMetadata","ROLES_KEY"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/nestjs-authzpaas",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.13",
4
4
  "description": "FullStack Nestjs authzpaas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,8 @@
35
35
  "lint": "eslint src/**/*.ts",
36
36
  "type-check": "tsc --noEmit",
37
37
  "test": "jest",
38
- "test:watch": "jest --watch"
38
+ "test:watch": "jest --watch",
39
+ "prepublishOnly": "npm run build"
39
40
  },
40
41
  "dependencies": {
41
42
  "@casl/ability": "^6.7.3",