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

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,6 +24,7 @@ __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
29
  CACHE_CONFIG_TOKEN: () => CACHE_CONFIG_TOKEN,
29
30
  CanRole: () => CanRole,
@@ -36,6 +37,7 @@ __export(index_exports, {
36
37
  PERMISSION_API_CONFIG_TOKEN: () => PERMISSION_API_CONFIG_TOKEN,
37
38
  PermissionDeniedException: () => PermissionDeniedException,
38
39
  PermissionDeniedType: () => PermissionDeniedType,
40
+ PermissionService: () => PermissionService,
39
41
  ROLES_KEY: () => ROLES_KEY,
40
42
  ROLE_SUBJECT: () => ROLE_SUBJECT
41
43
  });
@@ -340,16 +342,13 @@ var PermissionService = class _PermissionService {
340
342
  */
341
343
  async fetchFromApi(requestDto) {
342
344
  const { timeout = 5e3 } = this.apiConfig || {};
343
- const { baseUrl, userId, appId, cookies, csrfToken } = requestDto;
345
+ const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;
344
346
  const url = `${baseUrl}/api/v1/permission/apps/${appId}/roles`;
345
347
  const requestHeaders = {
346
348
  "Content-Type": "application/json"
347
349
  };
348
- if (cookies) {
349
- const cookieString = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
350
- if (cookieString) {
351
- requestHeaders.Cookie = cookieString;
352
- }
350
+ if (cookie) {
351
+ requestHeaders.Cookie = cookie;
353
352
  }
354
353
  if (csrfToken) {
355
354
  requestHeaders["X-Suda-Csrf-Token"] = csrfToken;
@@ -359,6 +358,7 @@ var PermissionService = class _PermissionService {
359
358
  try {
360
359
  const response = await fetch(url, {
361
360
  method: "GET",
361
+ credentials: "same-origin",
362
362
  headers: requestHeaders,
363
363
  signal: controller.signal
364
364
  });
@@ -374,12 +374,13 @@ var PermissionService = class _PermissionService {
374
374
  const data = await response.json();
375
375
  return {
376
376
  userId,
377
- roles: data.role_list || [],
377
+ roles: data.data?.role_list || [],
378
378
  // TODO: 基于权限点位设置能力
379
379
  // permissions: data.permissions || [],
380
380
  fetchedAt: /* @__PURE__ */ new Date()
381
381
  };
382
382
  } catch (error) {
383
+ console.log("error", error);
383
384
  clearTimeout(timeoutId);
384
385
  let err = error;
385
386
  if (error.name === "AbortError") {
@@ -422,14 +423,15 @@ var PermissionService = class _PermissionService {
422
423
  * @returns 用户权限数据
423
424
  * @throws PermissionDeniedException 当角色不满足时
424
425
  */
425
- async checkRoles(requirement, userContext) {
426
+ async checkRoles(requirement, userContext, cookie) {
426
427
  const userId = userContext?.userId || ANONYMOUS_USER_ID;
427
428
  const permissionData = await this.getUserPermissions({
429
+ // FIXME: 使用 request 的 base url
428
430
  baseUrl: userContext?.baseUrl || "",
429
431
  userId,
430
432
  appId: userContext?.appId || "",
431
- cookies: userContext?.cookies || {},
432
- csrfToken: userContext?.csrfToken || ""
433
+ csrfToken: userContext?.csrfToken || "",
434
+ cookie
433
435
  });
434
436
  if (!permissionData) {
435
437
  throw new PermissionDeniedException({
@@ -488,22 +490,19 @@ var AuthZPaasGuard = class {
488
490
  async canActivate(context) {
489
491
  const http = context.switchToHttp();
490
492
  const request = http.getRequest();
493
+ const cookie = request.headers.cookie;
494
+ const baseUrl = `${request.protocol}://${request.get("host")}`;
491
495
  const userContext = request.userContext;
496
+ userContext.baseUrl = baseUrl;
492
497
  const checkRoleRequirement = this.reflector.getAllAndOverride(ROLES_KEY, [
493
498
  context.getHandler(),
494
499
  context.getClass()
495
500
  ]);
496
501
  if (checkRoleRequirement) {
497
- await this.checkRoleRequirement(checkRoleRequirement, userContext);
502
+ await this.permissionService.checkRoles(checkRoleRequirement, userContext, cookie);
498
503
  }
499
504
  return true;
500
505
  }
501
- /**
502
- * 检查角色要求
503
- */
504
- async checkRoleRequirement(requirement, userContext) {
505
- return this.permissionService.checkRoles(requirement, userContext);
506
- }
507
506
  };
508
507
  AuthZPaasGuard = _ts_decorate5([
509
508
  (0, import_common8.Injectable)(),
@@ -665,6 +664,7 @@ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
665
664
  ANONYMOUS_USER_ID,
666
665
  AUTHZPAAS_MODULE_OPTIONS,
667
666
  AbilityFactory,
667
+ AuthZPaasGuard,
668
668
  AuthZPaasModule,
669
669
  CACHE_CONFIG_TOKEN,
670
670
  CanRole,
@@ -677,6 +677,7 @@ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
677
677
  PERMISSION_API_CONFIG_TOKEN,
678
678
  PermissionDeniedException,
679
679
  PermissionDeniedType,
680
+ PermissionService,
680
681
  ROLES_KEY,
681
682
  ROLE_SUBJECT
682
683
  });
@@ -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 {\n PermissionDeniedException,\n PermissionDeniedType,\n type PermissionDeniedDetails,\n} from './exceptions/permission-denied.exception';\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, cookies, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/api/v1/permission/apps/${appId}/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookies) {\n const cookieString = Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n if (cookieString) {\n requestHeaders.Cookie = cookieString;\n }\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: 'GET',\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 role_list?: string[];\n };\n\n return {\n userId,\n roles: data.role_list || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\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 ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n const permissionData = await this.getUserPermissions({\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n cookies: userContext?.cookies || {},\n csrfToken: userContext?.csrfToken || '',\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/** 缓存配置 Token */\nexport const CACHE_CONFIG_TOKEN = Symbol('CACHE_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/** 需要的权限元数据键 */\nexport const PERMISSIONS_KEY = 'authzpaas:permissions';\n\n/** 需要的环境元数据键 */\nexport const ENVIRONMENT_KEY = 'authzpaas:environment';\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authzpaas:needLogin';\n\n/** 模块选项:登录页路径默认值 */\nexport const DEFAULT_LOGIN_PATH = '/login';\n\n/** 角色模拟的 Cookie 键名 */\nexport const MOCK_ROLES_COOKIE_KEY = 'mockRoles';\n\nexport const ENABLE_MOCK_ROLE_KEY = '__authzpaas_enableMockRole';\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 { RoleRequirement, 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\n const userContext = request.userContext as UserContext;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n await this.checkRoleRequirement(checkRoleRequirement, userContext);\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 checkRoleRequirement(\n requirement: RoleRequirement,\n userContext?: UserContext\n ) {\n return this.permissionService.checkRoles(requirement, userContext);\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":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;wBAAAA;EAAA;;;;;;;;;;ACAA,IAAAC,iBAA4C;AAC5C,IAAAC,eAAqC;;;ACDrC,IAAAC,iBAAuD;;;ACAvD,oBAAsC;AACtC,kBAAqC;AEDrC,IAAAC,iBAA0D;AAE1D,IAAAC,eAA0B;ACF1B,IAAAD,iBAA4B;ACA5B,IAAAA,iBAA4B;;;;;;AHKrB,IAAME,2BAA2BC,OAAO,0BAAA;AAOxC,IAAMC,iBAAiB;;;;;;;;;;;;;;ACFvB,IAAMC,iBAAN,MAAMA;SAAAA;;;SAAAA;;;;EACX,YAAoBC,WAAsB;SAAtBA,YAAAA;EAAuB;EAE3C,MAAMC,YAAYC,SAA6C;AAC7D,UAAMC,OAAOD,QAAQE,aAAY;AACjC,UAAMC,UAAUF,KAAKG,WAAU;AAC/B,UAAMC,WAAWJ,KAAKK,YAAW;AAEjC,UAAM,EAAEC,QAAQC,SAAQ,IAAKL,QAAQM,eAAe,CAAC;AAGrD,UAAMC,gBAAgB,KAAKZ,UAAUa,kBAElCf,gBAAgB;MAACI,QAAQY,WAAU;MAAIZ,QAAQa,SAAQ;KAAG;AAG7D,QAAIH,iBAAiB,CAACH,UAAUC,UAAU;AACxCH,eAASS,SAAS,KAAKN,QAAAA;AACvB,aAAO;IACT;AAEA,WAAO;EACT;AACF;;;;;;;;;;;;;;;;AF1BO,IAAMO,kBAAN,MAAMA,iBAAAA;SAAAA;;;SAAAA;;;EACX,OAAOC,QAAQC,SAAiD;AAC9D,WAAO;MACLC,QAAQH;MACRI,QAAQ;MACRC,aAAa,CAAA;MACbC,WAAW;;QAET;UACEC,SAAS5B;UACT6B,UAAU;YACR,GAAIN,WAAW,CAAC;UAClB;QACF;;QAEAO,YAAAA;;QAEA3B;;QAEA;UACEyB,SAASG;UACTC,UAAU7B;QACZ;;MAEF8B,SAAS,CAAA;IACX;EACF;AACF;;;;AG7BO,IAAMC,gBAAgB;AAuBtB,IAAMC,SAAS,gBAAAC,QAAA,UAAMC,4BAAYH,eAAe,IAAA,GAAjC,QAAA;;;AEvBf,IAAMI,oBAAoB;AAO1B,IAAMC,8BAA8BC,OAAO,uBAAA;AAG3C,IAAMC,qBAAqBD,OAAO,cAAA;AAGlC,IAAME,2BAA2BF,OAAO,0BAAA;AAOxC,IAAMG,YAAY;AAGlB,IAAMC,kBAAkB;AAGxB,IAAMC,kBAAkB;AAGxB,IAAMC,kBAAiB;AAGvB,IAAMC,qBAAqB;AAG3B,IAAMC,wBAAwB;AAE9B,IAAMC,uBAAuB;;;AC1CpC,IAAAC,iBAA2B;AAC3B,qBAA0D;;;;;;;;AAgBnD,IAAMC,eAAe;AAWrB,IAAMC,iBAAN,MAAMA;SAAAA;;;;;;EAIXC,cAAcC,gBAAgD;AAC5D,UAAM,EAAEC,KAAKC,MAAK,IAAK,IAAIC,8BACzBC,0BAAAA;AAcF,eAAWC,QAAQL,eAAeM,OAAO;AACvCL,UAAII,MAAgBR,YAAAA;IACtB;AAEA,WAAOK,MAAAA;EACT;AACF;;;;;;ACtDA,IAAAK,iBAA8B;AAKvB,IAAKC,uBAAAA,0BAAAA,uBAAAA;AACA,EAAAA,sBAAA,iBAAA,IAAA;AAED,EAAAA,sBAAA,eAAA,IAAA;AAEA,EAAAA,sBAAA,qBAAA,IAAA;AAEI,EAAAA,sBAAA,gCAAA,IAAA;SAPHA;;AAsCL,IAAMC,4BAAN,MAAMA,mCAAkCC,6BAAAA;EA3C/C,OA2C+CA;;;EAC7BC;EACAC;EAEhB,YAAYA,SAAkCC,iBAAyB,KAAK;AAC1E,UACE;MACEC,YAAYD;MACZE,OAAOH,QAAQG;MACfJ,MAAMC,QAAQD;MACdK,SAASJ,QAAQI;MACjB,GAAIJ,QAAQK,iBAAiB;QAAEA,eAAeL,QAAQK;MAAc;MACpE,GAAIL,QAAQM,uBAAuB;QACjCA,qBAAqBN,QAAQM;MAC/B;MACA,GAAIN,QAAQO,0BAA0B;QACpCA,wBAAwBP,QAAQO;MAClC;MACA,GAAIP,QAAQQ,YAAY;QAAEA,UAAUR,QAAQQ;MAAS;IACvD,GACAP,cAAAA;AAGF,SAAKF,OAAOC,QAAQD;AACpB,SAAKC,UAAUA;AACf,SAAKS,OAAO;EACd;;;;EAKA,OAAOC,gBACLN,UAAkB,kCACS;AAC3B,WAAO,IAAIP,2BAA0B;MACnCE,MAAI;MACJK;IACF,CAAA;EACF;;;;EAKA,OAAOO,aACLN,eACAO,MAAe,OACY;AAC3B,UAAMR,UAAUQ,MACZ,yCAAWP,cAAcQ,KAAK,IAAA,CAAA,KAC9B,qDAAaR,cAAcQ,KAAK,IAAA,CAAA;AAEpC,WAAO,IAAIhB,2BAA0B;MACnCE,MAAI;MACJK;MACAC;MACAG,UAAU;QAAEI;MAAI;IAClB,CAAA;EACF;;;;EAKA,OAAOE,mBACLR,qBACAS,KAAc,OACdC,eAC2B;AAC3B,QAAIZ;AAEJ,QAAIY,eAAe;AACjBZ,gBAAUY;IACZ,WAAWV,oBAAoBW,WAAW,GAAG;AAC3C,YAAMC,OAAOZ,oBAAoB,CAAA;AACjCF,gBAAUW,KACN,gDAAaG,KAAKC,OAAO,sDAAcD,KAAKE,QAAQP,KAAK,IAAA,CAAA,MACzD,gDAAaK,KAAKC,OAAO,0CAAYD,KAAKE,QAAQP,KAAK,IAAA,CAAA;IAC7D,OAAO;AACLT,gBAAUW,KACN,uGAAuBT,oBAAoBe,IAAI,CAAC,EAAED,SAASD,QAAO,MAAO,UAAKA,OAAAA,sDAAqBC,QAAQP,KAAK,IAAA,CAAA,GAAQ,EAAEA,KAAK,IAAA,CAAA,KAC/H,uGAAuBP,oBAAoBe,IAAI,CAAC,EAAED,SAASD,QAAO,MAAO,UAAKA,OAAAA,0CAAmBC,QAAQP,KAAK,IAAA,CAAA,GAAQ,EAAEA,KAAK,IAAA,CAAA;IACnI;AAEA,WAAO,IAAIhB,2BAA0B;MACnCE,MAAI;MACJK;MACAE;MACAE,UAAU;QAAEO;MAAG;IACjB,CAAA;EACF;AACF;;;;;;;;;;;;;;;;;;;;AR7GO,IAAMO,oBAAN,MAAMA,mBAAAA;SAAAA;;;;;EACMC,SAAS,IAAIC,sBAAOF,mBAAkBG,IAAI;EAE3D,YAEmBC,WACAC,gBACjB;SAFiBD,YAAAA;SACAC,iBAAAA;EAChB;;;;EAKH,MAAMC,mBACJC,YACoC;AAEpC,UAAMC,kBAAkB,YAAA;AACtB,YAAMC,SAASF,WAAWE,UAAUC;AACpC,UAAI;AACF,cAAMC,iBAAiB,MAAM,KAAKC,aAAaL,UAAAA;AAE/C,cAAMM,oBAAwC;UAC5C,GAAGF;UACHG,WAAW,oBAAIC,KAAAA;QACjB;AAEA,eAAOF;MACT,SAASG,OAAO;AACd,aAAKf,OAAOe,MACV,wCAAwCP,MAAAA,KACxCO,KAAAA;AAEF,cAAMA;MACR;IACF,GAAA;AAEA,WAAOR;EACT;;;;;EAMA,MAAcI,aACZL,YAC6B;AAC7B,UAAM,EAAEU,UAAU,IAAI,IAAK,KAAKb,aAAa,CAAC;AAC9C,UAAM,EAAEc,SAAST,QAAQU,OAAOC,SAASC,UAAS,IAAKd;AAGvD,UAAMe,MAAM,GAAGJ,OAAAA,2BAAkCC,KAAAA;AAGjD,UAAMI,iBAAyC;MAC7C,gBAAgB;IAClB;AAEA,QAAIH,SAAS;AACX,YAAMI,eAAeC,OAAOC,QAAQN,OAAAA,EACjCO,IAAI,CAAC,CAACC,KAAKC,KAAAA,MAAW,GAAGD,GAAAA,IAAOC,KAAAA,EAAO,EACvCC,KAAK,IAAA;AACR,UAAIN,cAAc;AAChBD,uBAAeQ,SAASP;MAC1B;IACF;AAEA,QAAIH,WAAW;AACbE,qBAAe,mBAAA,IAAuBF;IACxC;AAGA,UAAMW,aAAa,IAAIC,gBAAAA;AACvB,UAAMC,YAAYC,WAAW,MAAMH,WAAWI,MAAK,GAAInB,OAAAA;AAEvD,QAAI;AACF,YAAMoB,WAAW,MAAMC,MAAMhB,KAAK;QAChCiB,QAAQ;QACRC,SAASjB;QACTkB,QAAQT,WAAWS;MACrB,CAAA;AAEAC,mBAAaR,SAAAA;AAEb,UAAI,CAACG,SAASM,IAAI;AAChB,cAAM3B,QAAQ,IAAI4B,MAChB,2BAA2BP,SAASQ,MAAM,KAAKR,SAASS,UAAU,EAAE;AAEtE,cAAM,IAAIC,0BACR;UACEC,OAAOhC;UACPiC,MAAMC,qBAAqBC;UAC3BC,SAASpC,MAAMoC;QACjB,GACAC,0BAAWC,qBAAqB;MAEpC;AAEA,YAAMC,OAAQ,MAAMlB,SAASmB,KAAI;AAIjC,aAAO;QACL/C;QACAgD,OAAOF,KAAKG,aAAa,CAAA;;;QAGzB5C,WAAW,oBAAIC,KAAAA;MACjB;IACF,SAASC,OAAgB;AACvB0B,mBAAaR,SAAAA;AACb,UAAIyB,MAAM3C;AAEV,UAAKA,MAA4Bb,SAAS,cAAc;AACtDwD,cAAM,IAAIf,MAAM,wCAAwC3B,OAAAA,IAAW;MACrE;AAEA,YAAM,IAAI8B,0BACR;QACEC,OAAOW;QACPV,MAAMC,qBAAqBC;QAC3BC,SAASO,IAAIP;MACf,GACAC,0BAAWC,qBAAqB;IAEpC;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCA,MAAMM,WACJC,aACAC,aAC6B;AAC7B,UAAMrD,SAASqD,aAAarD,UAAUC;AACtC,UAAMC,iBAAiB,MAAM,KAAKL,mBAAmB;MACnDY,SAAS4C,aAAa5C,WAAW;MACjCT;MACAU,OAAO2C,aAAa3C,SAAS;MAC7BC,SAAS0C,aAAa1C,WAAW,CAAC;MAClCC,WAAWyC,aAAazC,aAAa;IACvC,CAAA;AACA,QAAI,CAACV,gBAAgB;AACnB,YAAM,IAAIoC,0BACR;QACEC,OAAO,IAAIJ,MAAM,6CAAA;QACjBK,MAAMC,qBAAqBC;QAC3BC,SAAS;MACX,GACAC,0BAAWU,WAAW;IAE1B;AAGA,UAAMC,UAAU,KAAK3D,eAAe4D,cAActD,cAAAA;AAElD,UAAM,EAAE8C,OAAOS,IAAG,IAAKL;AAIvB,UAAMM,eAAeV,MAAM9B,IAAIyC,CAAAA,SAAQJ,QAAQK,IAAID,MAAME,YAAAA,CAAAA;AAEzD,UAAMC,UAAUL,MACZC,aAAaK,MAAMC,CAAAA,WAAUA,MAAAA,IAC7BN,aAAaO,KAAKD,CAAAA,WAAUA,MAAAA;AAEhC,QAAI,CAACF,SAAS;AACZ,YAAMI,YAAYhE,eAAe8C;AACjC,WAAKxD,OAAO2E,KACV,sDAAcnE,MAAAA,+BAAiBkE,UAAU7C,KAAK,IAAA,CAAA,oBAAe2B,MAAM3B,KAAK,IAAA,CAAA,GAAQ;AAElF,YAAMiB,0BAA0B8B,aAAapB,OAAOS,GAAAA;IACtD;AAEA,WAAOvD;EACT;AAoFF;;;;;;;;;;;;;ASzTA,IAAAmE,iBAA0D;AAC1D,IAAAC,eAA0B;;;;;;;;;;;;AAWnB,IAAMC,iBAAN,MAAMA;SAAAA;;;;;EACX,YACUC,WACAC,mBACR;SAFQD,YAAAA;SACAC,oBAAAA;EACP;EAEH,MAAMC,YAAYC,SAA6C;AAC7D,UAAMC,OAAOD,QAAQE,aAAY;AACjC,UAAMC,UAAUF,KAAKG,WAAU;AAE/B,UAAMC,cAAcF,QAAQE;AAG5B,UAAMC,uBACJ,KAAKT,UAAUU,kBAAwCC,WAAW;MAChER,QAAQS,WAAU;MAClBT,QAAQU,SAAQ;KACjB;AAEH,QAAIJ,sBAAsB;AACxB,YAAM,KAAKA,qBAAqBA,sBAAsBD,WAAAA;IACxD;AAiBA,WAAO;EACT;;;;EAKA,MAAcC,qBACZK,aACAN,aACA;AACA,WAAO,KAAKP,kBAAkBc,WAAWD,aAAaN,WAAAA;EACxD;AAYF;;;;;;;;;;;;;;;;;;AVvDO,IAAMQ,kBAAN,MAAMA,iBAAAA;SAAAA;;;EACX,OAAOC,QAAQC,SAAiD;AAC9D,UAAM,EAAEC,gBAAgB,CAAC,EAAC,IAAKD,WAAW,CAAC;AAC3C,WAAO;MACLE,QAAQJ;MACRK,QAAQ;MACRC,aAAa,CAAA;MACbC,WAAW;;QAET;UACEC,SAASC;UACTC,UAAU;YAAE,GAAGR;UAAQ;QACzB;QACA;UACEM,SAASG;UACTD,UAAUP;QACZ;;QAEAS;;QAEAC;QACAC;QACAC;;QAEA;UACEP,SAASQ;UACTC,UAAUF;QACZ;;MAEFG,SAAS;QAACL;QAAmBC;;IAC/B;EACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCA,OAAOK,aAAajB,SAAqD;AACvE,UAAM,EAAEkB,UAAU,CAAA,GAAIC,SAAS,CAAA,GAAIC,WAAU,IAAKpB;AAElD,WAAO;MACLE,QAAQJ;MACRK,QAAQ;MACRe;MACAd,aAAa,CAAA;MACbC,WAAW;;QAET;UACEC,SAASC;UACTa;UACAD;QACF;;QAEA;UACEb,SAASG;UACTW,YAAY,wBAACC,kBAAAA;AACX,mBAAOA,cAAcpB;UACvB,GAFY;UAGZkB,QAAQ;YAACZ;;QACX;;QAEAG;;QAEAC;QACAC;QACAC;;QAEA;UACEP,SAASQ;UACTC,UAAUF;QACZ;;MAEFG,SAAS;QAACL;QAAmBC;;IAC/B;EACF;AACF;;;;;;AWvHA,IAAAU,kBAA4B;AA6BrB,IAAMC,UAAU,wBACrBC,MACAC,MAAe,UAAK;AAGpB,MAAIC;AAEJ,MAAI,CAACC,MAAMC,QAAQJ,IAAAA,KAAS,OAAOA,SAAS,UAAU;AAEpDE,kBAAc;MACZG,OAAO;QAACL;;MACRC;IACF;EACF,WAAWE,MAAMC,QAAQJ,IAAAA,KAASA,KAAKM,KAAK,CAACN,UAAS,OAAOA,UAAS,QAAA,GAAW;AAE/EE,kBAAc;MACZG,OAAOL;MACPC;IACF;EACF,OAAO;AACL,UAAM,IAAIM,MAAM,gCAAgCC,KAAKC,UAAUT,IAAAA,CAAAA;EACjE;AAEA,aAAOU,6BAAYC,WAAWT,WAAAA;AAChC,GAxBuB;","names":["NEED_LOGIN_KEY","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","CACHE_CONFIG_TOKEN","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","PERMISSIONS_KEY","ENVIRONMENT_KEY","NEED_LOGIN_KEY","DEFAULT_LOGIN_PATH","MOCK_ROLES_COOKIE_KEY","ENABLE_MOCK_ROLE_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","cookies","csrfToken","url","requestHeaders","cookieString","Object","entries","map","key","value","join","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","role_list","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","and","checkResults","role","can","ROLE_SUBJECT","hasRole","every","result","some","userRoles","warn","roleRequired","import_common","import_core","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","requirement","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}/api/v1/permission/apps/${appId}/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: 'GET',\n credentials: 'same-origin',\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?: { role_list?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.role_list || [],\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 ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken: userContext?.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/** 缓存配置 Token */\nexport const CACHE_CONFIG_TOKEN = Symbol('CACHE_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/** 需要的权限元数据键 */\nexport const PERMISSIONS_KEY = 'authzpaas:permissions';\n\n/** 需要的环境元数据键 */\nexport const ENVIRONMENT_KEY = 'authzpaas:environment';\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authzpaas:needLogin';\n\n/** 模块选项:登录页路径默认值 */\nexport const DEFAULT_LOGIN_PATH = '/login';\n\n/** 角色模拟的 Cookie 键名 */\nexport const MOCK_ROLES_COOKIE_KEY = 'mockRoles';\n\nexport const ENABLE_MOCK_ROLE_KEY = '__authzpaas_enableMockRole';\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\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 );\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":["NEED_LOGIN_KEY","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","CACHE_CONFIG_TOKEN","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","PERMISSIONS_KEY","ENVIRONMENT_KEY","NEED_LOGIN_KEY","DEFAULT_LOGIN_PATH","MOCK_ROLES_COOKIE_KEY","ENABLE_MOCK_ROLE_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","role_list","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","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"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { DynamicModule, Type, HttpException } from '@nestjs/common';
1
+ import { DynamicModule, Type, CanActivate, ExecutionContext, HttpException } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
2
3
  import { PureAbility } from '@casl/ability';
3
4
 
4
5
  /**
@@ -88,8 +89,8 @@ interface UserRolesDTO {
88
89
  userId: string;
89
90
  /** 应用ID */
90
91
  appId: string;
91
- /** cookies 字符串 */
92
- cookies: Record<string, string>;
92
+ /** cookie 字符串 */
93
+ cookie?: string;
93
94
  /** CSRF 令牌 */
94
95
  csrfToken: string;
95
96
  }
@@ -97,9 +98,7 @@ interface UserContext {
97
98
  userId?: string;
98
99
  tenantId?: number;
99
100
  appId?: string;
100
- userRoles?: string[];
101
101
  baseUrl?: string;
102
- cookies?: Record<string, string>;
103
102
  csrfToken?: string;
104
103
  }
105
104
  /**
@@ -183,6 +182,74 @@ type CheckRoleRequirement = RoleRequirement;
183
182
  */
184
183
  declare const CanRole: (role: string[] | string, and?: boolean) => MethodDecorator;
185
184
 
185
+ /**
186
+ * CASL Ability 类型
187
+ */
188
+ type AppAbility = PureAbility<[Action, Subject]>;
189
+ /**
190
+ * 角色检查的特殊 Subject
191
+ * 用于统一角色鉴权和权限点位鉴权
192
+ *
193
+ * 使用方式:
194
+ * - 权限点位鉴权:ability.can('read', 'Todo')
195
+ * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')
196
+ */
197
+ declare const ROLE_SUBJECT = "@role";
198
+ /**
199
+ * Ability 工厂
200
+ * 负责根据用户权限数据创建 CASL Ability 实例
201
+ *
202
+ * 统一了两种鉴权方式:
203
+ * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject
204
+ * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式
205
+ */
206
+ declare class AbilityFactory {
207
+ /**
208
+ * 为用户创建 Ability
209
+ */
210
+ createForUser(permissionData: UserPermissionData): AppAbility;
211
+ }
212
+
213
+ /**
214
+ * 权限服务
215
+ * 内置权限获取和缓存逻辑,以及权限检查逻辑
216
+ */
217
+ declare class PermissionService {
218
+ private readonly apiConfig;
219
+ private readonly abilityFactory;
220
+ private readonly logger;
221
+ constructor(apiConfig: PermissionApiConfig, abilityFactory: AbilityFactory);
222
+ /**
223
+ * 获取用户权限数据
224
+ */
225
+ getUserPermissions(requestDto: UserRolesDTO): Promise<UserPermissionData | null>;
226
+ /**
227
+ * 从 API 获取权限数据
228
+ * 内置实现,用户无需配置
229
+ */
230
+ private fetchFromApi;
231
+ /**
232
+ * 检查角色要求
233
+ * 使用 CASL Ability 统一鉴权方式
234
+ * @param requirement 角色要求
235
+ * @param userId 用户ID,匿名用户时为空
236
+ * @returns 用户权限数据
237
+ * @throws PermissionDeniedException 当角色不满足时
238
+ */
239
+ checkRoles(requirement: RoleRequirement, userContext?: UserContext, cookie?: string): Promise<UserPermissionData>;
240
+ }
241
+
242
+ /**
243
+ * AuthZPaas 守卫
244
+ * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService
245
+ */
246
+ declare class AuthZPaasGuard implements CanActivate {
247
+ private reflector;
248
+ private permissionService;
249
+ constructor(reflector: Reflector, permissionService: PermissionService);
250
+ canActivate(context: ExecutionContext): Promise<boolean>;
251
+ }
252
+
186
253
  /**
187
254
  * 权限拒绝异常类型
188
255
  */
@@ -243,34 +310,6 @@ declare class PermissionDeniedException extends HttpException {
243
310
  }>, or?: boolean, customMessage?: string): PermissionDeniedException;
244
311
  }
245
312
 
246
- /**
247
- * CASL Ability 类型
248
- */
249
- type AppAbility = PureAbility<[Action, Subject]>;
250
- /**
251
- * 角色检查的特殊 Subject
252
- * 用于统一角色鉴权和权限点位鉴权
253
- *
254
- * 使用方式:
255
- * - 权限点位鉴权:ability.can('read', 'Todo')
256
- * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')
257
- */
258
- declare const ROLE_SUBJECT = "@role";
259
- /**
260
- * Ability 工厂
261
- * 负责根据用户权限数据创建 CASL Ability 实例
262
- *
263
- * 统一了两种鉴权方式:
264
- * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject
265
- * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式
266
- */
267
- declare class AbilityFactory {
268
- /**
269
- * 为用户创建 Ability
270
- */
271
- createForUser(permissionData: UserPermissionData): AppAbility;
272
- }
273
-
274
313
  /**
275
314
  * 常量
276
315
  */
@@ -302,4 +341,4 @@ declare const DEFAULT_LOGIN_PATH = "/login";
302
341
  declare const MOCK_ROLES_COOKIE_KEY = "mockRoles";
303
342
  declare const ENABLE_MOCK_ROLE_KEY = "__authzpaas_enableMockRole";
304
343
 
305
- export { ANONYMOUS_USER_ID, AUTHZPAAS_MODULE_OPTIONS, AbilityFactory, type Action, type AppAbility, AuthZPaasModule, type AuthZPaasModuleOptions, type AuthorizationContext, CACHE_CONFIG_TOKEN, CanRole, type CheckRoleRequirement, DEFAULT_LOGIN_PATH, ENABLE_MOCK_ROLE_KEY, ENVIRONMENT_KEY, type EnvironmentContext, MOCK_ROLES_COOKIE_KEY, NEED_LOGIN_KEY, PERMISSIONS_KEY, PERMISSION_API_CONFIG_TOKEN, type Permission, type PermissionApiConfig, type PermissionDeniedDetails, PermissionDeniedException, PermissionDeniedType, ROLES_KEY, ROLE_SUBJECT, type RoleRequirement, type Subject, type UserContext, type UserPermissionData, type UserRole, type UserRolesDTO };
344
+ export { ANONYMOUS_USER_ID, AUTHZPAAS_MODULE_OPTIONS, AbilityFactory, type Action, type AppAbility, AuthZPaasGuard, AuthZPaasModule, type AuthZPaasModuleOptions, type AuthorizationContext, CACHE_CONFIG_TOKEN, CanRole, type CheckRoleRequirement, DEFAULT_LOGIN_PATH, ENABLE_MOCK_ROLE_KEY, ENVIRONMENT_KEY, type EnvironmentContext, MOCK_ROLES_COOKIE_KEY, NEED_LOGIN_KEY, PERMISSIONS_KEY, PERMISSION_API_CONFIG_TOKEN, type Permission, type PermissionApiConfig, type PermissionDeniedDetails, PermissionDeniedException, PermissionDeniedType, PermissionService, ROLES_KEY, ROLE_SUBJECT, type RoleRequirement, type Subject, type UserContext, type UserPermissionData, type UserRole, type UserRolesDTO };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { DynamicModule, Type, HttpException } from '@nestjs/common';
1
+ import { DynamicModule, Type, CanActivate, ExecutionContext, HttpException } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
2
3
  import { PureAbility } from '@casl/ability';
3
4
 
4
5
  /**
@@ -88,8 +89,8 @@ interface UserRolesDTO {
88
89
  userId: string;
89
90
  /** 应用ID */
90
91
  appId: string;
91
- /** cookies 字符串 */
92
- cookies: Record<string, string>;
92
+ /** cookie 字符串 */
93
+ cookie?: string;
93
94
  /** CSRF 令牌 */
94
95
  csrfToken: string;
95
96
  }
@@ -97,9 +98,7 @@ interface UserContext {
97
98
  userId?: string;
98
99
  tenantId?: number;
99
100
  appId?: string;
100
- userRoles?: string[];
101
101
  baseUrl?: string;
102
- cookies?: Record<string, string>;
103
102
  csrfToken?: string;
104
103
  }
105
104
  /**
@@ -183,6 +182,74 @@ type CheckRoleRequirement = RoleRequirement;
183
182
  */
184
183
  declare const CanRole: (role: string[] | string, and?: boolean) => MethodDecorator;
185
184
 
185
+ /**
186
+ * CASL Ability 类型
187
+ */
188
+ type AppAbility = PureAbility<[Action, Subject]>;
189
+ /**
190
+ * 角色检查的特殊 Subject
191
+ * 用于统一角色鉴权和权限点位鉴权
192
+ *
193
+ * 使用方式:
194
+ * - 权限点位鉴权:ability.can('read', 'Todo')
195
+ * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')
196
+ */
197
+ declare const ROLE_SUBJECT = "@role";
198
+ /**
199
+ * Ability 工厂
200
+ * 负责根据用户权限数据创建 CASL Ability 实例
201
+ *
202
+ * 统一了两种鉴权方式:
203
+ * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject
204
+ * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式
205
+ */
206
+ declare class AbilityFactory {
207
+ /**
208
+ * 为用户创建 Ability
209
+ */
210
+ createForUser(permissionData: UserPermissionData): AppAbility;
211
+ }
212
+
213
+ /**
214
+ * 权限服务
215
+ * 内置权限获取和缓存逻辑,以及权限检查逻辑
216
+ */
217
+ declare class PermissionService {
218
+ private readonly apiConfig;
219
+ private readonly abilityFactory;
220
+ private readonly logger;
221
+ constructor(apiConfig: PermissionApiConfig, abilityFactory: AbilityFactory);
222
+ /**
223
+ * 获取用户权限数据
224
+ */
225
+ getUserPermissions(requestDto: UserRolesDTO): Promise<UserPermissionData | null>;
226
+ /**
227
+ * 从 API 获取权限数据
228
+ * 内置实现,用户无需配置
229
+ */
230
+ private fetchFromApi;
231
+ /**
232
+ * 检查角色要求
233
+ * 使用 CASL Ability 统一鉴权方式
234
+ * @param requirement 角色要求
235
+ * @param userId 用户ID,匿名用户时为空
236
+ * @returns 用户权限数据
237
+ * @throws PermissionDeniedException 当角色不满足时
238
+ */
239
+ checkRoles(requirement: RoleRequirement, userContext?: UserContext, cookie?: string): Promise<UserPermissionData>;
240
+ }
241
+
242
+ /**
243
+ * AuthZPaas 守卫
244
+ * 负责协调所有鉴权检查,具体检查逻辑委托给 PermissionService
245
+ */
246
+ declare class AuthZPaasGuard implements CanActivate {
247
+ private reflector;
248
+ private permissionService;
249
+ constructor(reflector: Reflector, permissionService: PermissionService);
250
+ canActivate(context: ExecutionContext): Promise<boolean>;
251
+ }
252
+
186
253
  /**
187
254
  * 权限拒绝异常类型
188
255
  */
@@ -243,34 +310,6 @@ declare class PermissionDeniedException extends HttpException {
243
310
  }>, or?: boolean, customMessage?: string): PermissionDeniedException;
244
311
  }
245
312
 
246
- /**
247
- * CASL Ability 类型
248
- */
249
- type AppAbility = PureAbility<[Action, Subject]>;
250
- /**
251
- * 角色检查的特殊 Subject
252
- * 用于统一角色鉴权和权限点位鉴权
253
- *
254
- * 使用方式:
255
- * - 权限点位鉴权:ability.can('read', 'Todo')
256
- * - 角色鉴权:ability.can('admin', ROLE_SUBJECT) 或 ability.can('admin', '@role')
257
- */
258
- declare const ROLE_SUBJECT = "@role";
259
- /**
260
- * Ability 工厂
261
- * 负责根据用户权限数据创建 CASL Ability 实例
262
- *
263
- * 统一了两种鉴权方式:
264
- * 1. 基于角色的鉴权 - 角色名作为 action,'@role' 作为 subject
265
- * 2. 基于权限点位的鉴权 - 标准的 action + subject 模式
266
- */
267
- declare class AbilityFactory {
268
- /**
269
- * 为用户创建 Ability
270
- */
271
- createForUser(permissionData: UserPermissionData): AppAbility;
272
- }
273
-
274
313
  /**
275
314
  * 常量
276
315
  */
@@ -302,4 +341,4 @@ declare const DEFAULT_LOGIN_PATH = "/login";
302
341
  declare const MOCK_ROLES_COOKIE_KEY = "mockRoles";
303
342
  declare const ENABLE_MOCK_ROLE_KEY = "__authzpaas_enableMockRole";
304
343
 
305
- export { ANONYMOUS_USER_ID, AUTHZPAAS_MODULE_OPTIONS, AbilityFactory, type Action, type AppAbility, AuthZPaasModule, type AuthZPaasModuleOptions, type AuthorizationContext, CACHE_CONFIG_TOKEN, CanRole, type CheckRoleRequirement, DEFAULT_LOGIN_PATH, ENABLE_MOCK_ROLE_KEY, ENVIRONMENT_KEY, type EnvironmentContext, MOCK_ROLES_COOKIE_KEY, NEED_LOGIN_KEY, PERMISSIONS_KEY, PERMISSION_API_CONFIG_TOKEN, type Permission, type PermissionApiConfig, type PermissionDeniedDetails, PermissionDeniedException, PermissionDeniedType, ROLES_KEY, ROLE_SUBJECT, type RoleRequirement, type Subject, type UserContext, type UserPermissionData, type UserRole, type UserRolesDTO };
344
+ export { ANONYMOUS_USER_ID, AUTHZPAAS_MODULE_OPTIONS, AbilityFactory, type Action, type AppAbility, AuthZPaasGuard, AuthZPaasModule, type AuthZPaasModuleOptions, type AuthorizationContext, CACHE_CONFIG_TOKEN, CanRole, type CheckRoleRequirement, DEFAULT_LOGIN_PATH, ENABLE_MOCK_ROLE_KEY, ENVIRONMENT_KEY, type EnvironmentContext, MOCK_ROLES_COOKIE_KEY, NEED_LOGIN_KEY, PERMISSIONS_KEY, PERMISSION_API_CONFIG_TOKEN, type Permission, type PermissionApiConfig, type PermissionDeniedDetails, PermissionDeniedException, PermissionDeniedType, PermissionService, ROLES_KEY, ROLE_SUBJECT, type RoleRequirement, type Subject, type UserContext, type UserPermissionData, type UserRole, type UserRolesDTO };
package/dist/index.js CHANGED
@@ -300,16 +300,13 @@ var PermissionService = class _PermissionService {
300
300
  */
301
301
  async fetchFromApi(requestDto) {
302
302
  const { timeout = 5e3 } = this.apiConfig || {};
303
- const { baseUrl, userId, appId, cookies, csrfToken } = requestDto;
303
+ const { baseUrl, userId, appId, cookie, csrfToken } = requestDto;
304
304
  const url = `${baseUrl}/api/v1/permission/apps/${appId}/roles`;
305
305
  const requestHeaders = {
306
306
  "Content-Type": "application/json"
307
307
  };
308
- if (cookies) {
309
- const cookieString = Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
310
- if (cookieString) {
311
- requestHeaders.Cookie = cookieString;
312
- }
308
+ if (cookie) {
309
+ requestHeaders.Cookie = cookie;
313
310
  }
314
311
  if (csrfToken) {
315
312
  requestHeaders["X-Suda-Csrf-Token"] = csrfToken;
@@ -319,6 +316,7 @@ var PermissionService = class _PermissionService {
319
316
  try {
320
317
  const response = await fetch(url, {
321
318
  method: "GET",
319
+ credentials: "same-origin",
322
320
  headers: requestHeaders,
323
321
  signal: controller.signal
324
322
  });
@@ -334,12 +332,13 @@ var PermissionService = class _PermissionService {
334
332
  const data = await response.json();
335
333
  return {
336
334
  userId,
337
- roles: data.role_list || [],
335
+ roles: data.data?.role_list || [],
338
336
  // TODO: 基于权限点位设置能力
339
337
  // permissions: data.permissions || [],
340
338
  fetchedAt: /* @__PURE__ */ new Date()
341
339
  };
342
340
  } catch (error) {
341
+ console.log("error", error);
343
342
  clearTimeout(timeoutId);
344
343
  let err = error;
345
344
  if (error.name === "AbortError") {
@@ -382,14 +381,15 @@ var PermissionService = class _PermissionService {
382
381
  * @returns 用户权限数据
383
382
  * @throws PermissionDeniedException 当角色不满足时
384
383
  */
385
- async checkRoles(requirement, userContext) {
384
+ async checkRoles(requirement, userContext, cookie) {
386
385
  const userId = userContext?.userId || ANONYMOUS_USER_ID;
387
386
  const permissionData = await this.getUserPermissions({
387
+ // FIXME: 使用 request 的 base url
388
388
  baseUrl: userContext?.baseUrl || "",
389
389
  userId,
390
390
  appId: userContext?.appId || "",
391
- cookies: userContext?.cookies || {},
392
- csrfToken: userContext?.csrfToken || ""
391
+ csrfToken: userContext?.csrfToken || "",
392
+ cookie
393
393
  });
394
394
  if (!permissionData) {
395
395
  throw new PermissionDeniedException({
@@ -448,22 +448,19 @@ var AuthZPaasGuard = class {
448
448
  async canActivate(context) {
449
449
  const http = context.switchToHttp();
450
450
  const request = http.getRequest();
451
+ const cookie = request.headers.cookie;
452
+ const baseUrl = `${request.protocol}://${request.get("host")}`;
451
453
  const userContext = request.userContext;
454
+ userContext.baseUrl = baseUrl;
452
455
  const checkRoleRequirement = this.reflector.getAllAndOverride(ROLES_KEY, [
453
456
  context.getHandler(),
454
457
  context.getClass()
455
458
  ]);
456
459
  if (checkRoleRequirement) {
457
- await this.checkRoleRequirement(checkRoleRequirement, userContext);
460
+ await this.permissionService.checkRoles(checkRoleRequirement, userContext, cookie);
458
461
  }
459
462
  return true;
460
463
  }
461
- /**
462
- * 检查角色要求
463
- */
464
- async checkRoleRequirement(requirement, userContext) {
465
- return this.permissionService.checkRoles(requirement, userContext);
466
- }
467
464
  };
468
465
  AuthZPaasGuard = _ts_decorate5([
469
466
  Injectable4(),
@@ -624,6 +621,7 @@ export {
624
621
  ANONYMOUS_USER_ID,
625
622
  AUTHZPAAS_MODULE_OPTIONS,
626
623
  AbilityFactory,
624
+ AuthZPaasGuard,
627
625
  AuthZPaasModule,
628
626
  CACHE_CONFIG_TOKEN,
629
627
  CanRole,
@@ -636,6 +634,7 @@ export {
636
634
  PERMISSION_API_CONFIG_TOKEN,
637
635
  PermissionDeniedException,
638
636
  PermissionDeniedType,
637
+ PermissionService,
639
638
  ROLES_KEY,
640
639
  ROLE_SUBJECT
641
640
  };
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, cookies, csrfToken } = requestDto;\n\n // 构建完整获取用户权限 URL\n const url = `${baseUrl}/api/v1/permission/apps/${appId}/roles`;\n\n // 构建请求头\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (cookies) {\n const cookieString = Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n if (cookieString) {\n requestHeaders.Cookie = cookieString;\n }\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: 'GET',\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 role_list?: string[];\n };\n\n return {\n userId,\n roles: data.role_list || [],\n // TODO: 基于权限点位设置能力\n // permissions: data.permissions || [],\n fetchedAt: new Date(),\n };\n } catch (error: unknown) {\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 ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n const permissionData = await this.getUserPermissions({\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n cookies: userContext?.cookies || {},\n csrfToken: userContext?.csrfToken || '',\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/** 缓存配置 Token */\nexport const CACHE_CONFIG_TOKEN = Symbol('CACHE_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/** 需要的权限元数据键 */\nexport const PERMISSIONS_KEY = 'authzpaas:permissions';\n\n/** 需要的环境元数据键 */\nexport const ENVIRONMENT_KEY = 'authzpaas:environment';\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authzpaas:needLogin';\n\n/** 模块选项:登录页路径默认值 */\nexport const DEFAULT_LOGIN_PATH = '/login';\n\n/** 角色模拟的 Cookie 键名 */\nexport const MOCK_ROLES_COOKIE_KEY = 'mockRoles';\n\nexport const ENABLE_MOCK_ROLE_KEY = '__authzpaas_enableMockRole';\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 { RoleRequirement, 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\n const userContext = request.userContext as UserContext;\n\n // 检查角色要求\n const checkRoleRequirement =\n this.reflector.getAllAndOverride<CheckRoleRequirement>(ROLES_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (checkRoleRequirement) {\n await this.checkRoleRequirement(checkRoleRequirement, userContext);\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 checkRoleRequirement(\n requirement: RoleRequirement,\n userContext?: UserContext\n ) {\n return this.permissionService.checkRoles(requirement, userContext);\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","CACHE_CONFIG_TOKEN","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","PERMISSIONS_KEY","ENVIRONMENT_KEY","NEED_LOGIN_KEY","DEFAULT_LOGIN_PATH","MOCK_ROLES_COOKIE_KEY","ENABLE_MOCK_ROLE_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","cookies","csrfToken","url","requestHeaders","cookieString","Object","entries","map","key","value","join","Cookie","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","method","headers","signal","clearTimeout","ok","Error","status","statusText","PermissionDeniedException","cause","type","PermissionDeniedType","PERMISSION_CONFIG_QUERY_FAILED","message","HttpStatus","INTERNAL_SERVER_ERROR","data","json","roles","role_list","err","checkRoles","requirement","userContext","BAD_REQUEST","ability","createForUser","and","checkResults","role","can","ROLE_SUBJECT","hasRole","every","result","some","userRoles","warn","roleRequired","Injectable","Reflector","AuthZPaasGuard","reflector","permissionService","canActivate","context","http","switchToHttp","request","getRequest","userContext","checkRoleRequirement","getAllAndOverride","ROLES_KEY","getHandler","getClass","requirement","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}/api/v1/permission/apps/${appId}/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: 'GET',\n credentials: 'same-origin',\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?: { role_list?: string[] };\n };\n\n return {\n userId,\n roles: data.data?.role_list || [],\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 ): Promise<UserPermissionData> {\n const userId = userContext?.userId || ANONYMOUS_USER_ID;\n const permissionData = await this.getUserPermissions({\n // FIXME: 使用 request 的 base url\n baseUrl: userContext?.baseUrl || '',\n userId,\n appId: userContext?.appId || '',\n csrfToken: userContext?.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/** 缓存配置 Token */\nexport const CACHE_CONFIG_TOKEN = Symbol('CACHE_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/** 需要的权限元数据键 */\nexport const PERMISSIONS_KEY = 'authzpaas:permissions';\n\n/** 需要的环境元数据键 */\nexport const ENVIRONMENT_KEY = 'authzpaas:environment';\n\n/** 需要登录元数据键 */\nexport const NEED_LOGIN_KEY = 'authzpaas:needLogin';\n\n/** 模块选项:登录页路径默认值 */\nexport const DEFAULT_LOGIN_PATH = '/login';\n\n/** 角色模拟的 Cookie 键名 */\nexport const MOCK_ROLES_COOKIE_KEY = 'mockRoles';\n\nexport const ENABLE_MOCK_ROLE_KEY = '__authzpaas_enableMockRole';\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\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 );\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","CACHE_CONFIG_TOKEN","AUTHZPAAS_MODULE_OPTIONS","ROLES_KEY","PERMISSIONS_KEY","ENVIRONMENT_KEY","NEED_LOGIN_KEY","DEFAULT_LOGIN_PATH","MOCK_ROLES_COOKIE_KEY","ENABLE_MOCK_ROLE_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","role_list","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","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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/nestjs-authzpaas",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "FullStack Nestjs authzpaas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",