@lark-apaas/nestjs-authzpaas 0.1.0-alpha.1 → 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -230,257 +230,6 @@ export class SensitiveController {
230
230
  }
231
231
  ```
232
232
 
233
- ### 2. CanPermission - 权限检查
234
-
235
- 使用 `@CanPermission()` 装饰器检查用户对特定资源的操作权限。
236
-
237
- #### 示例 1: 单个操作检查
238
-
239
- ```typescript
240
- import { Controller, Get } from '@nestjs/common';
241
- import { CanPermission } from '@lark-apaas/nestjs-authzpaas';
242
-
243
- @Controller('demo')
244
- export class DemoController {
245
- // 需要对 User 资源有 read 权限
246
- @CanPermission([{ actions: ['read'], subject: 'User' }])
247
- @Get('read-user')
248
- readUser() {
249
- return {
250
- message: '✅ 用户读取成功(需要 read 权限)',
251
- };
252
- }
253
-
254
- // 需要对 User 资源有 create 权限
255
- @CanPermission([{ actions: ['create'], subject: 'User' }])
256
- @Get('create-user')
257
- createUser() {
258
- return {
259
- message: '✅ 用户创建成功(需要 create 权限)',
260
- };
261
- }
262
- }
263
- ```
264
-
265
- #### 示例 2: 多个操作检查(AND 逻辑,默认)
266
-
267
- ```typescript
268
- @Controller('articles')
269
- export class ArticleController {
270
- // 需要同时拥有 read 和 update 权限
271
- @CanPermission([{
272
- actions: ['read', 'update'],
273
- subject: 'Article',
274
- requireAll: true // 默认为 true,可省略
275
- }])
276
- @Get('publish')
277
- publish() {
278
- return '发布文章';
279
- }
280
- }
281
- ```
282
-
283
- #### 示例 3: 多个操作检查(OR 逻辑)
284
-
285
- ```typescript
286
- @Controller('articles')
287
- export class ArticleController {
288
- // 拥有 read 或 preview 任一权限即可
289
- @CanPermission([{
290
- actions: ['read', 'preview'],
291
- subject: 'Article',
292
- requireAll: false // OR 逻辑
293
- }])
294
- @Get('preview')
295
- preview() {
296
- return '预览文章';
297
- }
298
- }
299
- ```
300
-
301
- #### 示例 4: 多个资源权限检查
302
-
303
- ```typescript
304
- @Controller('articles')
305
- export class ArticleController {
306
- // 需要同时满足:能读文章 AND 能创建评论
307
- @CanPermission([
308
- { actions: ['read'], subject: 'Article' },
309
- { actions: ['create'], subject: 'Comment' }
310
- ])
311
- @Get('comment')
312
- addComment() {
313
- return '添加评论';
314
- }
315
-
316
- // 需要同时满足:能读文章 AND (能更新或删除评论)
317
- @CanPermission([
318
- { actions: ['read'], subject: 'Article' },
319
- { actions: ['update', 'delete'], subject: 'Comment', requireAll: false }
320
- ])
321
- @Get('manage-comment')
322
- manageComment() {
323
- return '管理评论';
324
- }
325
- }
326
- ```
327
-
328
- ### 3. UserId - 获取用户ID
329
-
330
- 使用 `@UserId()` 参数装饰器获取当前用户ID。
331
-
332
- #### 示例 1: 基本用法
333
-
334
- ```typescript
335
- import { Controller, Get } from '@nestjs/common';
336
- import { UserId } from '@lark-apaas/nestjs-authzpaas';
337
-
338
- @Controller('demo')
339
- export class DemoController {
340
- @Get('profile')
341
- getProfile(@UserId() userId: string) {
342
- return {
343
- message: '获取用户资料',
344
- userId,
345
- };
346
- }
347
- }
348
- ```
349
-
350
- #### 示例 2: 结合 PermissionService 使用
351
-
352
- ```typescript
353
- import { Controller, Get } from '@nestjs/common';
354
- import { UserId, PermissionService } from '@lark-apaas/nestjs-authzpaas';
355
-
356
- @Controller('demo')
357
- export class DemoController {
358
- constructor(private readonly permissionService: PermissionService) {}
359
-
360
- @Get('check-permission')
361
- async checkPermission(@UserId() userId: string) {
362
- // 手动检查权限
363
- const hasPermission = await this.permissionService.checkPermissions(
364
- [{ actions: ['read'], subject: 'User' }],
365
- userId
366
- );
367
-
368
- return {
369
- message: '权限检查成功',
370
- userId,
371
- hasPermission,
372
- };
373
- }
374
- }
375
- ```
376
-
377
- #### 示例 3: 使用 CASL Ability
378
-
379
- ```typescript
380
- import { Controller, Get } from '@nestjs/common';
381
- import { UserId, PermissionService, ROLE_SUBJECT } from '@lark-apaas/nestjs-authzpaas';
382
-
383
- @Controller('demo')
384
- export class DemoController {
385
- constructor(private readonly permissionService: PermissionService) {}
386
-
387
- @Get('ability')
388
- async getAbility(@UserId() userId: string) {
389
- const ability = await this.permissionService.getAbility(userId);
390
-
391
- // 使用 CASL Ability 检查权限
392
- const canReadUser = ability.can('read', 'User');
393
- const canWriteTask = ability.can('write', 'Task');
394
- const isAdmin = ability.can('admin', ROLE_SUBJECT);
395
-
396
- return {
397
- userId,
398
- permissions: {
399
- canReadUser,
400
- canWriteTask,
401
- isAdmin,
402
- },
403
- };
404
- }
405
- }
406
- ```
407
-
408
- ### 4. MockRoles - 角色模拟
409
-
410
- 使用 `@MockRoles()` 参数装饰器获取模拟角色,仅在 `enableMockRole: true` 时有效。
411
-
412
- #### 示例 1: 基本用法
413
-
414
- ```typescript
415
- import { Controller, Get } from '@nestjs/common';
416
- import { MockRoles } from '@lark-apaas/nestjs-authzpaas';
417
-
418
- @Controller('demo')
419
- export class DemoController {
420
- @Get('mock-roles')
421
- getMockRoles(@MockRoles() mockRoles: string[]) {
422
- return {
423
- message: '获取模拟角色',
424
- mockRoles: mockRoles || [],
425
- hasMockRoles: !!mockRoles,
426
- };
427
- }
428
- }
429
- ```
430
-
431
- #### 示例 2: 结合权限检查
432
-
433
- ```typescript
434
- @Controller('admin')
435
- export class AdminController {
436
- @Get('test')
437
- @CanRole(['admin']) // 会优先使用模拟角色进行验证
438
- testWithMockRoles(@MockRoles() mockRoles: string[]) {
439
- return {
440
- message: '管理员测试接口',
441
- mockRoles,
442
- };
443
- }
444
- }
445
- ```
446
-
447
- ### 5. 装饰器组合使用
448
-
449
- 可以组合使用多个装饰器实现复杂的鉴权需求。
450
-
451
- #### 示例 1: 角色 + 权限
452
-
453
- ```typescript
454
- import { Controller, Get } from '@nestjs/common';
455
- import { CanRole, CanPermission } from '@lark-apaas/nestjs-authzpaas';
456
-
457
- @Controller('posts')
458
- export class PostController {
459
- // 需要 editor 角色 + 对 Post 有 delete 权限
460
- @CanRole(['editor'])
461
- @CanPermission([{ actions: ['delete'], subject: 'Post' }])
462
- @Get('delete')
463
- delete() {
464
- return '删除帖子';
465
- }
466
- }
467
- ```
468
-
469
- #### 示例 2: 多层权限检查
470
-
471
- ```typescript
472
- @Controller('tasks')
473
- export class TasksController {
474
- // 需要 admin 或 manager 角色 + Task 的 manage 权限
475
- @CanRole(['admin', 'manager'])
476
- @CanPermission([{ actions: ['manage'], subject: 'Task' }])
477
- @Get('manage')
478
- manageTasks() {
479
- return '管理任务';
480
- }
481
- }
482
- ```
483
-
484
233
  ## 核心组件
485
234
 
486
235
  > 📚 **核心组件是装饰器的底层实现**,了解核心组件有助于深入理解权限系统的工作原理。
@@ -491,8 +240,6 @@ export class TasksController {
491
240
 
492
241
  1. **提取用户ID**: 从 `req.userContext.userId` 中提取用户ID
493
242
  2. **角色检查**: 如果使用了 `@CanRole()`,检查用户角色
494
- 3. **权限检查**: 如果使用了 `@CanPermission()`,检查用户权限
495
- 4. **环境检查**: 如果使用了 `@RequireEnvironment()`,检查环境条件
496
243
  5. **注入权限数据**: 将权限数据附加到 `req.userPermissions`
497
244
 
498
245
  **工作流程**:
package/dist/index.cjs CHANGED
@@ -24,7 +24,6 @@ __export(index_exports, {
24
24
  ANONYMOUS_USER_ID: () => ANONYMOUS_USER_ID,
25
25
  AUTHZPAAS_MODULE_OPTIONS: () => AUTHZPAAS_MODULE_OPTIONS,
26
26
  AbilityFactory: () => AbilityFactory,
27
- AuthZPaasExceptionFilter: () => AuthZPaasExceptionFilter,
28
27
  AuthZPaasGuard: () => AuthZPaasGuard,
29
28
  AuthZPaasModule: () => AuthZPaasModule,
30
29
  CACHE_CONFIG_TOKEN: () => CACHE_CONFIG_TOKEN,
@@ -40,13 +39,12 @@ __export(index_exports, {
40
39
  PermissionDeniedType: () => PermissionDeniedType,
41
40
  PermissionService: () => PermissionService,
42
41
  ROLES_KEY: () => ROLES_KEY,
43
- ROLE_SUBJECT: () => ROLE_SUBJECT,
44
- RolesMiddleware: () => RolesMiddleware
42
+ ROLE_SUBJECT: () => ROLE_SUBJECT
45
43
  });
46
44
  module.exports = __toCommonJS(index_exports);
47
45
 
48
46
  // src/authzpaas.module.ts
49
- var import_common11 = require("@nestjs/common");
47
+ var import_common9 = require("@nestjs/common");
50
48
  var import_core4 = require("@nestjs/core");
51
49
 
52
50
  // src/services/permission.service.ts
@@ -344,16 +342,13 @@ var PermissionService = class _PermissionService {
344
342
  */
345
343
  async fetchFromApi(requestDto) {
346
344
  const { timeout = 5e3 } = this.apiConfig || {};
347
- const { baseUrl, userId, appId, cookies, csrfToken } = requestDto;
348
- const url = `${baseUrl}/api/apps/${appId}/management/permissions/roles`;
345
+ const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;
346
+ const url = `${baseUrl}/spark/app/${appId}/runtime/api/v1/permissions/roles`;
349
347
  const requestHeaders = {
350
348
  "Content-Type": "application/json"
351
349
  };
352
- if (cookies) {
353
- const cookieString = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
354
- if (cookieString) {
355
- requestHeaders.Cookie = cookieString;
356
- }
350
+ if (cookie) {
351
+ requestHeaders.Cookie = cookie;
357
352
  }
358
353
  if (csrfToken) {
359
354
  requestHeaders["X-Suda-Csrf-Token"] = csrfToken;
@@ -362,7 +357,8 @@ var PermissionService = class _PermissionService {
362
357
  const timeoutId = setTimeout(() => controller.abort(), timeout);
363
358
  try {
364
359
  const response = await fetch(url, {
365
- method: "GET",
360
+ method: "POST",
361
+ credentials: "include",
366
362
  headers: requestHeaders,
367
363
  signal: controller.signal
368
364
  });
@@ -378,12 +374,13 @@ var PermissionService = class _PermissionService {
378
374
  const data = await response.json();
379
375
  return {
380
376
  userId,
381
- roles: data.role_list || [],
377
+ roles: data.data?.roleList || [],
382
378
  // TODO: 基于权限点位设置能力
383
379
  // permissions: data.permissions || [],
384
380
  fetchedAt: /* @__PURE__ */ new Date()
385
381
  };
386
382
  } catch (error) {
383
+ console.log("error", error);
387
384
  clearTimeout(timeoutId);
388
385
  let err = error;
389
386
  if (error.name === "AbortError") {
@@ -426,14 +423,22 @@ var PermissionService = class _PermissionService {
426
423
  * @returns 用户权限数据
427
424
  * @throws PermissionDeniedException 当角色不满足时
428
425
  */
429
- async checkRoles(requirement, userContext) {
426
+ async checkRoles(requirement, userContext, cookie, csrfToken) {
430
427
  const userId = userContext?.userId || ANONYMOUS_USER_ID;
428
+ if (!csrfToken) {
429
+ throw new PermissionDeniedException({
430
+ cause: new Error("CSRF token is required"),
431
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
432
+ message: "CSRF token is required"
433
+ }, import_common7.HttpStatus.BAD_REQUEST);
434
+ }
431
435
  const permissionData = await this.getUserPermissions({
436
+ // FIXME: 使用 request 的 base url
432
437
  baseUrl: userContext?.baseUrl || "",
433
438
  userId,
434
439
  appId: userContext?.appId || "",
435
- cookies: userContext?.cookies || {},
436
- csrfToken: userContext?.csrfToken || ""
440
+ csrfToken,
441
+ cookie
437
442
  });
438
443
  if (!permissionData) {
439
444
  throw new PermissionDeniedException({
@@ -492,22 +497,20 @@ var AuthZPaasGuard = class {
492
497
  async canActivate(context) {
493
498
  const http = context.switchToHttp();
494
499
  const request = http.getRequest();
500
+ const cookie = request.headers.cookie;
501
+ const csrfToken = request.csrfToken;
502
+ const baseUrl = `${request.protocol}://${request.get("host")}`;
495
503
  const userContext = request.userContext;
