@lark-apaas/nestjs-authzpaas 0.1.0-alpha.4 → 0.1.0-alpha.40

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/dist/index.cjs CHANGED
@@ -24,18 +24,13 @@ __export(index_exports, {
24
24
  ANONYMOUS_USER_ID: () => ANONYMOUS_USER_ID,
25
25
  AUTHZPAAS_MODULE_OPTIONS: () => AUTHZPAAS_MODULE_OPTIONS,
26
26
  AbilityFactory: () => AbilityFactory,
27
+ AuthZPaasGuard: () => AuthZPaasGuard,
27
28
  AuthZPaasModule: () => AuthZPaasModule,
28
- CACHE_CONFIG_TOKEN: () => CACHE_CONFIG_TOKEN,
29
29
  CanRole: () => CanRole,
30
- DEFAULT_LOGIN_PATH: () => DEFAULT_LOGIN_PATH,
31
- ENABLE_MOCK_ROLE_KEY: () => ENABLE_MOCK_ROLE_KEY,
32
- ENVIRONMENT_KEY: () => ENVIRONMENT_KEY,
33
- MOCK_ROLES_COOKIE_KEY: () => MOCK_ROLES_COOKIE_KEY,
34
- NEED_LOGIN_KEY: () => NEED_LOGIN_KEY2,
35
- PERMISSIONS_KEY: () => PERMISSIONS_KEY,
36
30
  PERMISSION_API_CONFIG_TOKEN: () => PERMISSION_API_CONFIG_TOKEN,
37
31
  PermissionDeniedException: () => PermissionDeniedException,
38
32
  PermissionDeniedType: () => PermissionDeniedType,
33
+ PermissionService: () => PermissionService,
39
34
  ROLES_KEY: () => ROLES_KEY,
40
35
  ROLE_SUBJECT: () => ROLE_SUBJECT
41
36
  });
@@ -60,7 +55,7 @@ var __name2 = /* @__PURE__ */ __name((target, value) => __defProp2(target, "name
60
55
  value,
61
56
  configurable: true
62
57
  }), "__name");
63
- var AUTHNPAAS_MODULE_OPTIONS = Symbol("AUTHNPAAS_MODULE_OPTIONS");
58
+ var AUTHNPAAS_MODULE_OPTIONS = /* @__PURE__ */ Symbol("AUTHNPAAS_MODULE_OPTIONS");
64
59
  var NEED_LOGIN_KEY = "authnpaas:needLogin";
65
60
  function _ts_decorate(decorators, target, key, desc) {
66
61
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -96,8 +91,8 @@ var AuthNPaasGuard = class {
96
91
  context.getClass()
97
92
  ]);
98
93
  if (needLoginMeta && !userId && loginUrl) {
99
- response.redirect(302, loginUrl);
100
- return false;
94
+ response.setHeader("x-login-url", loginUrl);
95
+ throw new import_common2.UnauthorizedException("\u672A\u767B\u5F55");
101
96
  }
102
97
  return true;
103
98
  }
