@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 +8 -529
- package/dist/index.cjs +11 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -8
- package/dist/index.d.ts +6 -8
- package/dist/index.js +12 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
-
"
|
|
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
|
-
-
|
|
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
|
-
###
|
|
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.
|
|
95
|
-
|
|
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
|
|
242
|
-
const message =
|
|
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
|
|
434
|
+
const { roles } = requirement;
|
|
438
435
|
const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
|
|
439
|
-
const hasRole =
|
|
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
|
|
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
|
|
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));
|
package/dist/index.cjs.map
CHANGED
|
@@ -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'
|
|
125
|
+
* 单一角色
|
|
126
|
+
* @CanRole(['admin'])
|
|
129
127
|
* async deleteUser() {}
|
|
130
128
|
*
|
|
131
|
-
*
|
|
132
|
-
* @CanRole(['admin', 'superuser']
|
|
129
|
+
* 任一角色
|
|
130
|
+
* @CanRole(['admin', 'superuser'])
|
|
133
131
|
* async criticalOperation() {}
|
|
134
132
|
* ```
|
|
135
133
|
*/
|
|
136
|
-
declare const CanRole: (role: string[] | string
|
|
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[]
|
|
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'
|
|
125
|
+
* 单一角色
|
|
126
|
+
* @CanRole(['admin'])
|
|
129
127
|
* async deleteUser() {}
|
|
130
128
|
*
|
|
131
|
-
*
|
|
132
|
-
* @CanRole(['admin', 'superuser']
|
|
129
|
+
* 任一角色
|
|
130
|
+
* @CanRole(['admin', 'superuser'])
|
|
133
131
|
* async criticalOperation() {}
|
|
134
132
|
* ```
|
|
135
133
|
*/
|
|
136
|
-
declare const CanRole: (role: string[] | string
|
|
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[]
|
|
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.
|
|
60
|
-
|
|
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
|
|
207
|
-
const message =
|
|
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
|
|
399
|
+
const { roles } = requirement;
|
|
403
400
|
const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
|
|
404
|
-
const hasRole =
|
|
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
|
|
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
|
|
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.
|
|
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",
|