504
+ userContext.baseUrl = baseUrl;
496
505
  const checkRoleRequirement = this.reflector.getAllAndOverride(ROLES_KEY, [
497
506
  context.getHandler(),
498
507
  context.getClass()
499
508
  ]);
500
509
  if (checkRoleRequirement) {
501
- await this.checkRoleRequirement(checkRoleRequirement, userContext);
510
+ await this.permissionService.checkRoles(checkRoleRequirement, userContext, cookie, csrfToken);
502
511
  }
503
512
  return true;
504
513
  }
505
- /**
506
- * 检查角色要求
507
- */
508
- async checkRoleRequirement(requirement, userContext) {
509
- return this.permissionService.checkRoles(requirement, userContext);
510
- }
511
514
  };
512
515
  AuthZPaasGuard = _ts_decorate5([
513
516
  (0, import_common8.Injectable)(),
@@ -518,8 +521,7 @@ AuthZPaasGuard = _ts_decorate5([
518
521
  ])
519
522
  ], AuthZPaasGuard);
520
523
 
521
- // src/middlewares/roles.middleware.ts
522
- var import_common9 = require("@nestjs/common");
524
+ // src/authzpaas.module.ts
523
525
  function _ts_decorate6(decorators, target, key, desc) {
524
526
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
525
527
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -527,114 +529,12 @@ function _ts_decorate6(decorators, target, key, desc) {
527
529
  return c > 3 && r && Object.defineProperty(target, key, r), r;
528
530
  }
529
531
  __name(_ts_decorate6, "_ts_decorate");
530
- function _ts_metadata4(k, v) {
531
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
532
- }
533
- __name(_ts_metadata4, "_ts_metadata");
534
- var RolesMiddleware = class _RolesMiddleware {
535
- static {
536
- __name(this, "RolesMiddleware");
537
- }
538
- permissionService;
539
- logger = new import_common9.Logger(_RolesMiddleware.name);
540
- constructor(permissionService) {
541
- this.permissionService = permissionService;
542
- }
543
- async use(req, _res, next) {
544
- try {
545
- const baseUrl = `${req.protocol}://${req.get("host")}`;
546
- const userId = req.userContext?.userId ?? "";
547
- const appId = req.userContext?.appId ?? "";
548
- const cookies = req.cookies ?? {};
549
- const csrfToken = req.headers["x-suda-csrf-token"] ?? "";
550
- this.logger.debug(`Fetching roles for user: ${userId}`);
551
- const permissionData = await this.permissionService.getUserPermissions({
552
- baseUrl,
553
- userId,
554
- appId,
555
- cookies,
556
- csrfToken
557
- });
558
- let roles;
559
- if (permissionData) {
560
- roles = permissionData.roles;
561
- }
562
- req.userContext.baseUrl = baseUrl;
563
- req.userContext.userRoles = roles;
564
- req.userContext.userId = userId;
565
- req.userContext.cookies = cookies;
566
- req.userContext.csrfToken = csrfToken;
567
- this.logger.debug(`User ${userId} roles loaded: [${roles?.join(", ")}]`);
568
- next();
569
- } catch (error) {
570
- this.logger.warn(`Failed to fetch roles for request: ${error instanceof Error ? error.message : "Unknown error"}`);
571
- next();
572
- }
573
- }
574
- };
575
- RolesMiddleware = _ts_decorate6([
576
- (0, import_common9.Injectable)(),
577
- _ts_metadata4("design:type", Function),
578
- _ts_metadata4("design:paramtypes", [
579
- typeof PermissionService === "undefined" ? Object : PermissionService
580
- ])
581
- ], RolesMiddleware);
582
-
583
- // src/filters/authzpaas-exception.filter.ts
584
- var import_common10 = require("@nestjs/common");
585
- function _ts_decorate7(decorators, target, key, desc) {
586
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
587
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
588
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
589
- return c > 3 && r && Object.defineProperty(target, key, r), r;
590
- }
591
- __name(_ts_decorate7, "_ts_decorate");
592
- var AuthZPaasExceptionFilter = class {
593
- static {
594
- __name(this, "AuthZPaasExceptionFilter");
595
- }
596
- catch(exception, host) {
597
- const ctx = host.switchToHttp();
598
- const response = ctx.getResponse();
599
- const status = exception.getStatus();
600
- const exceptionResponse = exception.getResponse();
601
- const errorResponse = {
602
- statusCode: status,
603
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
604
- cause: exception.cause,
605
- type: exception.type,
606
- message: typeof exceptionResponse === "string" ? exceptionResponse : exceptionResponse.message || "\u6743\u9650\u4E0D\u8DB3"
607
- };
608
- if (exception.details.requiredRoles) {
609
- errorResponse.requiredRoles = exception.details.requiredRoles;
610
- }
611
- if (exception.details.requiredPermissions) {
612
- errorResponse.requiredPermissions = exception.details.requiredPermissions;
613
- }
614
- if (exception.details.metadata) {
615
- errorResponse.metadata = exception.details.metadata;
616
- }
617
- response.status(status).json(errorResponse);
618
- }
619
- };
620
- AuthZPaasExceptionFilter = _ts_decorate7([
621
- (0, import_common10.Catch)(PermissionDeniedException)
622
- ], AuthZPaasExceptionFilter);
623
-
624
- // src/authzpaas.module.ts
625
- function _ts_decorate8(decorators, target, key, desc) {
626
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
627
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
628
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
629
- return c > 3 && r && Object.defineProperty(target, key, r), r;
630
- }
631
- __name(_ts_decorate8, "_ts_decorate");
632
532
  var AuthZPaasModule = class _AuthZPaasModule {
633
533
  static {
634
534
  __name(this, "AuthZPaasModule");
635
535
  }
636
536
  static forRoot(options) {
637
- const { permissionApi } = options;
537
+ const { permissionApi = {} } = options || {};
638
538
  return {
639
539
  module: _AuthZPaasModule,
640
540
  global: true,
@@ -657,21 +557,15 @@ var AuthZPaasModule = class _AuthZPaasModule {
657
557
  PermissionService,
658
558
  AbilityFactory,
659
559
  AuthZPaasGuard,
660
- RolesMiddleware,
661
560
  // 守卫提供者
662
561
  {
663
562
  provide: import_core4.APP_GUARD,
664
563
  useClass: AuthZPaasGuard
665
- },
666
- {
667
- provide: import_core4.APP_FILTER,
668
- useClass: AuthZPaasExceptionFilter
669
564
  }
670
565
  ],
671
566
  exports: [
672
567
  PermissionService,
673
- AbilityFactory,
674
- RolesMiddleware
568
+ AbilityFactory
675
569
  ]
676
570
  };
677
571
  }
@@ -735,31 +629,25 @@ var AuthZPaasModule = class _AuthZPaasModule {
735
629
  PermissionService,
736
630
  AbilityFactory,
737
631
  AuthZPaasGuard,
738
- RolesMiddleware,
739
632
  // 守卫提供者
740
633
  {
741
634
  provide: import_core4.APP_GUARD,
742
635
  useClass: AuthZPaasGuard
743
- },
744
- {
745
- provide: import_core4.APP_FILTER,
746
- useClass: AuthZPaasExceptionFilter
747
636
  }
748
637
  ],
749
638
  exports: [
750
639
  PermissionService,
751
- AbilityFactory,
752
- RolesMiddleware
640
+ AbilityFactory
753
641
  ]
754
642
  };
755
643
  }
