@flusys/nestjs-iam 6.0.2 → 6.1.1
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/cjs/controllers/action.controller.spec.js +79 -0
- package/cjs/controllers/company-action-permission.controller.js +1 -1
- package/cjs/controllers/company-action-permission.controller.spec.js +65 -0
- package/cjs/controllers/my-permission.controller.spec.js +56 -0
- package/cjs/controllers/role-permission.controller.spec.js +140 -0
- package/cjs/controllers/role.controller.spec.js +86 -0
- package/cjs/controllers/user-action-permission.controller.js +1 -1
- package/cjs/controllers/user-action-permission.controller.spec.js +68 -0
- package/cjs/docs/iam-swagger.config.spec.js +99 -0
- package/cjs/dtos/permission.dto.js +7 -0
- package/cjs/entities/index.spec.js +56 -0
- package/cjs/entities/permission-base.entity.js +1 -10
- package/cjs/entities/permission-with-company.entity.js +0 -3
- package/cjs/entities/user-iam-permission.entity.js +0 -3
- package/cjs/helpers/company-access.helper.spec.js +68 -0
- package/cjs/helpers/permission-mode.helper.spec.js +54 -0
- package/cjs/modules/iam.module.js +2 -2
- package/cjs/modules/iam.module.spec.js +293 -0
- package/cjs/services/action.service.js +4 -4
- package/cjs/services/action.service.spec.js +314 -0
- package/cjs/services/iam-config.service.spec.js +110 -0
- package/cjs/services/iam-datasource.service.spec.js +117 -0
- package/cjs/services/permission-cache.service.js +363 -52
- package/cjs/services/permission-cache.service.spec.js +699 -0
- package/cjs/services/permission.service.js +92 -396
- package/cjs/services/permission.service.spec.js +692 -0
- package/cjs/services/role.service.js +1 -1
- package/cjs/services/role.service.spec.js +309 -0
- package/dtos/permission.dto.d.ts +1 -0
- package/entities/permission-base.entity.d.ts +0 -1
- package/fesm/controllers/action.controller.spec.js +75 -0
- package/fesm/controllers/company-action-permission.controller.js +1 -1
- package/fesm/controllers/company-action-permission.controller.spec.js +61 -0
- package/fesm/controllers/my-permission.controller.spec.js +52 -0
- package/fesm/controllers/role-permission.controller.spec.js +136 -0
- package/fesm/controllers/role.controller.spec.js +82 -0
- package/fesm/controllers/user-action-permission.controller.js +1 -1
- package/fesm/controllers/user-action-permission.controller.spec.js +64 -0
- package/fesm/docs/iam-swagger.config.spec.js +95 -0
- package/fesm/dtos/permission.dto.js +7 -0
- package/fesm/entities/index.spec.js +52 -0
- package/fesm/entities/permission-base.entity.js +1 -10
- package/fesm/entities/permission-with-company.entity.js +0 -3
- package/fesm/entities/user-iam-permission.entity.js +0 -3
- package/fesm/helpers/company-access.helper.spec.js +64 -0
- package/fesm/helpers/permission-mode.helper.spec.js +50 -0
- package/fesm/modules/iam.module.js +4 -4
- package/fesm/modules/iam.module.spec.js +289 -0
- package/fesm/services/action.service.js +4 -4
- package/fesm/services/action.service.spec.js +310 -0
- package/fesm/services/iam-config.service.spec.js +106 -0
- package/fesm/services/iam-datasource.service.spec.js +113 -0
- package/fesm/services/permission-cache.service.js +365 -51
- package/fesm/services/permission-cache.service.spec.js +695 -0
- package/fesm/services/permission.service.js +92 -396
- package/fesm/services/permission.service.spec.js +688 -0
- package/fesm/services/role.service.js +1 -1
- package/fesm/services/role.service.spec.js +305 -0
- package/package.json +3 -3
- package/services/permission-cache.service.d.ts +20 -3
- package/services/permission.service.d.ts +2 -16
|
@@ -9,9 +9,6 @@ function _export(target, all) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
_export(exports, {
|
|
12
|
-
get MY_PERMISSIONS_CACHE_PREFIX () {
|
|
13
|
-
return MY_PERMISSIONS_CACHE_PREFIX;
|
|
14
|
-
},
|
|
15
12
|
get PermissionCacheKeyOptions () {
|
|
16
13
|
return _nestjsshared.PermissionCacheKeyOptions;
|
|
17
14
|
},
|
|
@@ -22,6 +19,18 @@ _export(exports, {
|
|
|
22
19
|
const _config = require("@flusys/nestjs-core/config");
|
|
23
20
|
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
24
21
|
const _common = require("@nestjs/common");
|
|
22
|
+
const _typeorm = require("typeorm");
|
|
23
|
+
const _actionentity = require("../entities/action.entity");
|
|
24
|
+
const _permissionwithcompanyentity = require("../entities/permission-with-company.entity");
|
|
25
|
+
const _rolewithcompanyentity = require("../entities/role-with-company.entity");
|
|
26
|
+
const _roleentity = require("../entities/role.entity");
|
|
27
|
+
const _useriampermissionentity = require("../entities/user-iam-permission.entity");
|
|
28
|
+
const _actiontypeenum = require("../enums/action-type.enum");
|
|
29
|
+
const _iamentitytypeenum = require("../enums/iam-entity-type.enum");
|
|
30
|
+
const _iampermissiontypeenum = require("../enums/iam-permission-type.enum");
|
|
31
|
+
const _permissiontypeenum = require("../enums/permission-type.enum");
|
|
32
|
+
const _iamconfigservice = require("./iam-config.service");
|
|
33
|
+
const _iamdatasourceservice = require("./iam-datasource.service");
|
|
25
34
|
function _define_property(obj, key, value) {
|
|
26
35
|
if (key in obj) {
|
|
27
36
|
Object.defineProperty(obj, key, {
|
|
@@ -49,9 +58,22 @@ function _ts_param(paramIndex, decorator) {
|
|
|
49
58
|
decorator(target, key, paramIndex);
|
|
50
59
|
};
|
|
51
60
|
}
|
|
52
|
-
const MY_PERMISSIONS_CACHE_PREFIX = _nestjsshared.PERMISSIONS_CACHE_PREFIX;
|
|
53
61
|
const NO_COMPANY_SCOPE = 'none';
|
|
54
62
|
let PermissionCacheService = class PermissionCacheService {
|
|
63
|
+
// Repository Getters
|
|
64
|
+
async getPermissionRepository() {
|
|
65
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
66
|
+
const entity = enableCompanyFeature ? _permissionwithcompanyentity.UserIamPermissionWithCompany : _useriampermissionentity.UserIamPermission;
|
|
67
|
+
return this.dataSourceProvider.getRepository(entity);
|
|
68
|
+
}
|
|
69
|
+
async getActionRepository() {
|
|
70
|
+
return this.dataSourceProvider.getRepository(_actionentity.Action);
|
|
71
|
+
}
|
|
72
|
+
async getRoleRepository() {
|
|
73
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
74
|
+
const entity = enableCompanyFeature ? _rolewithcompanyentity.RoleWithCompany : _roleentity.Role;
|
|
75
|
+
return this.dataSourceProvider.getRepository(entity);
|
|
76
|
+
}
|
|
55
77
|
// Cache Key Generation
|
|
56
78
|
parseDurationMs(duration) {
|
|
57
79
|
const unitMs = {
|
|
@@ -98,69 +120,305 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
98
120
|
}
|
|
99
121
|
async trackKey(userId, key, companyId) {
|
|
100
122
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
keys.
|
|
105
|
-
|
|
123
|
+
let scopeKey = key;
|
|
124
|
+
if (companyId) {
|
|
125
|
+
scopeKey = this.buildTrackedKeysKey(userId, companyId);
|
|
126
|
+
const keys = await this.cacheManager.get(scopeKey) || [];
|
|
127
|
+
if (!keys.includes(key)) {
|
|
128
|
+
keys.push(key);
|
|
129
|
+
await this.cacheManager.set(scopeKey, keys, this.TTL);
|
|
130
|
+
}
|
|
106
131
|
}
|
|
107
132
|
const scopesKey = this.buildTrackedScopesKey(userId);
|
|
108
133
|
const scopes = await this.cacheManager.get(scopesKey) || [];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
scopes.push(scope);
|
|
134
|
+
if (!scopes.includes(scopeKey)) {
|
|
135
|
+
scopes.push(scopeKey);
|
|
112
136
|
await this.cacheManager.set(scopesKey, scopes, this.TTL);
|
|
113
137
|
}
|
|
114
138
|
} catch {
|
|
115
139
|
// tracking is best-effort; a missed entry only widens invalidation, never narrows it
|
|
116
140
|
}
|
|
117
141
|
}
|
|
118
|
-
//
|
|
119
|
-
async
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
// My Permissions (cache-first read, DB-backed rebuild)
|
|
143
|
+
/** Get user's effective permissions (cache-first approach) */ async getMyPermissionsResponse(userId, branchId, companyId, parentCodes) {
|
|
144
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
145
|
+
const cacheOptions = {
|
|
146
|
+
userId,
|
|
147
|
+
companyId: enableCompanyFeature ? companyId : null,
|
|
148
|
+
branchId: enableCompanyFeature ? branchId : null
|
|
149
|
+
};
|
|
150
|
+
const cachedData = await this.getMyPermissions(cacheOptions);
|
|
151
|
+
if (cachedData) {
|
|
152
|
+
return this.buildResponseFromCache(cachedData, parentCodes);
|
|
123
153
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
154
|
+
const freshData = await this.fetchAndCachePermissions(userId, branchId, companyId);
|
|
155
|
+
return this.buildResponseFromCache(freshData, parentCodes);
|
|
156
|
+
}
|
|
157
|
+
/** Build response from cached data, applying optional parentCodes filter */ async buildResponseFromCache(cachedData, parentCodes) {
|
|
158
|
+
let frontendActions = cachedData.frontendActions;
|
|
159
|
+
if (parentCodes?.length) {
|
|
160
|
+
const parentIds = await this.getParentIdsByCodesWithCache(parentCodes);
|
|
161
|
+
if (parentIds.size > 0) {
|
|
162
|
+
frontendActions = frontendActions.filter((a)=>a.parentId && parentIds.has(a.parentId));
|
|
163
|
+
} else {
|
|
164
|
+
frontendActions = [];
|
|
165
|
+
}
|
|
131
166
|
}
|
|
132
|
-
|
|
133
|
-
|
|
167
|
+
return {
|
|
168
|
+
frontendActions: frontendActions.map((a)=>({
|
|
169
|
+
id: a.id,
|
|
170
|
+
code: a.code,
|
|
171
|
+
name: a.name,
|
|
172
|
+
description: a.description
|
|
173
|
+
})),
|
|
174
|
+
cachedEndpoints: cachedData.backendCodes.length
|
|
175
|
+
};
|
|
134
176
|
}
|
|
135
|
-
async
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
177
|
+
/** Resolve action IDs for the given codes */ async getParentIdsByCodesWithCache(codes) {
|
|
178
|
+
const actionRepo = await this.getActionRepository();
|
|
179
|
+
const actions = await actionRepo.find({
|
|
180
|
+
where: {
|
|
181
|
+
code: (0, _typeorm.In)(codes)
|
|
182
|
+
},
|
|
183
|
+
select: [
|
|
184
|
+
'id',
|
|
185
|
+
'code'
|
|
186
|
+
]
|
|
187
|
+
});
|
|
188
|
+
return new Set(actions.map((action)=>action.id));
|
|
189
|
+
}
|
|
190
|
+
/** Fetch permissions from DB and cache them (empty permissions are also cached) */ async fetchAndCachePermissions(userId, branchId, companyId) {
|
|
191
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
192
|
+
const cacheOptions = {
|
|
193
|
+
userId,
|
|
194
|
+
companyId: enableCompanyFeature ? companyId : null,
|
|
195
|
+
branchId
|
|
196
|
+
};
|
|
197
|
+
const emptyData = {
|
|
198
|
+
frontendActions: [],
|
|
199
|
+
backendCodes: []
|
|
200
|
+
};
|
|
201
|
+
const allActionIds = await this.collectAllActionIds(userId, branchId, companyId);
|
|
202
|
+
if (allActionIds.size === 0) {
|
|
203
|
+
await this.setMyPermissions(cacheOptions, emptyData);
|
|
204
|
+
return emptyData;
|
|
205
|
+
}
|
|
206
|
+
return this.buildAndCachePermissionData(allActionIds, cacheOptions);
|
|
207
|
+
}
|
|
208
|
+
/** Collect all action IDs based on permission mode (RBAC, DIRECT, or FULL) */ async collectAllActionIds(userId, branchId, companyId) {
|
|
209
|
+
const permissionMode = this.iamConfigService.getPermissionMode();
|
|
210
|
+
const allActionIds = new Set();
|
|
211
|
+
if (permissionMode === _permissiontypeenum.IAMPermissionMode.RBAC || permissionMode === _permissiontypeenum.IAMPermissionMode.FULL) {
|
|
212
|
+
const userRoleIds = await this.getUserRoleIds(userId, branchId, companyId);
|
|
213
|
+
if (userRoleIds.length > 0) {
|
|
214
|
+
const roleActionIds = await this.getRoleActionIds(userRoleIds);
|
|
215
|
+
roleActionIds.forEach((id)=>allActionIds.add(id));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (permissionMode === _permissiontypeenum.IAMPermissionMode.DIRECT || permissionMode === _permissiontypeenum.IAMPermissionMode.FULL) {
|
|
219
|
+
const userActionIds = await this.getUserActionIds(userId, branchId, companyId);
|
|
220
|
+
userActionIds.forEach((id)=>allActionIds.add(id));
|
|
221
|
+
}
|
|
222
|
+
return allActionIds;
|
|
223
|
+
}
|
|
224
|
+
/** Fetch actions from DB, separate by type, build cache data, and cache it */ async buildAndCachePermissionData(actionIds, cacheOptions) {
|
|
225
|
+
const actionRepo = await this.getActionRepository();
|
|
226
|
+
const actions = await actionRepo.find({
|
|
227
|
+
where: {
|
|
228
|
+
id: (0, _typeorm.In)(Array.from(actionIds)),
|
|
229
|
+
isActive: true
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
const backendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.BACKEND || a.actionType === _actiontypeenum.ActionType.BOTH);
|
|
233
|
+
const frontendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.FRONTEND || a.actionType === _actiontypeenum.ActionType.BOTH);
|
|
234
|
+
const backendCodes = backendActions.map((a)=>a.code).filter((c)=>!!c);
|
|
235
|
+
const cacheData = {
|
|
236
|
+
frontendActions: frontendActions.map((a)=>({
|
|
237
|
+
id: a.id,
|
|
238
|
+
code: a.code ?? '',
|
|
239
|
+
name: a.name,
|
|
240
|
+
description: a.description,
|
|
241
|
+
parentId: a.parentId
|
|
242
|
+
})),
|
|
243
|
+
backendCodes
|
|
244
|
+
};
|
|
245
|
+
await this.setMyPermissions(cacheOptions, cacheData);
|
|
246
|
+
return cacheData;
|
|
247
|
+
}
|
|
248
|
+
/** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
|
|
249
|
+
const permissionRepo = await this.getPermissionRepository();
|
|
250
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
251
|
+
if (!enableCompanyFeature) {
|
|
252
|
+
const permissions = await permissionRepo.find({
|
|
253
|
+
where: {
|
|
254
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.USER_ROLE,
|
|
255
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER,
|
|
256
|
+
sourceId: userId
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
143
260
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
261
|
+
const roleIds = new Set();
|
|
262
|
+
const baseWhere = {
|
|
263
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.USER_ROLE,
|
|
264
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER,
|
|
265
|
+
sourceId: userId
|
|
266
|
+
};
|
|
267
|
+
if (companyId) {
|
|
268
|
+
baseWhere.companyId = companyId;
|
|
269
|
+
}
|
|
270
|
+
if (branchId) {
|
|
271
|
+
// Get company-wide + branch-specific roles
|
|
272
|
+
const companyWidePermissions = await permissionRepo.find({
|
|
273
|
+
where: {
|
|
274
|
+
...baseWhere,
|
|
275
|
+
branchId: (0, _typeorm.IsNull)()
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
279
|
+
const branchPermissions = await permissionRepo.find({
|
|
280
|
+
where: {
|
|
281
|
+
...baseWhere,
|
|
282
|
+
branchId
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
147
286
|
} else {
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
287
|
+
// No branch context: get all roles for this company
|
|
288
|
+
const allPermissions = await permissionRepo.find({
|
|
289
|
+
where: baseWhere
|
|
290
|
+
});
|
|
291
|
+
allPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
292
|
+
}
|
|
293
|
+
return Array.from(roleIds);
|
|
294
|
+
}
|
|
295
|
+
async getRoleActionIds(roleIds) {
|
|
296
|
+
const permissionRepo = await this.getPermissionRepository();
|
|
297
|
+
const permissions = await permissionRepo.find({
|
|
298
|
+
where: {
|
|
299
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.ROLE_ACTION,
|
|
300
|
+
sourceType: _iamentitytypeenum.IamEntityType.ROLE,
|
|
301
|
+
sourceId: (0, _typeorm.In)(roleIds)
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
305
|
+
}
|
|
306
|
+
/** Get action IDs directly assigned to a user (merges company-wide + branch-specific actions) */ async getUserActionIds(userId, branchId, companyId) {
|
|
307
|
+
const permissionRepo = await this.getPermissionRepository();
|
|
308
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
309
|
+
if (!enableCompanyFeature) {
|
|
310
|
+
const permissions = await permissionRepo.find({
|
|
311
|
+
where: {
|
|
312
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.USER_ACTION,
|
|
313
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER,
|
|
314
|
+
sourceId: userId
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
318
|
+
}
|
|
319
|
+
const actionIds = new Set();
|
|
320
|
+
const baseWhere = {
|
|
321
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.USER_ACTION,
|
|
322
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER,
|
|
323
|
+
sourceId: userId
|
|
324
|
+
};
|
|
325
|
+
if (companyId) {
|
|
326
|
+
baseWhere.companyId = companyId;
|
|
327
|
+
}
|
|
328
|
+
if (branchId) {
|
|
329
|
+
// Get company-wide + branch-specific actions
|
|
330
|
+
const companyWidePermissions = await permissionRepo.find({
|
|
331
|
+
where: {
|
|
332
|
+
...baseWhere,
|
|
333
|
+
branchId: (0, _typeorm.IsNull)()
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
337
|
+
const branchPermissions = await permissionRepo.find({
|
|
338
|
+
where: {
|
|
339
|
+
...baseWhere,
|
|
152
340
|
branchId
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
344
|
+
} else {
|
|
345
|
+
// No branch context: get all actions for this company
|
|
346
|
+
const allPermissions = await permissionRepo.find({
|
|
347
|
+
where: baseWhere
|
|
348
|
+
});
|
|
349
|
+
allPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
350
|
+
}
|
|
351
|
+
return Array.from(actionIds);
|
|
352
|
+
}
|
|
353
|
+
// Cache Invalidation
|
|
354
|
+
async invalidateUser(userId, companyId, branchIds) {
|
|
355
|
+
const keysToDelete = new Set();
|
|
356
|
+
/** Drop a scope entry from the tracked-scopes index, or queue the whole index for deletion once nothing remains */ const untrackScope = async (scopeKey)=>{
|
|
357
|
+
const scopesKeysKey = this.buildTrackedScopesKey(userId);
|
|
358
|
+
let scopesKeys = [];
|
|
359
|
+
try {
|
|
360
|
+
scopesKeys = await this.cacheManager.get(scopesKeysKey) || [];
|
|
361
|
+
} catch {
|
|
362
|
+
scopesKeys = [];
|
|
363
|
+
}
|
|
364
|
+
if (scopeKey) {
|
|
365
|
+
const remainingScopes = scopesKeys.filter((scope)=>scope !== scopeKey);
|
|
366
|
+
if (remainingScopes.length > 0) {
|
|
367
|
+
await this.cacheManager.set(scopesKeysKey, remainingScopes, this.TTL);
|
|
368
|
+
} else {
|
|
369
|
+
keysToDelete.add(scopesKeysKey);
|
|
370
|
+
}
|
|
157
371
|
} else {
|
|
372
|
+
for (const scope of scopesKeys){
|
|
373
|
+
if (scope.startsWith(this.TRACKED_KEYS_PREFIX)) {
|
|
374
|
+
let nestedKeys = [];
|
|
375
|
+
try {
|
|
376
|
+
nestedKeys = await this.cacheManager.get(scope) || [];
|
|
377
|
+
} catch {
|
|
378
|
+
nestedKeys = [];
|
|
379
|
+
}
|
|
380
|
+
nestedKeys.forEach((key)=>keysToDelete.add(key));
|
|
381
|
+
}
|
|
382
|
+
keysToDelete.add(scope);
|
|
383
|
+
}
|
|
384
|
+
keysToDelete.add(scopesKeysKey);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
if (companyId) {
|
|
388
|
+
const trackedKeysKey = this.buildTrackedKeysKey(userId, companyId);
|
|
389
|
+
let trackedKeys = [];
|
|
390
|
+
try {
|
|
391
|
+
trackedKeys = await this.cacheManager.get(trackedKeysKey) || [];
|
|
392
|
+
} catch {
|
|
393
|
+
trackedKeys = [];
|
|
394
|
+
}
|
|
395
|
+
if (!branchIds?.length) {
|
|
396
|
+
trackedKeys.forEach((key)=>keysToDelete.add(key));
|
|
158
397
|
keysToDelete.add(trackedKeysKey);
|
|
398
|
+
await untrackScope(trackedKeysKey);
|
|
399
|
+
} else {
|
|
400
|
+
// Branch scope given: only invalidate those branches, keep the rest tracked
|
|
401
|
+
branchIds.forEach((branchId)=>keysToDelete.add(this.buildCacheKey({
|
|
402
|
+
userId,
|
|
403
|
+
companyId,
|
|
404
|
+
branchId
|
|
405
|
+
})));
|
|
406
|
+
const remainingKeys = trackedKeys.filter((key)=>!keysToDelete.has(key));
|
|
407
|
+
if (remainingKeys.length) {
|
|
408
|
+
await this.cacheManager.set(trackedKeysKey, remainingKeys, this.TTL);
|
|
409
|
+
} else {
|
|
410
|
+
keysToDelete.add(trackedKeysKey);
|
|
411
|
+
await untrackScope(trackedKeysKey);
|
|
412
|
+
}
|
|
159
413
|
}
|
|
414
|
+
} else {
|
|
415
|
+
await untrackScope();
|
|
160
416
|
}
|
|
161
|
-
await Promise.all(
|
|
162
|
-
|
|
163
|
-
|
|
417
|
+
await Promise.all(Array.from(keysToDelete).map(async (key)=>{
|
|
418
|
+
try {
|
|
419
|
+
await this.cacheManager.del(key);
|
|
420
|
+
} catch {}
|
|
421
|
+
}));
|
|
164
422
|
}
|
|
165
423
|
async invalidateUsers(userIds, companyId, branchIds) {
|
|
166
424
|
if (userIds.length === 0) {
|
|
@@ -169,13 +427,60 @@ let PermissionCacheService = class PermissionCacheService {
|
|
|
169
427
|
const results = await Promise.allSettled(userIds.map((userId)=>this.invalidateUser(userId, companyId, branchIds)));
|
|
170
428
|
return results.filter((r)=>r.status === 'fulfilled').length;
|
|
171
429
|
}
|
|
172
|
-
|
|
430
|
+
/** Invalidate permission cache for all users holding a given role */ async invalidateRoleMembersCache(roleId) {
|
|
431
|
+
const permissionRepo = await this.getPermissionRepository();
|
|
432
|
+
const roleRepo = await this.getRoleRepository();
|
|
433
|
+
const userRoles = await permissionRepo.find({
|
|
434
|
+
where: {
|
|
435
|
+
permissionType: _iampermissiontypeenum.IamPermissionType.USER_ROLE,
|
|
436
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER,
|
|
437
|
+
targetType: _iamentitytypeenum.IamEntityType.ROLE,
|
|
438
|
+
targetId: roleId
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
const userIds = [
|
|
442
|
+
...new Set(userRoles.map((ur)=>ur.sourceId))
|
|
443
|
+
];
|
|
444
|
+
if (userIds.length === 0) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
const role = await roleRepo.findOne({
|
|
448
|
+
where: {
|
|
449
|
+
id: roleId
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
const companyId = role?.companyId || null;
|
|
453
|
+
return this.invalidateUsers(userIds, companyId);
|
|
454
|
+
}
|
|
455
|
+
/** Invalidate permission cache for all users in a company */ async invalidateCompanyMembersCache(companyId) {
|
|
456
|
+
if (!this.iamConfigService.isCompanyFeatureEnabled()) {
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
const permissionRepo = await this.getPermissionRepository();
|
|
460
|
+
const userPermissions = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.source_id', 'userId').where('p.company_id = :companyId', {
|
|
461
|
+
companyId
|
|
462
|
+
}).andWhere('p.source_type = :sourceType', {
|
|
463
|
+
sourceType: _iamentitytypeenum.IamEntityType.USER
|
|
464
|
+
}).getRawMany();
|
|
465
|
+
const userIds = [
|
|
466
|
+
...new Set(userPermissions.map((p)=>p.userId).filter(Boolean))
|
|
467
|
+
];
|
|
468
|
+
if (userIds.length === 0) {
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
return this.invalidateUsers(userIds, companyId);
|
|
472
|
+
}
|
|
473
|
+
constructor(cacheManager, iamConfigService, dataSourceProvider, guardConfig){
|
|
173
474
|
_define_property(this, "cacheManager", void 0);
|
|
475
|
+
_define_property(this, "iamConfigService", void 0);
|
|
476
|
+
_define_property(this, "dataSourceProvider", void 0);
|
|
174
477
|
_define_property(this, "guardConfig", void 0);
|
|
175
478
|
_define_property(this, "TRACKED_KEYS_PREFIX", void 0);
|
|
176
479
|
_define_property(this, "TRACKED_SCOPES_PREFIX", void 0);
|
|
177
480
|
_define_property(this, "TTL", void 0);
|
|
178
481
|
this.cacheManager = cacheManager;
|
|
482
|
+
this.iamConfigService = iamConfigService;
|
|
483
|
+
this.dataSourceProvider = dataSourceProvider;
|
|
179
484
|
this.guardConfig = guardConfig;
|
|
180
485
|
this.TRACKED_KEYS_PREFIX = 'permission-tracked-keys';
|
|
181
486
|
this.TRACKED_SCOPES_PREFIX = 'permission-tracked-scopes';
|
|
@@ -221,13 +526,19 @@ _ts_decorate([
|
|
|
221
526
|
_ts_metadata("design:returntype", Promise)
|
|
222
527
|
], PermissionCacheService.prototype, "invalidateUsers", null);
|
|
223
528
|
PermissionCacheService = _ts_decorate([
|
|
224
|
-
(0, _common.Injectable)(
|
|
529
|
+
(0, _common.Injectable)({
|
|
530
|
+
scope: _common.Scope.REQUEST
|
|
531
|
+
}),
|
|
225
532
|
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
226
|
-
_ts_param(1, (0, _common.
|
|
227
|
-
_ts_param(
|
|
533
|
+
_ts_param(1, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
|
|
534
|
+
_ts_param(2, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
|
|
535
|
+
_ts_param(3, (0, _common.Optional)()),
|
|
536
|
+
_ts_param(3, (0, _common.Inject)(_nestjsshared.PERMISSION_GUARD_CONFIG)),
|
|
228
537
|
_ts_metadata("design:type", Function),
|
|
229
538
|
_ts_metadata("design:paramtypes", [
|
|
230
539
|
typeof _nestjsshared.HybridCache === "undefined" ? Object : _nestjsshared.HybridCache,
|
|
540
|
+
typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
|
|
541
|
+
typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService,
|
|
231
542
|
typeof _nestjsshared.PermissionGuardConfig === "undefined" ? Object : _nestjsshared.PermissionGuardConfig
|
|
232
543
|
])
|
|
233
544
|
], PermissionCacheService);
|