@@ -159,16 +154,9 @@ var Public = /* @__PURE__ */ __name2(() => (0, import_common3.SetMetadata)(IS_PU
159
154
 
160
155
  // src/const.ts
161
156
  var ANONYMOUS_USER_ID = "anonymous_user_id";
162
- var PERMISSION_API_CONFIG_TOKEN = Symbol("PERMISSION_API_CONFIG");
163
- var CACHE_CONFIG_TOKEN = Symbol("CACHE_CONFIG");
164
- var AUTHZPAAS_MODULE_OPTIONS = Symbol("AUTHZPAAS_MODULE_OPTIONS");
157
+ var PERMISSION_API_CONFIG_TOKEN = /* @__PURE__ */ Symbol("PERMISSION_API_CONFIG");
158
+ var AUTHZPAAS_MODULE_OPTIONS = /* @__PURE__ */ Symbol("AUTHZPAAS_MODULE_OPTIONS");
165
159
  var ROLES_KEY = "authzpaas:roles";
166
- var PERMISSIONS_KEY = "authzpaas:permissions";
167
- var ENVIRONMENT_KEY = "authzpaas:environment";
168
- var NEED_LOGIN_KEY2 = "authzpaas:needLogin";
169
- var DEFAULT_LOGIN_PATH = "/login";
170
- var MOCK_ROLES_COOKIE_KEY = "mockRoles";
171
- var ENABLE_MOCK_ROLE_KEY = "__authzpaas_enableMockRole";
172
160
 
173
161
  // src/casl/ability.factory.ts
174
162
  var import_common5 = require("@nestjs/common");
@@ -250,15 +238,12 @@ var PermissionDeniedException = class _PermissionDeniedException extends import_
250
238
  /**
251
239
  * 创建角色不足异常
252
240
  */
253
- static roleRequired(requiredRoles, and = false) {
254
- const message = and ? `\u9700\u8981\u6240\u6709\u89D2\u8272: ${requiredRoles.join(", ")}` : `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
241
+ static roleRequired(requiredRoles) {
242
+ const message = `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
255
243
  return new _PermissionDeniedException({
256
244
  type: "ROLE_REQUIRED",
257
245
  message,
258
- requiredRoles,
259
- metadata: {
260
- and
261
- }
246
+ requiredRoles
262
247
  });
263
248
  }
264
249
  /**
@@ -286,6 +271,8 @@ var PermissionDeniedException = class _PermissionDeniedException extends import_
286
271
  };
287
272
 
288
273
  // src/services/permission.service.ts
274
+ var import_nestjs_common = require("@lark-apaas/nestjs-common");
275
+ var import_nestjs_common2 = require("@lark-apaas/nestjs-common");
289
276
  function _ts_decorate4(decorators, target, key, desc) {
290
277
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
291
278
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -303,62 +290,61 @@ function _ts_param(paramIndex, decorator) {
303
290
  };
304
291
  }
305
292
  __name(_ts_param, "_ts_param");
306
- var PermissionService = class _PermissionService {
293
+ var PermissionService = class {
307
294
  static {
308
295
  __name(this, "PermissionService");
309
296
  }
310
297
  apiConfig;
311
298
  abilityFactory;
312
- logger = new import_common7.Logger(_PermissionService.name);
313
- constructor(apiConfig, abilityFactory) {
299
+ client;
300
+ requestContextService;
301
+ constructor(apiConfig, abilityFactory, client, requestContextService) {
314
302
  this.apiConfig = apiConfig;
315
303
  this.abilityFactory = abilityFactory;
304
+ this.client = client;
305
+ this.requestContextService = requestContextService;
316
306
  }
317
307
  /**
318
308
  * 获取用户权限数据
319
309
  */
320
- async getUserPermissions(requestDto) {
321
- const requestPromise = (async () => {
322
- const userId = requestDto.userId || ANONYMOUS_USER_ID;
323
- try {
324
- const permissionData = await this.fetchFromApi(requestDto);
325
- const dataWithTimestamp = {
326
- ...permissionData,
327
- fetchedAt: /* @__PURE__ */ new Date()
328
- };
329
- return dataWithTimestamp;
330
- } catch (error) {
331
- this.logger.error(`Failed to fetch permissions for user ${userId}:`, error);
332
- throw error;
333
- }
334
- })();
335
- return requestPromise;
310
+ async getUserPermissions({ laneId }) {
311
+ const permissionData = await this.fetchFromApi({
312
+ laneId
313
+ });
314
+ const dataWithTimestamp = {
315
+ ...permissionData,
316
+ fetchedAt: /* @__PURE__ */ new Date()
317
+ };
318
+ return dataWithTimestamp;
336
319
  }
337
320
  /**
338
321
  * 从 API 获取权限数据
339
322
  * 内置实现,用户无需配置
340
323
  */
341
- async fetchFromApi(requestDto) {
324
+ async fetchFromApi({ laneId }) {
342
325
  const { timeout = 5e3 } = this.apiConfig || {};
343
- const { baseUrl, userId, appId, cookies, csrfToken } = requestDto;
344
- const url = `${baseUrl}/api/v1/permission/apps/${appId}/roles`;
326
+ const { appId = "", userId = "" } = this.requestContextService.getContext() || {};
327
+ if (!appId) {
328
+ throw new PermissionDeniedException({
329
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
330
+ message: "appId is empty"
331
+ }, import_common7.HttpStatus.BAD_REQUEST);
332
+ }
333
+ const url = `/app/${appId}/inner/api/v1/permissions/roles`;
345
334
  const requestHeaders = {
346
- "Content-Type": "application/json"
335
+ "Content-Type": "application/json",
336
+ // 透传 laneId 到权限服务
337
+ ...laneId ? {
338
+ "x-tt-env": laneId || ""
339
+ } : {}
347
340
  };
348
- if (cookies) {
349
- const cookieString = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
350
- if (cookieString) {
351
- requestHeaders.Cookie = cookieString;
352
- }
353
- }
354
- if (csrfToken) {
355
- requestHeaders["X-Suda-Csrf-Token"] = csrfToken;
356
- }
357
341
  const controller = new AbortController();
358
342
  const timeoutId = setTimeout(() => controller.abort(), timeout);
359
343
  try {
360
- const response = await fetch(url, {
361
- method: "GET",
344
+ const response = await this.client.post(url, {
345
+ userID: userId || ""
346
+ }, {
347
+ credentials: "include",
362
348
  headers: requestHeaders,
363
349
  signal: controller.signal
364
350
  });
@@ -371,10 +357,22 @@ var PermissionService = class _PermissionService {
371
357
  message: error.message
372
358
  }, import_common7.HttpStatus.INTERNAL_SERVER_ERROR);
373
359
  }
374
- const data = await response.json();
360
+ let data;
361
+ let responseText = "";
362
+ try {
363
+ responseText = await response.text();
364
+ data = JSON.parse(responseText);
365
+ } catch (jsonError) {
366
+ const error = new Error(`Permission API returned invalid JSON: ${responseText}`);
367
+ throw new PermissionDeniedException({
368
+ cause: error,
369
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
370
+ message: error.message
371
+ }, import_common7.HttpStatus.INTERNAL_SERVER_ERROR);
372
+ }
375
373
  return {
376
374
  userId,
377
- roles: data.role_list || [],
375
+ roles: data.data?.roleList || [],
378
376
  // TODO: 基于权限点位设置能力
379
377
  // permissions: data.permissions || [],
380
378
  fetchedAt: /* @__PURE__ */ new Date()
@@ -418,18 +416,14 @@ var PermissionService = class _PermissionService {
418
416
  * 检查角色要求
419
417
  * 使用 CASL Ability 统一鉴权方式
420
418
  * @param requirement 角色要求
421
- * @param userId 用户ID,匿名用户时为空
422
- * @returns 用户权限数据
423
- * @throws PermissionDeniedException 当角色不满足时
419
+ * @param laneId 环境ID
420
+ * @returns 用户权限检查结果,包含结果和详细信息
421
+ * @throws PermissionDeniedException 当权限数据获取失败时
424
422
  */
425
- async checkRoles(requirement, userContext) {
426
- const userId = userContext?.userId || ANONYMOUS_USER_ID;
423
+ async checkRoles(requirement, laneId) {
424
+ const { userId = "" } = this.requestContextService.getContext() || {};
427
425
  const permissionData = await this.getUserPermissions({
428
- baseUrl: userContext?.baseUrl || "",
429
- userId,
430
- appId: userContext?.appId || "",
431
- cookies: userContext?.cookies || {},
432
- csrfToken: userContext?.csrfToken || ""
426
+ laneId
433
427
  });
434
428
  if (!permissionData) {
435
429
  throw new PermissionDeniedException({
@@ -439,31 +433,43 @@ var PermissionService = class _PermissionService {
439
433
  }, import_common7.HttpStatus.BAD_REQUEST);
440
434
  }
441
435
  const ability = this.abilityFactory.createForUser(permissionData);
442
- const { roles, and } = requirement;
443
- const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
444
- const hasRole = and ? checkResults.every((result) => result) : checkResults.some((result) => result);
436
+ const { roles } = requirement;
437
+ if (!roles || roles.length === 0) {
438
+ return {
439
+ result: true
440
+ };
441
+ }
442
+ const hasRole = roles.some((role) => ability.can(role, ROLE_SUBJECT));
445
443
  if (!hasRole) {
446
444
  const userRoles = permissionData.roles;
447
- this.logger.warn(`\u89D2\u8272\u68C0\u67E5\u5931\u8D25: \u7528\u6237 ${userId}, \u7528\u6237\u89D2\u8272 [${userRoles.join(", ")}], \u9700\u8981 [${roles.join(", ")}]`);
448
- throw PermissionDeniedException.roleRequired(roles, and);
445
+ return {
446
+ result: false,
447
+ details: `\u7528\u6237 ${userId}, \u7528\u6237\u89D2\u8272 [${userRoles.join(", ")}], \u9700\u8981 [${roles.join(", ")}]`
448
+ };
449
449
  }
450
- return permissionData;
450
+ return {
451
+ result: true
452
+ };
451
453
  }
452
454
  };
453
455
  PermissionService = _ts_decorate4([
454
456
  (0, import_common7.Injectable)(),
455
457
  Public(),
456
458
  _ts_param(0, (0, import_common7.Inject)(PERMISSION_API_CONFIG_TOKEN)),
459
+ _ts_param(2, (0, import_common7.Inject)(import_nestjs_common.PLATFORM_HTTP_CLIENT)),
457
460
  _ts_metadata2("design:type", Function),
458
461
  _ts_metadata2("design:paramtypes", [
459
462
  typeof PermissionApiConfig === "undefined" ? Object : PermissionApiConfig,
460
- typeof AbilityFactory === "undefined" ? Object : AbilityFactory
463
+ typeof AbilityFactory === "undefined" ? Object : AbilityFactory,
464
+ typeof import_nestjs_common.PlatformHttpClient === "undefined" ? Object : import_nestjs_common.PlatformHttpClient,
465
+ typeof import_nestjs_common2.RequestContextService === "undefined" ? Object : import_nestjs_common2.RequestContextService
461
466
  ])
462
467
  ], PermissionService);
463
468
 
464
469
  // src/guards/authzpaas.guard.ts
465
470
  var import_common8 = require("@nestjs/common");
466
471
  var import_core3 = require("@nestjs/core");
472
+ var import_nestjs_common3 = require("@lark-apaas/nestjs-common");
467
473
  function _ts_decorate5(decorators, target, key, desc) {
468
474
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
469
475
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -475,42 +481,101 @@ function _ts_metadata3(k, v) {
475
481
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
476
482
  }
477
483
  __name(_ts_metadata3, "_ts_metadata");
478
- var AuthZPaasGuard = class {
484
+ function _ts_param2(paramIndex, decorator) {
485
+ return function(target, key) {
486
+ decorator(target, key, paramIndex);
487
+ };
488
+ }
489
+ __name(_ts_param2, "_ts_param");
490
+ var AuthZPaasGuard = class _AuthZPaasGuard {
479
491
  static {
480
492
  __name(this, "AuthZPaasGuard");
481
493
  }
482
494
  reflector;
483
495
  permissionService;
484
- constructor(reflector, permissionService) {
496
+ obs;
497
+ constructor(reflector, permissionService, obs) {
485
498
  this.reflector = reflector;
486
499
  this.permissionService = permissionService;
500
+ this.obs = obs;
501
+ }
502
+ logger = new import_common8.Logger(_AuthZPaasGuard.name);
503
+ /**
504
+ * 验证角色要求是否有效
505
+ * @param requirements 角色要求
506
+ * @returns 是否有效
507
+ */
508
+ isValidRoleRequirement(requirements) {
509
+ return Boolean(requirements && requirements.roles && requirements.roles.length > 0);
487
510
  }
488
511
  async canActivate(context) {
489
512
  const http = context.switchToHttp();
490
513
  const request = http.getRequest();
514
+ const laneId = request.headers["x-tt-env"];
515
+ const baseUrl = `${request.protocol}://${request.get("host")}`;
491
516
  const userContext = request.userContext;
517
+ userContext.baseUrl = baseUrl;
492
518
  const checkRoleRequirement = this.reflector.getAllAndOverride(ROLES_KEY, [
493
519
  context.getHandler(),
494
520
  context.getClass()
495
521
  ]);
496
- if (checkRoleRequirement) {
497
- await this.checkRoleRequirement(checkRoleRequirement, userContext);
522
+ if (this.isValidRoleRequirement(checkRoleRequirement)) {
523
+ const spanName = checkRoleRequirement.roles.join(" or ");
524
+ return this.obs.trace(spanName, async (span) => {
525
+ span.setAttributes({
526
+ module: "permission"
527
+ });
528
+ const startTime = Date.now();
529
+ try {
530
+ const checkResult = await this.permissionService.checkRoles(checkRoleRequirement, laneId);
531
+ const endTime = Date.now();
532
+ if (!checkResult.result) {
533
+ this.logger.warn(JSON.stringify({
534
+ role: spanName,
535
+ duration_ms: endTime - startTime,
536
+ result: checkResult.result ? "has_auth" : "no_auth",
537
+ result_detail: checkResult.details
538
+ }), {
539
+ source_type: "platform",
540
+ paas_attributes_module: "permission"
541
+ });
542
+ } else {
543
+ this.logger.log(JSON.stringify({
544
+ role: spanName,
545
+ duration_ms: endTime - startTime,
546
+ result: checkResult.result ? "has_auth" : "no_auth"
547
+ }), {
548
+ source_type: "platform",
549
+ paas_attributes_module: "permission"
550
+ });
551
+ }
552
+ return checkResult.result;
553
+ } catch (error) {
554
+ const endTime = Date.now();
555
+ this.logger.error(JSON.stringify({
556
+ role: spanName,
557
+ duration_ms: endTime - startTime,
558
+ result: "",
559
+ error_message: error.message
560
+ }), {
561
+ source_type: "platform",
562
+ paas_attributes_module: "permission"
563
+ });
564
+ throw error;
565
+ }
566
+ });
498
567
  }
499
568
  return true;
500
569
  }
501
- /**
502
- * 检查角色要求
503
- */
504
- async checkRoleRequirement(requirement, userContext) {
505
- return this.permissionService.checkRoles(requirement, userContext);
506
- }
507
570
  };
508
571
  AuthZPaasGuard = _ts_decorate5([
509
572
  (0, import_common8.Injectable)(),
573
+ _ts_param2(2, (0, import_common8.Inject)(import_nestjs_common3.OBSERVABLE_SERVICE)),
510
574
  _ts_metadata3("design:type", Function),
511
575
  _ts_metadata3("design:paramtypes", [
512
576
  typeof import_core3.Reflector === "undefined" ? Object : import_core3.Reflector,
513
- typeof PermissionService === "undefined" ? Object : PermissionService
577
+ typeof PermissionService === "undefined" ? Object : PermissionService,
578
+ typeof import_nestjs_common3.ObservableService === "undefined" ? Object : import_nestjs_common3.ObservableService
514
579
  ])
515
580
  ], AuthZPaasGuard);
516
581
 
@@ -641,19 +706,17 @@ AuthZPaasModule = _ts_decorate6([
641
706
 
642
707
  // src/decorators/can-role.decorator.ts
643
708
  var import_common10 = require("@nestjs/common");
644
- var CanRole = /* @__PURE__ */ __name((role, and = false) => {
709
+ var CanRole = /* @__PURE__ */ __name((role) => {
645
710
  let requirement;
646
711
  if (!Array.isArray(role) && typeof role === "string") {
647
712
  requirement = {
648
713
  roles: [
649
714
  role
650
- ],
651
- and
715
+ ]
652
716
  };
653
- } else if (Array.isArray(role) && role.some((role2) => typeof role2 === "string")) {
717
+ } else if (Array.isArray(role) && role.every((role2) => typeof role2 === "string")) {
654
718
  requirement = {
655
- roles: role,
656
- and
719
+ roles: role
657
720
  };
658
721
  } else {
659
722
  throw new Error("Invalid CanRole parameter: " + JSON.stringify(role));
@@ -665,18 +728,13 @@ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
665
728
  ANONYMOUS_USER_ID,
666
729
  AUTHZPAAS_MODULE_OPTIONS,
667
730
  AbilityFactory,
731
+ AuthZPaasGuard,
668
732
  AuthZPaasModule,
669
- CACHE_CONFIG_TOKEN,
670
733
  CanRole,
671
- DEFAULT_LOGIN_PATH,
672
- ENABLE_MOCK_ROLE_KEY,
673
- ENVIRONMENT_KEY,
674
- MOCK_ROLES_COOKIE_KEY,
675
- NEED_LOGIN_KEY,
676
- PERMISSIONS_KEY,
677
734
  PERMISSION_API_CONFIG_TOKEN,
678
735
  PermissionDeniedException,
679
736
  PermissionDeniedType,
737
+ PermissionService,
680
738
  ROLES_KEY,
681
739
  ROLE_SUBJECT
682
740
  });