756
644
  };
757
- AuthZPaasModule = _ts_decorate8([
758
- (0, import_common11.Module)({})
645
+ AuthZPaasModule = _ts_decorate6([
646
+ (0, import_common9.Module)({})
759
647
  ], AuthZPaasModule);
760
648
 
761
649
  // src/decorators/can-role.decorator.ts
762
- var import_common12 = require("@nestjs/common");
650
+ var import_common10 = require("@nestjs/common");
763
651
  var CanRole = /* @__PURE__ */ __name((role, and = false) => {
764
652
  let requirement;
765
653
  if (!Array.isArray(role) && typeof role === "string") {
@@ -777,14 +665,13 @@ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
777
665
  } else {
778
666
  throw new Error("Invalid CanRole parameter: " + JSON.stringify(role));
779
667
  }
780
- return (0, import_common12.SetMetadata)(ROLES_KEY, requirement);
668
+ return (0, import_common10.SetMetadata)(ROLES_KEY, requirement);
781
669
  }, "CanRole");
782
670
  // Annotate the CommonJS export names for ESM import in node:
783
671
  0 && (module.exports = {
784
672
  ANONYMOUS_USER_ID,
785
673
  AUTHZPAAS_MODULE_OPTIONS,
786
674
  AbilityFactory,
787
- AuthZPaasExceptionFilter,
788
675
  AuthZPaasGuard,
789
676
  AuthZPaasModule,
790
677
  CACHE_CONFIG_TOKEN,
@@ -800,7 +687,6 @@ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
800
687
  PermissionDeniedType,
801
688
  PermissionService,
802
689
  ROLES_KEY,
803
- ROLE_SUBJECT,
804
- RolesMiddleware
690
+ ROLE_SUBJECT
805
691
  });
806
692
  //# sourceMappingURL=index.cjs.map