@lark-apaas/nestjs-authzpaas 0.1.0-alpha.0

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.js ADDED
@@ -0,0 +1,1420 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/authzpaas.module.ts
5
+ import { Module } from "@nestjs/common";
6
+ import { APP_FILTER, APP_GUARD, Reflector as Reflector2 } from "@nestjs/core";
7
+
8
+ // src/const.ts
9
+ var ANONYMOUS_USER_ID = "anonymous_user_id";
10
+ var PERMISSION_API_CONFIG_TOKEN = Symbol("PERMISSION_API_CONFIG");
11
+ var CACHE_CONFIG_TOKEN = Symbol("CACHE_CONFIG");
12
+ var AUTHZPAAS_MODULE_OPTIONS = Symbol("AUTHZPAAS_MODULE_OPTIONS");
13
+ var ROLES_KEY = "authzpaas:roles";
14
+ var PERMISSIONS_KEY = "authzpaas:permissions";
15
+ var ENVIRONMENT_KEY = "authzpaas:environment";
16
+ var NEED_LOGIN_KEY = "authzpaas:needLogin";
17
+ var DEFAULT_LOGIN_PATH = "/login";
18
+ var MOCK_ROLES_COOKIE_KEY = "mockRoles";
19
+ var ENABLE_MOCK_ROLE_KEY = "__authzpaas_enableMockRole";
20
+
21
+ // src/services/permission.service.ts
22
+ import { Injectable as Injectable2, Inject, Logger, HttpStatus } from "@nestjs/common";
23
+
24
+ // src/utils/memory-cache.ts
25
+ var MemoryCache = class {
26
+ static {
27
+ __name(this, "MemoryCache");
28
+ }
29
+ cache;
30
+ accessOrder;
31
+ options;
32
+ hits = 0;
33
+ misses = 0;
34
+ constructor(options) {
35
+ this.cache = /* @__PURE__ */ new Map();
36
+ this.accessOrder = [];
37
+ this.options = options;
38
+ }
39
+ /**
40
+ * 获取缓存值
41
+ */
42
+ get(key) {
43
+ if (!this.options.enabled) {
44
+ return void 0;
45
+ }
46
+ const item = this.cache.get(key);
47
+ if (!item) {
48
+ this.misses++;
49
+ return void 0;
50
+ }
51
+ if (Date.now() > item.expireAt) {
52
+ this.cache.delete(key);
53
+ this.removeFromAccessOrder(key);
54
+ this.misses++;
55
+ return void 0;
56
+ }
57
+ this.updateAccessOrder(key);
58
+ this.hits++;
59
+ return item.value;
60
+ }
61
+ /**
62
+ * 设置缓存值
63
+ */
64
+ set(key, value) {
65
+ if (!this.options.enabled) {
66
+ return;
67
+ }
68
+ if (this.cache.size >= this.options.max && !this.cache.has(key)) {
69
+ this.evictLRU();
70
+ }
71
+ const expireAt = Date.now() + this.options.ttl * 1e3;
72
+ this.cache.set(key, {
73
+ value,
74
+ expireAt
75
+ });
76
+ this.updateAccessOrder(key);
77
+ }
78
+ /**
79
+ * 删除缓存
80
+ */
81
+ delete(key) {
82
+ this.cache.delete(key);
83
+ this.removeFromAccessOrder(key);
84
+ }
85
+ /**
86
+ * 清空所有缓存
87
+ */
88
+ clear() {
89
+ this.cache.clear();
90
+ this.accessOrder = [];
91
+ this.hits = 0;
92
+ this.misses = 0;
93
+ }
94
+ /**
95
+ * 获取缓存统计信息
96
+ */
97
+ getStats() {
98
+ return {
99
+ size: this.cache.size,
100
+ hits: this.hits,
101
+ misses: this.misses,
102
+ hitRate: this.hits / (this.hits + this.misses) || 0,
103
+ enabled: this.options.enabled
104
+ };
105
+ }
106
+ /**
107
+ * 更新访问顺序
108
+ */
109
+ updateAccessOrder(key) {
110
+ this.removeFromAccessOrder(key);
111
+ this.accessOrder.push(key);
112
+ }
113
+ /**
114
+ * 从访问顺序中移除
115
+ */
116
+ removeFromAccessOrder(key) {
117
+ const index = this.accessOrder.indexOf(key);
118
+ if (index > -1) {
119
+ this.accessOrder.splice(index, 1);
120
+ }
121
+ }
122
+ /**
123
+ * 淘汰最少使用的项
124
+ */
125
+ evictLRU() {
126
+ if (this.accessOrder.length > 0) {
127
+ const oldestKey = this.accessOrder[0];
128
+ this.delete(oldestKey);
129
+ }
130
+ }
131
+ };
132
+
133
+ // src/casl/ability.factory.ts
134
+ import { Injectable } from "@nestjs/common";
135
+ import { AbilityBuilder, PureAbility } from "@casl/ability";
136
+ function _ts_decorate(decorators, target, key, desc) {
137
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
138
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
139
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
140
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
141
+ }
142
+ __name(_ts_decorate, "_ts_decorate");
143
+ var ROLE_SUBJECT = "@role";
144
+ var AbilityFactory = class {
145
+ static {
146
+ __name(this, "AbilityFactory");
147
+ }
148
+ /**
149
+ * 为用户创建 Ability
150
+ */
151
+ createForUser(permissionData) {
152
+ const { can, build } = new AbilityBuilder(PureAbility);
153
+ for (const permission of permissionData.permissions) {
154
+ const { sub, actions } = permission;
155
+ for (const action of actions) {
156
+ can(action, sub);
157
+ }
158
+ }
159
+ for (const role of permissionData.roles) {
160
+ can(role, ROLE_SUBJECT);
161
+ }
162
+ return build();
163
+ }
164
+ };
165
+ AbilityFactory = _ts_decorate([
166
+ Injectable()
167
+ ], AbilityFactory);
168
+
169
+ // src/exceptions/permission-denied.exception.ts
170
+ import { HttpException } from "@nestjs/common";
171
+ var PermissionDeniedType = /* @__PURE__ */ (function(PermissionDeniedType2) {
172
+ PermissionDeniedType2["UNAUTHENTICATED"] = "UNAUTHENTICATED";
173
+ PermissionDeniedType2["ROLE_REQUIRED"] = "ROLE_REQUIRED";
174
+ PermissionDeniedType2["PERMISSION_REQUIRED"] = "PERMISSION_REQUIRED";
175
+ PermissionDeniedType2["ENVIRONMENT_REQUIRED"] = "ENVIRONMENT_REQUIRED";
176
+ PermissionDeniedType2["PERMISSION_CONFIG_QUERY_FAILED"] = "PERMISSION_CONFIG_QUERY_FAILED";
177
+ return PermissionDeniedType2;
178
+ })({});
179
+ var PermissionDeniedException = class _PermissionDeniedException extends HttpException {
180
+ static {
181
+ __name(this, "PermissionDeniedException");
182
+ }
183
+ type;
184
+ details;
185
+ constructor(details, httpStatusCode = 403) {
186
+ super({
187
+ statusCode: httpStatusCode,
188
+ cause: details.cause,
189
+ type: details.type,
190
+ message: details.message,
191
+ ...details.requiredRoles && {
192
+ requiredRoles: details.requiredRoles
193
+ },
194
+ ...details.requiredPermissions && {
195
+ requiredPermissions: details.requiredPermissions
196
+ },
197
+ ...details.environmentRequirement && {
198
+ environmentRequirement: details.environmentRequirement
199
+ },
200
+ ...details.metadata && {
201
+ metadata: details.metadata
202
+ }
203
+ }, httpStatusCode);
204
+ this.type = details.type;
205
+ this.details = details;
206
+ this.name = "PermissionDeniedException";
207
+ }
208
+ /**
209
+ * 创建用户未认证异常
210
+ */
211
+ static unauthenticated(message = "\u7528\u6237\u672A\u8BA4\u8BC1") {
212
+ return new _PermissionDeniedException({
213
+ type: "UNAUTHENTICATED",
214
+ message
215
+ });
216
+ }
217
+ /**
218
+ * 创建角色不足异常
219
+ */
220
+ static roleRequired(requiredRoles, and = false) {
221
+ const message = and ? `\u9700\u8981\u6240\u6709\u89D2\u8272: ${requiredRoles.join(", ")}` : `\u9700\u8981\u4EE5\u4E0B\u4EFB\u4E00\u89D2\u8272: ${requiredRoles.join(", ")}`;
222
+ return new _PermissionDeniedException({
223
+ type: "ROLE_REQUIRED",
224
+ message,
225
+ requiredRoles,
226
+ metadata: {
227
+ and
228
+ }
229
+ });
230
+ }
231
+ /**
232
+ * 创建权限不足异常
233
+ */
234
+ static permissionRequired(requiredPermissions, or = false, customMessage) {
235
+ let message;
236
+ if (customMessage) {
237
+ message = customMessage;
238
+ } else if (requiredPermissions.length === 1) {
239
+ const perm = requiredPermissions[0];
240
+ message = or ? `\u7F3A\u5C11\u6743\u9650: \u9700\u8981\u5BF9 ${perm.subject} \u6267\u884C\u4EE5\u4E0B\u4EFB\u4E00\u64CD\u4F5C [${perm.actions.join(", ")}]` : `\u7F3A\u5C11\u6743\u9650: \u9700\u8981\u5BF9 ${perm.subject} \u6267\u884C\u6240\u6709\u64CD\u4F5C [${perm.actions.join(", ")}]`;
241
+ } else {
242
+ message = or ? `\u7F3A\u5C11\u6743\u9650: \u9700\u8981\u6EE1\u8DB3\u4EE5\u4E0B\u4EFB\u4E00\u6743\u9650\u8981\u6C42: ${requiredPermissions.map(({ actions, subject }) => `\u5BF9 ${subject} \u6267\u884C\u4EE5\u4E0B\u4EFB\u4E00\u64CD\u4F5C [${actions.join(", ")}]`).join(", ")}` : `\u7F3A\u5C11\u6743\u9650: \u9700\u8981\u6EE1\u8DB3\u4EE5\u4E0B\u6240\u6709\u6743\u9650\u8981\u6C42: ${requiredPermissions.map(({ actions, subject }) => `\u5BF9 ${subject} \u6267\u884C\u6240\u6709\u64CD\u4F5C [${actions.join(", ")}]`).join(", ")}`;
243
+ }
244
+ return new _PermissionDeniedException({
245
+ type: "PERMISSION_REQUIRED",
246
+ message,
247
+ requiredPermissions,
248
+ metadata: {
249
+ or
250
+ }
251
+ });
252
+ }
253
+ /**
254
+ * 创建环境不满足异常
255
+ */
256
+ static environmentRequired(requirement, message) {
257
+ return new _PermissionDeniedException({
258
+ type: "ENVIRONMENT_REQUIRED",
259
+ message: message || "\u4E0D\u6EE1\u8DB3\u73AF\u5883\u8981\u6C42",
260
+ environmentRequirement: requirement
261
+ });
262
+ }
263
+ };
264
+
265
+ // src/services/permission.service.ts
266
+ function _ts_decorate2(decorators, target, key, desc) {
267
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
268
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
269
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
270
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
271
+ }
272
+ __name(_ts_decorate2, "_ts_decorate");
273
+ function _ts_metadata(k, v) {
274
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
275
+ }
276
+ __name(_ts_metadata, "_ts_metadata");
277
+ function _ts_param(paramIndex, decorator) {
278
+ return function(target, key) {
279
+ decorator(target, key, paramIndex);
280
+ };
281
+ }
282
+ __name(_ts_param, "_ts_param");
283
+ var PermissionService = class _PermissionService {
284
+ static {
285
+ __name(this, "PermissionService");
286
+ }
287
+ apiConfig;
288
+ abilityFactory;
289
+ logger = new Logger(_PermissionService.name);
290
+ // 统一的缓存,同时缓存权限数据和 Ability 实例
291
+ cache;
292
+ // 缓存正在进行中的 Promise,避免重复请求
293
+ pendingRequests = /* @__PURE__ */ new Map();
294
+ constructor(apiConfig, cacheConfig, abilityFactory) {
295
+ this.apiConfig = apiConfig;
296
+ this.abilityFactory = abilityFactory;
297
+ this.cache = new MemoryCache({
298
+ ttl: cacheConfig.ttl || 300,
299
+ max: cacheConfig.max || 1e3,
300
+ enabled: cacheConfig.enabled !== false
301
+ });
302
+ this.logger.log(`PermissionService initialized with API: ${apiConfig?.baseUrl}`);
303
+ }
304
+ /**
305
+ * 构建权限/Ability 缓存 key
306
+ * - 若存在模拟角色:按角色集合排序拼接 + 用户维度
307
+ * - 否则按 userId/匿名用户
308
+ */
309
+ buildCacheKey(userId, mockRoles) {
310
+ if (mockRoles && mockRoles.length > 0) {
311
+ const sortedRoles = [
312
+ ...mockRoles
313
+ ].sort();
314
+ return `@roles:${sortedRoles.join("|")}#u:${userId || ANONYMOUS_USER_ID}`;
315
+ }
316
+ return userId || ANONYMOUS_USER_ID;
317
+ }
318
+ /**
319
+ * 获取用户权限数据(带缓存)
320
+ */
321
+ async getUserPermissions(userId, mockRoles) {
322
+ if (!this.apiConfig?.endpoint) {
323
+ this.logger.warn("Permission API endpoint is not configured, returning null");
324
+ return null;
325
+ }
326
+ const key = this.buildCacheKey(userId, mockRoles);
327
+ const cached = this.cache.get(key);
328
+ if (cached) {
329
+ this.logger.debug(`Cache hit for user ${key}`);
330
+ return cached.permissionData;
331
+ }
332
+ const pendingRequest = this.pendingRequests.get(key);
333
+ if (pendingRequest) {
334
+ this.logger.debug(`Reusing pending request for user ${key}`);
335
+ return pendingRequest;
336
+ }
337
+ this.logger.debug(`Cache miss for key ${key}, fetching from API`);
338
+ const requestPromise = (async () => {
339
+ try {
340
+ const permissionData = mockRoles && mockRoles.length > 0 ? await this.getPermissionsByMockRoles(userId, mockRoles) : await this.fetchFromApi(userId);
341
+ const dataWithTimestamp = {
342
+ ...permissionData,
343
+ fetchedAt: /* @__PURE__ */ new Date()
344
+ };
345
+ const ability = this.abilityFactory.createForUser(dataWithTimestamp);
346
+ this.cache.set(key, {
347
+ permissionData: dataWithTimestamp,
348
+ ability
349
+ });
350
+ return dataWithTimestamp;
351
+ } catch (error) {
352
+ this.logger.error(`Failed to fetch permissions for key ${key}:`, error);
353
+ throw error;
354
+ } finally {
355
+ this.pendingRequests.delete(key);
356
+ }
357
+ })();
358
+ this.pendingRequests.set(key, requestPromise);
359
+ return requestPromise;
360
+ }
361
+ /**
362
+ * 从 API 获取权限数据
363
+ * 内置实现,用户无需配置
364
+ */
365
+ async fetchFromApi(userId) {
366
+ const { baseUrl, apiToken, endpoint, headers = {}, timeout = 5e3 } = this.apiConfig || {};
367
+ const url = `${baseUrl}${endpoint}${userId ? `?userId=${userId}` : ""}`;
368
+ const requestHeaders = {
369
+ "Content-Type": "application/json",
370
+ ...headers
371
+ };
372
+ if (apiToken) {
373
+ requestHeaders["Authorization"] = `Bearer ${apiToken}`;
374
+ }
375
+ const controller = new AbortController();
376
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
377
+ try {
378
+ const response = await fetch(url, {
379
+ method: "GET",
380
+ headers: requestHeaders,
381
+ signal: controller.signal
382
+ });
383
+ clearTimeout(timeoutId);
384
+ if (!response.ok) {
385
+ const error = new Error(`Permission API returned ${response.status}: ${response.statusText}`);
386
+ throw new PermissionDeniedException({
387
+ cause: error,
388
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
389
+ message: error.message
390
+ }, HttpStatus.INTERNAL_SERVER_ERROR);
391
+ }
392
+ const data = await response.json();
393
+ const roles = (data.roles || []).map((role) => typeof role === "string" ? role : role.name);
394
+ return {
395
+ userId,
396
+ roles,
397
+ permissions: data.permissions || [],
398
+ fetchedAt: /* @__PURE__ */ new Date()
399
+ };
400
+ } catch (error) {
401
+ clearTimeout(timeoutId);
402
+ let err = error;
403
+ if (error.name === "AbortError") {
404
+ err = new Error(`Permission API request timeout after ${timeout}ms`);
405
+ }
406
+ throw new PermissionDeniedException({
407
+ cause: err,
408
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
409
+ message: err.message
410
+ }, HttpStatus.INTERNAL_SERVER_ERROR);
411
+ }
412
+ }
413
+ /**
414
+ * 基于模拟角色获取权限数据(不使用缓存)
415
+ * 该方法用于前端/守卫在检测到 mockRoles 时直接按角色获取权限
416
+ */
417
+ async getPermissionsByMockRoles(userId, mockRoles) {
418
+ const { baseUrl, timeout = 5e3 } = this.apiConfig || {};
419
+ const rolesParam = encodeURIComponent(mockRoles.join(","));
420
+ const userParam = userId ? `&userId=${encodeURIComponent(userId)}` : "";
421
+ const url = `${baseUrl}/mock-api/permissions-by-roles?roles=${rolesParam}${userParam}`;
422
+ const controller = new AbortController();
423
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
424
+ try {
425
+ const response = await fetch(url, {
426
+ method: "GET",
427
+ signal: controller.signal
428
+ });
429
+ clearTimeout(timeoutId);
430
+ if (!response.ok) {
431
+ throw new Error(`Permission API (mock by roles) returned ${response.status}: ${response.statusText}`);
432
+ }
433
+ const data = await response.json();
434
+ return {
435
+ userId,
436
+ roles: data.roles || [],
437
+ permissions: data.permissions || [],
438
+ fetchedAt: /* @__PURE__ */ new Date()
439
+ };
440
+ } catch (error) {
441
+ clearTimeout(timeoutId);
442
+ throw new PermissionDeniedException({
443
+ cause: error,
444
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
445
+ message: error.message
446
+ }, HttpStatus.INTERNAL_SERVER_ERROR);
447
+ }
448
+ }
449
+ /**
450
+ * 获取用户的 Ability 实例(带缓存)
451
+ * @param userId 用户ID
452
+ * @returns CASL Ability 实例
453
+ */
454
+ async getUserAbility(userId, mockRoles) {
455
+ const key = this.buildCacheKey(userId, mockRoles);
456
+ const cached = this.cache.get(key);
457
+ if (cached) {
458
+ return cached.ability;
459
+ }
460
+ await this.getUserPermissions(userId, mockRoles);
461
+ const newCached = this.cache.get(key);
462
+ return newCached.ability;
463
+ }
464
+ /**
465
+ * 清除用户权限缓存
466
+ */
467
+ clearUserCache(userId) {
468
+ const key = userId || ANONYMOUS_USER_ID;
469
+ this.cache.delete(key);
470
+ this.pendingRequests.delete(key);
471
+ this.logger.debug(`Cache cleared for user ${key}`);
472
+ }
473
+ /**
474
+ * 清除所有缓存
475
+ */
476
+ clearAllCache() {
477
+ this.cache.clear();
478
+ this.pendingRequests.clear();
479
+ this.logger.log("All permission cache cleared");
480
+ }
481
+ /**
482
+ * 获取缓存统计信息
483
+ */
484
+ getCacheStats() {
485
+ return this.cache.getStats();
486
+ }
487
+ /**
488
+ * 检查角色要求
489
+ * 使用 CASL Ability 统一鉴权方式
490
+ * @param requirement 角色要求
491
+ * @param userId 用户ID,匿名用户时为空
492
+ * @returns 用户权限数据
493
+ * @throws PermissionDeniedException 当角色不满足时
494
+ */
495
+ async checkRoles(requirement, userId, mockRoles) {
496
+ const permissionData = await this.getUserPermissions(userId, mockRoles);
497
+ if (!permissionData) {
498
+ throw new PermissionDeniedException({
499
+ cause: new Error("Permission data fetch api is not configured"),
500
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
501
+ message: "Permission data fetch api is not configured"
502
+ }, HttpStatus.BAD_REQUEST);
503
+ }
504
+ const ability = await this.getUserAbility(userId, mockRoles);
505
+ const { roles, and } = requirement;
506
+ const checkResults = roles.map((role) => ability.can(role, ROLE_SUBJECT));
507
+ const hasRole = and ? checkResults.every((result) => result) : checkResults.some((result) => result);
508
+ if (!hasRole) {
509
+ const userRoles = permissionData.roles;
510
+ this.logger.warn(`\u89D2\u8272\u68C0\u67E5\u5931\u8D25: \u7528\u6237 ${userId}, \u7528\u6237\u89D2\u8272 [${userRoles.join(", ")}], \u9700\u8981 [${roles.join(", ")}]`);
511
+ throw PermissionDeniedException.roleRequired(roles, and);
512
+ }
513
+ return permissionData;
514
+ }
515
+ /**
516
+ * 检查权限要求
517
+ * @param requirements 权限要求列表
518
+ * @param userId 用户ID
519
+ * @returns 用户权限数据
520
+ * @throws PermissionDeniedException 当权限不满足时
521
+ */
522
+ async checkPermissions(params, userId, mockRoles) {
523
+ const permissionData = await this.getUserPermissions(userId, mockRoles);
524
+ if (!permissionData) {
525
+ throw new PermissionDeniedException({
526
+ cause: new Error("Permission data fetch api is not configured"),
527
+ type: PermissionDeniedType.PERMISSION_CONFIG_QUERY_FAILED,
528
+ message: "Permission data fetch api is not configured"
529
+ }, HttpStatus.BAD_REQUEST);
530
+ }
531
+ const { requirements, or } = params;
532
+ if (!requirements || requirements.length === 0) {
533
+ return permissionData;
534
+ }
535
+ const ability = await this.getUserAbility(userId, mockRoles);
536
+ const failedRequirements = [];
537
+ for (const requirement of requirements) {
538
+ const { actions, subject, or: or2 = false } = requirement;
539
+ const checkResults = actions.map((action) => ability.can(action, subject));
540
+ const hasPermission = or2 ? checkResults.some((result) => result) : checkResults.every((result) => result);
541
+ if (!hasPermission) {
542
+ failedRequirements.push({
543
+ actions,
544
+ subject: String(subject),
545
+ or: or2
546
+ });
547
+ }
548
+ }
549
+ if (failedRequirements.length > 0) {
550
+ if (or && failedRequirements.length === requirements.length) {
551
+ throw PermissionDeniedException.permissionRequired(failedRequirements.map(({ actions, subject }) => ({
552
+ actions,
553
+ subject: String(subject)
554
+ })), true);
555
+ } else if (!or) {
556
+ throw PermissionDeniedException.permissionRequired(failedRequirements.map(({ actions, subject }) => ({
557
+ actions,
558
+ subject: String(subject)
559
+ })), false);
560
+ }
561
+ }
562
+ return permissionData;
563
+ }
564
+ async getAbility(userId) {
565
+ return this.getUserAbility(userId);
566
+ }
567
+ };
568
+ PermissionService = _ts_decorate2([
569
+ Injectable2(),
570
+ _ts_param(0, Inject(PERMISSION_API_CONFIG_TOKEN)),
571
+ _ts_param(1, Inject(CACHE_CONFIG_TOKEN)),
572
+ _ts_metadata("design:type", Function),
573
+ _ts_metadata("design:paramtypes", [
574
+ typeof PermissionApiConfig === "undefined" ? Object : PermissionApiConfig,
575
+ typeof CacheConfig === "undefined" ? Object : CacheConfig,
576
+ typeof AbilityFactory === "undefined" ? Object : AbilityFactory
577
+ ])
578
+ ], PermissionService);
579
+
580
+ // src/guards/authzpaas.guard.ts
581
+ import { Injectable as Injectable3, Inject as Inject2 } from "@nestjs/common";
582
+ import { Reflector } from "@nestjs/core";
583
+
584
+ // src/utils/index.ts
585
+ function getMockRolesFromCookie(request) {
586
+ const mockRoles = request.cookies?.[MOCK_ROLES_COOKIE_KEY];
587
+ return mockRoles ? mockRoles.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
588
+ }
589
+ __name(getMockRolesFromCookie, "getMockRolesFromCookie");
590
+
591
+ // src/guards/authzpaas.guard.ts
592
+ function _ts_decorate3(decorators, target, key, desc) {
593
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
594
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
595
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
596
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
597
+ }
598
+ __name(_ts_decorate3, "_ts_decorate");
599
+ function _ts_metadata2(k, v) {
600
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
601
+ }
602
+ __name(_ts_metadata2, "_ts_metadata");
603
+ function _ts_param2(paramIndex, decorator) {
604
+ return function(target, key) {
605
+ decorator(target, key, paramIndex);
606
+ };
607
+ }
608
+ __name(_ts_param2, "_ts_param");
609
+ var AuthZPaasGuard = class {
610
+ static {
611
+ __name(this, "AuthZPaasGuard");
612
+ }
613
+ reflector;
614
+ permissionService;
615
+ moduleOptions;
616
+ constructor(reflector, permissionService, moduleOptions) {
617
+ this.reflector = reflector;
618
+ this.permissionService = permissionService;
619
+ this.moduleOptions = moduleOptions;
620
+ }
621
+ async canActivate(context) {
622
+ const http = context.switchToHttp();
623
+ const request = http.getRequest();
624
+ request[ENABLE_MOCK_ROLE_KEY] = this.moduleOptions?.enableMockRole;
625
+ const userId = this.extractUserId(request);
626
+ const mockRoles = this.moduleOptions?.enableMockRole ? getMockRolesFromCookie(request) : void 0;
627
+ const checkRoleRequirement = this.reflector.getAllAndOverride(ROLES_KEY, [
628
+ context.getHandler(),
629
+ context.getClass()
630
+ ]);
631
+ if (checkRoleRequirement) {
632
+ await this.checkRoleRequirement(checkRoleRequirement, userId, mockRoles);
633
+ }
634
+ const checkPermissionParams = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [
635
+ context.getHandler(),
636
+ context.getClass()
637
+ ]);
638
+ if (checkPermissionParams) {
639
+ await this.checkPermissionRequirement(checkPermissionParams, userId, mockRoles);
640
+ }
641
+ const envRequirement = this.reflector.getAllAndOverride(ENVIRONMENT_KEY, [
642
+ context.getHandler(),
643
+ context.getClass()
644
+ ]);
645
+ if (envRequirement) {
646
+ const envContext = this.extractEnvironmentContext(request);
647
+ await this.checkEnvironmentRequirement(envRequirement, envContext, request);
648
+ }
649
+ return true;
650
+ }
651
+ /**
652
+ * 从请求中提取用户ID
653
+ * 子类可以重写此方法以适应不同的认证策略
654
+ */
655
+ extractUserId(request) {
656
+ const mockUserId = request.cookies?.mockUserId;
657
+ if (mockUserId) return mockUserId;
658
+ return request.userContext?.userId;
659
+ }
660
+ /**
661
+ * 从请求中提取环境上下文
662
+ */
663
+ extractEnvironmentContext(request) {
664
+ const regionHeader = request.headers["x-region"];
665
+ const osHeader = request.headers["x-os"];
666
+ const uaHeader = request.headers["user-agent"];
667
+ const region = Array.isArray(regionHeader) ? regionHeader[0] : regionHeader;
668
+ const os = Array.isArray(osHeader) ? osHeader[0] : osHeader;
669
+ const userAgent = Array.isArray(uaHeader) ? uaHeader[0] : uaHeader;
670
+ return {
671
+ network: {
672
+ ip: request.ip || request.connection?.remoteAddress,
673
+ region
674
+ },
675
+ device: {
676
+ type: this.detectDeviceType(userAgent),
677
+ os,
678
+ browser: userAgent
679
+ },
680
+ custom: {
681
+ headers: request.headers,
682
+ query: request.query
683
+ }
684
+ };
685
+ }
686
+ /**
687
+ * 检测设备类型
688
+ */
689
+ detectDeviceType(userAgent) {
690
+ if (!userAgent) return "desktop";
691
+ const ua = userAgent.toLowerCase();
692
+ if (/(tablet|ipad|playbook|silk)|(android(?!.*mobile))/i.test(ua)) {
693
+ return "tablet";
694
+ }
695
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) {
696
+ return "mobile";
697
+ }
698
+ return "desktop";
699
+ }
700
+ /**
701
+ * 检查角色要求
702
+ */
703
+ async checkRoleRequirement(requirement, userId, mockRoles) {
704
+ return this.permissionService.checkRoles(requirement, userId, mockRoles);
705
+ }
706
+ /**
707
+ * 检查权限要求
708
+ */
709
+ async checkPermissionRequirement(params, userId, mockRoles) {
710
+ return this.permissionService.checkPermissions(params, userId, mockRoles);
711
+ }
712
+ /**
713
+ * 检查环境要求
714
+ */
715
+ async checkEnvironmentRequirement(requirement, envContext, request) {
716
+ if (requirement.network) {
717
+ await this.checkNetworkRequirement(requirement.network, envContext);
718
+ }
719
+ if (requirement.device) {
720
+ await this.checkDeviceRequirement(requirement.device, envContext);
721
+ }
722
+ if (requirement.custom) {
723
+ const result = await requirement.custom(request);
724
+ if (!result) {
725
+ throw PermissionDeniedException.environmentRequired("custom", "\u4E0D\u6EE1\u8DB3\u81EA\u5B9A\u4E49\u73AF\u5883\u8981\u6C42");
726
+ }
727
+ }
728
+ }
729
+ /**
730
+ * 检查网络要求
731
+ */
732
+ async checkNetworkRequirement(requirement, envContext) {
733
+ const ip = envContext.network?.ip;
734
+ if (requirement.allowedIPs && ip) {
735
+ const isAllowed = this.checkIPMatch(ip, requirement.allowedIPs);
736
+ if (!isAllowed) {
737
+ throw PermissionDeniedException.environmentRequired("network.allowedIPs", `IP \u5730\u5740\u4E0D\u5728\u5141\u8BB8\u5217\u8868\u4E2D: ${ip}`);
738
+ }
739
+ }
740
+ if (requirement.blockedIPs && ip) {
741
+ const isBlocked = this.checkIPMatch(ip, requirement.blockedIPs);
742
+ if (isBlocked) {
743
+ throw PermissionDeniedException.environmentRequired("network.blockedIPs", `IP \u5730\u5740\u88AB\u7981\u6B62\u8BBF\u95EE: ${ip}`);
744
+ }
745
+ }
746
+ if (requirement.allowedRegions && envContext.network?.region) {
747
+ const isAllowed = requirement.allowedRegions.includes(envContext.network.region);
748
+ if (!isAllowed) {
749
+ throw PermissionDeniedException.environmentRequired("network.allowedRegions", `\u5730\u533A\u4E0D\u5728\u5141\u8BB8\u5217\u8868\u4E2D: ${envContext.network.region}`);
750
+ }
751
+ }
752
+ }
753
+ /**
754
+ * 检查设备要求
755
+ */
756
+ async checkDeviceRequirement(requirement, envContext) {
757
+ if (requirement.types && envContext.device?.type) {
758
+ const isAllowed = requirement.types.includes(envContext.device.type);
759
+ if (!isAllowed) {
760
+ throw PermissionDeniedException.environmentRequired("device.types", `\u4E0D\u5141\u8BB8\u7684\u8BBE\u5907\u7C7B\u578B: ${envContext.device.type}`);
761
+ }
762
+ }
763
+ }
764
+ /**
765
+ * 检查 IP 是否匹配
766
+ * 简化版本,仅支持精确匹配
767
+ * 生产环境建议使用 ipaddr.js 等库
768
+ */
769
+ checkIPMatch(ip, patterns) {
770
+ return patterns.some((pattern) => {
771
+ if (pattern === ip) return true;
772
+ return false;
773
+ });
774
+ }
775
+ };
776
+ AuthZPaasGuard = _ts_decorate3([
777
+ Injectable3(),
778
+ _ts_param2(2, Inject2(AUTHZPAAS_MODULE_OPTIONS)),
779
+ _ts_metadata2("design:type", Function),
780
+ _ts_metadata2("design:paramtypes", [
781
+ typeof Reflector === "undefined" ? Object : Reflector,
782
+ typeof PermissionService === "undefined" ? Object : PermissionService,
783
+ typeof AuthZPaasModuleOptions === "undefined" ? Object : AuthZPaasModuleOptions
784
+ ])
785
+ ], AuthZPaasGuard);
786
+
787
+ // src/middlewares/roles.middleware.ts
788
+ import { Injectable as Injectable4, Logger as Logger2 } from "@nestjs/common";
789
+ function _ts_decorate4(decorators, target, key, desc) {
790
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
791
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
792
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
793
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
794
+ }
795
+ __name(_ts_decorate4, "_ts_decorate");
796
+ function _ts_metadata3(k, v) {
797
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
798
+ }
799
+ __name(_ts_metadata3, "_ts_metadata");
800
+ var RolesMiddleware = class _RolesMiddleware {
801
+ static {
802
+ __name(this, "RolesMiddleware");
803
+ }
804
+ permissionService;
805
+ logger = new Logger2(_RolesMiddleware.name);
806
+ constructor(permissionService) {
807
+ this.permissionService = permissionService;
808
+ }
809
+ async use(req, _res, next) {
810
+ try {
811
+ const userId = req.userContext?.userId;
812
+ const mockRoles = req[ENABLE_MOCK_ROLE_KEY] ? getMockRolesFromCookie(req) : void 0;
813
+ this.logger.debug(`Fetching roles for user: ${userId}`);
814
+ const permissionData = await this.permissionService.getUserPermissions(userId, mockRoles);
815
+ let roles;
816
+ if (permissionData) {
817
+ roles = permissionData.roles;
818
+ }
819
+ req.userContext.userRoles = roles;
820
+ req.userContext.userId = userId;
821
+ this.logger.debug(`User ${userId} roles loaded: [${roles?.join(", ")}]`);
822
+ next();
823
+ } catch (error) {
824
+ this.logger.warn(`Failed to fetch roles for request: ${error instanceof Error ? error.message : "Unknown error"}`);
825
+ next();
826
+ }
827
+ }
828
+ };
829
+ RolesMiddleware = _ts_decorate4([
830
+ Injectable4(),
831
+ _ts_metadata3("design:type", Function),
832
+ _ts_metadata3("design:paramtypes", [
833
+ typeof PermissionService === "undefined" ? Object : PermissionService
834
+ ])
835
+ ], RolesMiddleware);
836
+
837
+ // src/filters/authzpaas-exception.filter.ts
838
+ import { Catch } from "@nestjs/common";
839
+ function _ts_decorate5(decorators, target, key, desc) {
840
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
841
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
842
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
843
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
844
+ }
845
+ __name(_ts_decorate5, "_ts_decorate");
846
+ var AuthZPaasExceptionFilter = class {
847
+ static {
848
+ __name(this, "AuthZPaasExceptionFilter");
849
+ }
850
+ catch(exception, host) {
851
+ const ctx = host.switchToHttp();
852
+ const response = ctx.getResponse();
853
+ const status = exception.getStatus();
854
+ const exceptionResponse = exception.getResponse();
855
+ const errorResponse = {
856
+ statusCode: status,
857
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
858
+ cause: exception.cause,
859
+ type: exception.type,
860
+ message: typeof exceptionResponse === "string" ? exceptionResponse : exceptionResponse.message || "\u6743\u9650\u4E0D\u8DB3"
861
+ };
862
+ if (exception.details.requiredRoles) {
863
+ errorResponse.requiredRoles = exception.details.requiredRoles;
864
+ }
865
+ if (exception.details.requiredPermissions) {
866
+ errorResponse.requiredPermissions = exception.details.requiredPermissions;
867
+ }
868
+ if (exception.details.environmentRequirement) {
869
+ errorResponse.environmentRequirement = exception.details.environmentRequirement;
870
+ }
871
+ if (exception.details.metadata) {
872
+ errorResponse.metadata = exception.details.metadata;
873
+ }
874
+ response.status(status).json(errorResponse);
875
+ }
876
+ };
877
+ AuthZPaasExceptionFilter = _ts_decorate5([
878
+ Catch(PermissionDeniedException)
879
+ ], AuthZPaasExceptionFilter);
880
+
881
+ // src/authzpaas.module.ts
882
+ function _ts_decorate6(decorators, target, key, desc) {
883
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
884
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
885
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
886
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
887
+ }
888
+ __name(_ts_decorate6, "_ts_decorate");
889
+ var AuthZPaasModule = class _AuthZPaasModule {
890
+ static {
891
+ __name(this, "AuthZPaasModule");
892
+ }
893
+ static forRoot(options) {
894
+ const { permissionApi, cache = {
895
+ ttl: 300,
896
+ max: 1e3,
897
+ enabled: true
898
+ }, isGlobal = true } = options;
899
+ return {
900
+ module: _AuthZPaasModule,
901
+ global: isGlobal,
902
+ controllers: [],
903
+ providers: [
904
+ // 配置提供者
905
+ {
906
+ provide: AUTHZPAAS_MODULE_OPTIONS,
907
+ useValue: {
908
+ ...options,
909
+ loginPath: options.loginPath || DEFAULT_LOGIN_PATH
910
+ }
911
+ },
912
+ {
913
+ provide: PERMISSION_API_CONFIG_TOKEN,
914
+ useValue: permissionApi
915
+ },
916
+ {
917
+ provide: CACHE_CONFIG_TOKEN,
918
+ useValue: cache
919
+ },
920
+ // 核心服务
921
+ Reflector2,
922
+ // 服务提供者
923
+ PermissionService,
924
+ AbilityFactory,
925
+ AuthZPaasGuard,
926
+ RolesMiddleware,
927
+ // 守卫提供者
928
+ {
929
+ provide: APP_GUARD,
930
+ useClass: AuthZPaasGuard
931
+ },
932
+ {
933
+ provide: APP_FILTER,
934
+ useClass: AuthZPaasExceptionFilter
935
+ }
936
+ ],
937
+ exports: [
938
+ PermissionService,
939
+ AbilityFactory,
940
+ RolesMiddleware
941
+ ]
942
+ };
943
+ }
944
+ /**
945
+ * 异步注册 AuthZPaas 模块(根模块)
946
+ * 用于需要从配置服务获取设置的场景
947
+ *
948
+ * @param options 异步配置选项
949
+ * @returns 动态模块
950
+ *
951
+ * @example
952
+ * ```typescript
953
+ * @Module({
954
+ * imports: [
955
+ * AuthZPaasModule.forRootAsync({
956
+ * imports: [ConfigModule],
957
+ * inject: [ConfigService],
958
+ * useFactory: async (configService: ConfigService) => ({
959
+ * permissionApi: {
960
+ * baseUrl: configService.get('PERMISSION_API_URL'),
961
+ * apiToken: configService.get('PERMISSION_API_TOKEN'),
962
+ * },
963
+ * cache: {
964
+ * ttl: configService.get('CACHE_TTL', 300),
965
+ * max: configService.get('CACHE_MAX', 1000),
966
+ * },
967
+ * }),
968
+ * }),
969
+ * ],
970
+ * })
971
+ * export class AppModule {}
972
+ * ```
973
+ */
974
+ static forRootAsync(options) {
975
+ const { imports = [], inject = [], useFactory, isGlobal = true } = options;
976
+ return {
977
+ module: _AuthZPaasModule,
978
+ global: isGlobal,
979
+ imports,
980
+ controllers: [],
981
+ providers: [
982
+ // 异步配置提供者
983
+ {
984
+ provide: AUTHZPAAS_MODULE_OPTIONS,
985
+ useFactory,
986
+ inject
987
+ },
988
+ // 权限 API 配置提供者
989
+ {
990
+ provide: PERMISSION_API_CONFIG_TOKEN,
991
+ useFactory: /* @__PURE__ */ __name((moduleOptions) => {
992
+ return moduleOptions.permissionApi;
993
+ }, "useFactory"),
994
+ inject: [
995
+ AUTHZPAAS_MODULE_OPTIONS
996
+ ]
997
+ },
998
+ // 缓存配置提供者
999
+ {
1000
+ provide: CACHE_CONFIG_TOKEN,
1001
+ useFactory: /* @__PURE__ */ __name((moduleOptions) => {
1002
+ return moduleOptions.cache || {
1003
+ ttl: 300,
1004
+ max: 1e3,
1005
+ enabled: true
1006
+ };
1007
+ }, "useFactory"),
1008
+ inject: [
1009
+ AUTHZPAAS_MODULE_OPTIONS
1010
+ ]
1011
+ },
1012
+ // 核心服务
1013
+ Reflector2,
1014
+ // 服务提供者
1015
+ PermissionService,
1016
+ AbilityFactory,
1017
+ AuthZPaasGuard,
1018
+ RolesMiddleware,
1019
+ // 守卫提供者
1020
+ {
1021
+ provide: APP_GUARD,
1022
+ useClass: AuthZPaasGuard
1023
+ },
1024
+ {
1025
+ provide: APP_FILTER,
1026
+ useClass: AuthZPaasExceptionFilter
1027
+ }
1028
+ ],
1029
+ exports: [
1030
+ PermissionService,
1031
+ AbilityFactory,
1032
+ RolesMiddleware
1033
+ ]
1034
+ };
1035
+ }
1036
+ };
1037
+ AuthZPaasModule = _ts_decorate6([
1038
+ Module({})
1039
+ ], AuthZPaasModule);
1040
+
1041
+ // src/decorators/can-role.decorator.ts
1042
+ import { SetMetadata } from "@nestjs/common";
1043
+ var CanRole = /* @__PURE__ */ __name((role, and = false) => {
1044
+ let requirement;
1045
+ if (!Array.isArray(role) && typeof role === "string") {
1046
+ requirement = {
1047
+ roles: [
1048
+ role
1049
+ ],
1050
+ and
1051
+ };
1052
+ } else if (Array.isArray(role) && role.some((role2) => typeof role2 === "string")) {
1053
+ requirement = {
1054
+ roles: role,
1055
+ and
1056
+ };
1057
+ } else {
1058
+ throw new Error("Invalid CanRole parameter: " + JSON.stringify(role));
1059
+ }
1060
+ return SetMetadata(ROLES_KEY, requirement);
1061
+ }, "CanRole");
1062
+
1063
+ // src/decorators/can-permission.decorator.ts
1064
+ import { SetMetadata as SetMetadata2 } from "@nestjs/common";
1065
+ var CanPermission = /* @__PURE__ */ __name((permission, or) => {
1066
+ let requirements;
1067
+ if (Array.isArray(permission)) {
1068
+ requirements = permission;
1069
+ } else if (typeof permission === "object" && "subject" in permission) {
1070
+ requirements = [
1071
+ permission
1072
+ ];
1073
+ } else {
1074
+ throw new Error("Invalid CanPermission parameter: " + JSON.stringify(permission));
1075
+ }
1076
+ return SetMetadata2(PERMISSIONS_KEY, {
1077
+ requirements,
1078
+ or: or ?? false
1079
+ });
1080
+ }, "CanPermission");
1081
+
1082
+ // src/decorators/can-env.decorator.ts
1083
+ import { SetMetadata as SetMetadata3 } from "@nestjs/common";
1084
+ var CanEnv = /* @__PURE__ */ __name((requirement) => {
1085
+ return SetMetadata3(ENVIRONMENT_KEY, requirement);
1086
+ }, "CanEnv");
1087
+
1088
+ // src/decorators/user-id.decorator.ts
1089
+ import { createParamDecorator } from "@nestjs/common";
1090
+ var UserId = createParamDecorator((_data, ctx) => {
1091
+ const request = ctx.switchToHttp().getRequest();
1092
+ return request.userContext?.userId;
1093
+ });
1094
+
1095
+ // src/decorators/mock-roles.decorator.ts
1096
+ import { createParamDecorator as createParamDecorator2 } from "@nestjs/common";
1097
+ var MockRoles = createParamDecorator2((_data, ctx) => {
1098
+ const request = ctx.switchToHttp().getRequest();
1099
+ const mockRoles = getMockRolesFromCookie(request);
1100
+ return request[ENABLE_MOCK_ROLE_KEY] ? mockRoles : void 0;
1101
+ });
1102
+
1103
+ // src/controllers/permission.controller.ts
1104
+ import { Controller, Get, Post, Res, Body } from "@nestjs/common";
1105
+ import { ApiOperation, ApiResponse, ApiTags, ApiProperty } from "@nestjs/swagger";
1106
+ function _ts_decorate7(decorators, target, key, desc) {
1107
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1108
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1109
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1110
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1111
+ }
1112
+ __name(_ts_decorate7, "_ts_decorate");
1113
+ function _ts_metadata4(k, v) {
1114
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1115
+ }
1116
+ __name(_ts_metadata4, "_ts_metadata");
1117
+ function _ts_param3(paramIndex, decorator) {
1118
+ return function(target, key) {
1119
+ decorator(target, key, paramIndex);
1120
+ };
1121
+ }
1122
+ __name(_ts_param3, "_ts_param");
1123
+ var PermissionDto = class {
1124
+ static {
1125
+ __name(this, "PermissionDto");
1126
+ }
1127
+ sub;
1128
+ actions;
1129
+ id;
1130
+ name;
1131
+ conditions;
1132
+ };
1133
+ _ts_decorate7([
1134
+ ApiProperty({
1135
+ description: "\u8D44\u6E90\u7C7B\u578B",
1136
+ example: "task"
1137
+ }),
1138
+ _ts_metadata4("design:type", String)
1139
+ ], PermissionDto.prototype, "sub", void 0);
1140
+ _ts_decorate7([
1141
+ ApiProperty({
1142
+ description: "\u64CD\u4F5C\u7C7B\u578B\u5217\u8868",
1143
+ example: [
1144
+ "create",
1145
+ "read",
1146
+ "update",
1147
+ "delete"
1148
+ ],
1149
+ type: [
1150
+ String
1151
+ ]
1152
+ }),
1153
+ _ts_metadata4("design:type", Array)
1154
+ ], PermissionDto.prototype, "actions", void 0);
1155
+ _ts_decorate7([
1156
+ ApiProperty({
1157
+ description: "\u6743\u9650ID",
1158
+ required: false
1159
+ }),
1160
+ _ts_metadata4("design:type", String)
1161
+ ], PermissionDto.prototype, "id", void 0);
1162
+ _ts_decorate7([
1163
+ ApiProperty({
1164
+ description: "\u6743\u9650\u540D\u79F0",
1165
+ required: false
1166
+ }),
1167
+ _ts_metadata4("design:type", String)
1168
+ ], PermissionDto.prototype, "name", void 0);
1169
+ _ts_decorate7([
1170
+ ApiProperty({
1171
+ description: "\u6743\u9650\u6761\u4EF6",
1172
+ required: false,
1173
+ type: "object"
1174
+ }),
1175
+ _ts_metadata4("design:type", typeof Record === "undefined" ? Object : Record)
1176
+ ], PermissionDto.prototype, "conditions", void 0);
1177
+ var PermissionResponse = class {
1178
+ static {
1179
+ __name(this, "PermissionResponse");
1180
+ }
1181
+ userId;
1182
+ roles;
1183
+ permissions;
1184
+ fetchedAt;
1185
+ };
1186
+ _ts_decorate7([
1187
+ ApiProperty({
1188
+ description: "\u7528\u6237ID",
1189
+ example: "user123",
1190
+ required: false
1191
+ }),
1192
+ _ts_metadata4("design:type", String)
1193
+ ], PermissionResponse.prototype, "userId", void 0);
1194
+ _ts_decorate7([
1195
+ ApiProperty({
1196
+ description: "\u7528\u6237\u89D2\u8272\u5217\u8868",
1197
+ example: [
1198
+ "admin",
1199
+ "user"
1200
+ ],
1201
+ type: [
1202
+ String
1203
+ ]
1204
+ }),
1205
+ _ts_metadata4("design:type", Array)
1206
+ ], PermissionResponse.prototype, "roles", void 0);
1207
+ _ts_decorate7([
1208
+ ApiProperty({
1209
+ description: "\u7528\u6237\u6743\u9650\u70B9\u4F4D\u5217\u8868",
1210
+ type: [
1211
+ PermissionDto
1212
+ ]
1213
+ }),
1214
+ _ts_metadata4("design:type", Array)
1215
+ ], PermissionResponse.prototype, "permissions", void 0);
1216
+ _ts_decorate7([
1217
+ ApiProperty({
1218
+ description: "\u6570\u636E\u83B7\u53D6\u65F6\u95F4",
1219
+ example: "2025-10-14T00:00:00.000Z"
1220
+ }),
1221
+ _ts_metadata4("design:type", typeof Date === "undefined" ? Object : Date)
1222
+ ], PermissionResponse.prototype, "fetchedAt", void 0);
1223
+ var PermissionController = class {
1224
+ static {
1225
+ __name(this, "PermissionController");
1226
+ }
1227
+ permissionService;
1228
+ constructor(permissionService) {
1229
+ this.permissionService = permissionService;
1230
+ }
1231
+ /**
1232
+ * 获取当前用户的权限数据
1233
+ *
1234
+ * @param userId 当前用户ID(从请求上下文中获取)
1235
+ * @returns 用户权限数据
1236
+ *
1237
+ * @example
1238
+ * GET /api/permissions
1239
+ *
1240
+ * Response:
1241
+ * {
1242
+ * "userId": "user123",
1243
+ * "roles": ["admin", "user"],
1244
+ * "permissions": [
1245
+ * { "sub": "task", "actions": ["create", "read", "update", "delete"] }
1246
+ * ],
1247
+ * "fetchedAt": "2025-10-14T00:00:00.000Z"
1248
+ * }
1249
+ */
1250
+ async getUserPermissions(userId, mockRoles) {
1251
+ const permissionData = await this.permissionService.getUserPermissions(userId, mockRoles);
1252
+ return {
1253
+ userId: permissionData?.userId,
1254
+ roles: permissionData?.roles || [],
1255
+ permissions: permissionData?.permissions || [],
1256
+ fetchedAt: permissionData?.fetchedAt || /* @__PURE__ */ new Date()
1257
+ };
1258
+ }
1259
+ /**
1260
+ * 开启角色模拟:将传入的 userId 写入 cookie,服务端优先使用该值请求权限
1261
+ */
1262
+ async enableMock(res, roles) {
1263
+ if (!Array.isArray(roles) || roles.length === 0) {
1264
+ return {
1265
+ success: false,
1266
+ message: "roles \u4E0D\u80FD\u4E3A\u7A7A"
1267
+ };
1268
+ }
1269
+ const val = roles.join(",");
1270
+ res.cookie(MOCK_ROLES_COOKIE_KEY, val, {
1271
+ httpOnly: false,
1272
+ sameSite: "lax",
1273
+ maxAge: 24 * 60 * 60 * 1e3,
1274
+ path: "/"
1275
+ });
1276
+ return {
1277
+ success: true,
1278
+ roles
1279
+ };
1280
+ }
1281
+ /**
1282
+ * 关闭角色模拟:清除 cookie
1283
+ */
1284
+ async disableMock(res) {
1285
+ res.clearCookie(MOCK_ROLES_COOKIE_KEY, {
1286
+ path: "/"
1287
+ });
1288
+ return {
1289
+ success: true
1290
+ };
1291
+ }
1292
+ async getMockRoles(mockRoles, userId) {
1293
+ const permissionData = await this.permissionService.getUserPermissions(userId, mockRoles);
1294
+ return {
1295
+ mocking: !!mockRoles,
1296
+ roles: permissionData?.roles || [],
1297
+ permissions: permissionData?.permissions || [],
1298
+ fetchedAt: permissionData?.fetchedAt || /* @__PURE__ */ new Date(),
1299
+ userId
1300
+ };
1301
+ }
1302
+ };
1303
+ _ts_decorate7([
1304
+ Get(),
1305
+ ApiOperation({
1306
+ summary: "\u83B7\u53D6\u5F53\u524D\u7528\u6237\u7684\u6743\u9650\u6570\u636E"
1307
+ }),
1308
+ ApiResponse({
1309
+ status: 200,
1310
+ description: "\u6210\u529F\u8FD4\u56DE\u7528\u6237\u6743\u9650\u6570\u636E",
1311
+ type: PermissionResponse
1312
+ }),
1313
+ ApiResponse({
1314
+ status: 400,
1315
+ description: "\u7528\u6237\u672A\u8BA4\u8BC1"
1316
+ }),
1317
+ _ts_param3(0, UserId()),
1318
+ _ts_param3(1, MockRoles()),
1319
+ _ts_metadata4("design:type", Function),
1320
+ _ts_metadata4("design:paramtypes", [
1321
+ String,
1322
+ Array
1323
+ ]),
1324
+ _ts_metadata4("design:returntype", Promise)
1325
+ ], PermissionController.prototype, "getUserPermissions", null);
1326
+ _ts_decorate7([
1327
+ Post("mock/enable"),
1328
+ ApiOperation({
1329
+ summary: "\u5F00\u542F\u89D2\u8272\u6A21\u62DF\uFF08\u5199\u5165 cookie: mockRoles\uFF09"
1330
+ }),
1331
+ ApiResponse({
1332
+ status: 200,
1333
+ description: "\u6A21\u62DF\u5DF2\u5F00\u542F"
1334
+ }),
1335
+ _ts_param3(0, Res({
1336
+ passthrough: true
1337
+ })),
1338
+ _ts_param3(1, Body("roles")),
1339
+ _ts_metadata4("design:type", Function),
1340
+ _ts_metadata4("design:paramtypes", [
1341
+ typeof Response === "undefined" ? Object : Response,
1342
+ Array
1343
+ ]),
1344
+ _ts_metadata4("design:returntype", Promise)
1345
+ ], PermissionController.prototype, "enableMock", null);
1346
+ _ts_decorate7([
1347
+ Post("mock/disable"),
1348
+ ApiOperation({
1349
+ summary: "\u5173\u95ED\u89D2\u8272\u6A21\u62DF\uFF08\u6E05\u9664 cookie: mockRoles\uFF09"
1350
+ }),
1351
+ ApiResponse({
1352
+ status: 200,
1353
+ description: "\u6A21\u62DF\u5DF2\u5173\u95ED"
1354
+ }),
1355
+ _ts_param3(0, Res({
1356
+ passthrough: true
1357
+ })),
1358
+ _ts_metadata4("design:type", Function),
1359
+ _ts_metadata4("design:paramtypes", [
1360
+ typeof Response === "undefined" ? Object : Response
1361
+ ]),
1362
+ _ts_metadata4("design:returntype", Promise)
1363
+ ], PermissionController.prototype, "disableMock", null);
1364
+ _ts_decorate7([
1365
+ Get("mock/status"),
1366
+ ApiOperation({
1367
+ summary: "\u83B7\u53D6\u5F53\u524D\u7528\u6237\u7684\u89D2\u8272\u6A21\u62DF\u72B6\u6001"
1368
+ }),
1369
+ ApiResponse({
1370
+ status: 200,
1371
+ description: "\u6210\u529F\u8FD4\u56DE\u89D2\u8272\u6A21\u62DF\u6570\u636E"
1372
+ }),
1373
+ _ts_param3(0, MockRoles()),
1374
+ _ts_param3(1, UserId()),
1375
+ _ts_metadata4("design:type", Function),
1376
+ _ts_metadata4("design:paramtypes", [
1377
+ Object,
1378
+ String
1379
+ ]),
1380
+ _ts_metadata4("design:returntype", Promise)
1381
+ ], PermissionController.prototype, "getMockRoles", null);
1382
+ PermissionController = _ts_decorate7([
1383
+ ApiTags("\u6743\u9650\u7BA1\u7406"),
1384
+ Controller("api/permissions"),
1385
+ _ts_metadata4("design:type", Function),
1386
+ _ts_metadata4("design:paramtypes", [
1387
+ typeof PermissionService === "undefined" ? Object : PermissionService
1388
+ ])
1389
+ ], PermissionController);
1390
+ export {
1391
+ ANONYMOUS_USER_ID,
1392
+ AUTHZPAAS_MODULE_OPTIONS,
1393
+ AbilityFactory,
1394
+ AuthZPaasExceptionFilter,
1395
+ AuthZPaasGuard,
1396
+ AuthZPaasModule,
1397
+ CACHE_CONFIG_TOKEN,
1398
+ CanEnv,
1399
+ CanPermission,
1400
+ CanRole,
1401
+ DEFAULT_LOGIN_PATH,
1402
+ ENABLE_MOCK_ROLE_KEY,
1403
+ ENVIRONMENT_KEY,
1404
+ MOCK_ROLES_COOKIE_KEY,
1405
+ MockRoles,
1406
+ NEED_LOGIN_KEY,
1407
+ PERMISSIONS_KEY,
1408
+ PERMISSION_API_CONFIG_TOKEN,
1409
+ PermissionController,
1410
+ PermissionDeniedException,
1411
+ PermissionDeniedType,
1412
+ PermissionDto,
1413
+ PermissionResponse,
1414
+ PermissionService,
1415
+ ROLES_KEY,
1416
+ ROLE_SUBJECT,
1417
+ RolesMiddleware,
1418
+ UserId
1419
+ };
1420
+ //# sourceMappingURL=index.js.map