@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/LICENSE +13 -0
- package/README.md +1191 -0
- package/dist/index.cjs +1472 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +673 -0
- package/dist/index.d.ts +673 -0
- package/dist/index.js +1420 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
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
|