@flusys/nestjs-iam 6.1.0 → 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/company-action-permission.controller.js +1 -1
- package/cjs/controllers/company-action-permission.controller.spec.js +1 -1
- package/cjs/controllers/user-action-permission.controller.js +1 -1
- package/cjs/controllers/user-action-permission.controller.spec.js +1 -1
- package/cjs/dtos/permission.dto.js +7 -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/modules/iam.module.js +2 -2
- package/cjs/services/action.service.js +4 -4
- package/cjs/services/action.service.spec.js +11 -11
- package/cjs/services/permission-cache.service.js +363 -52
- package/cjs/services/permission-cache.service.spec.js +462 -15
- package/cjs/services/permission.service.js +92 -396
- package/cjs/services/permission.service.spec.js +35 -310
- package/cjs/services/role.service.js +1 -1
- package/cjs/services/role.service.spec.js +4 -7
- package/dtos/permission.dto.d.ts +1 -0
- package/entities/permission-base.entity.d.ts +0 -1
- package/fesm/controllers/company-action-permission.controller.js +1 -1
- package/fesm/controllers/company-action-permission.controller.spec.js +1 -1
- package/fesm/controllers/user-action-permission.controller.js +1 -1
- package/fesm/controllers/user-action-permission.controller.spec.js +1 -1
- package/fesm/dtos/permission.dto.js +7 -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/modules/iam.module.js +4 -4
- package/fesm/services/action.service.js +4 -4
- package/fesm/services/action.service.spec.js +11 -11
- package/fesm/services/permission-cache.service.js +365 -51
- package/fesm/services/permission-cache.service.spec.js +462 -15
- package/fesm/services/permission.service.js +92 -396
- package/fesm/services/permission.service.spec.js +35 -310
- package/fesm/services/role.service.js +1 -1
- package/fesm/services/role.service.spec.js +4 -7
- package/package.json +3 -3
- package/services/permission-cache.service.d.ts +20 -3
- package/services/permission.service.d.ts +2 -16
|
@@ -35,10 +35,8 @@ import { UserIamPermissionWithCompany } from '../entities/permission-with-compan
|
|
|
35
35
|
import { RoleWithCompany } from '../entities/role-with-company.entity';
|
|
36
36
|
import { Role } from '../entities/role.entity';
|
|
37
37
|
import { UserIamPermission } from '../entities/user-iam-permission.entity';
|
|
38
|
-
import { ActionType } from '../enums/action-type.enum';
|
|
39
38
|
import { IamEntityType } from '../enums/iam-entity-type.enum';
|
|
40
39
|
import { IamPermissionType } from '../enums/iam-permission-type.enum';
|
|
41
|
-
import { IAMPermissionMode } from '../enums/permission-type.enum';
|
|
42
40
|
import { IAMConfigService } from './iam-config.service';
|
|
43
41
|
import { IAMDataSourceService } from './iam-datasource.service';
|
|
44
42
|
import { PermissionCacheService } from './permission-cache.service';
|
|
@@ -110,7 +108,6 @@ export class PermissionService {
|
|
|
110
108
|
sourceId: dto.userId,
|
|
111
109
|
targetType: IamEntityType.ACTION,
|
|
112
110
|
targetId: item.id,
|
|
113
|
-
userId: dto.userId,
|
|
114
111
|
companyId: enableCompanyFeature ? companyId : null,
|
|
115
112
|
branchId: enableCompanyFeature ? branchId : null
|
|
116
113
|
}));
|
|
@@ -145,12 +142,12 @@ export class PermissionService {
|
|
|
145
142
|
const result = await permissionRepo.delete(whereDelete);
|
|
146
143
|
removed = result.affected || 0;
|
|
147
144
|
}
|
|
148
|
-
await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, [
|
|
145
|
+
await this.permissionCacheService.invalidateUser(dto.userId, enableCompanyFeature ? companyId : null, enableCompanyFeature ? [
|
|
149
146
|
branchId
|
|
150
|
-
]);
|
|
147
|
+
] : []);
|
|
151
148
|
return this.buildOperationResult(dto.items.length, added, removed);
|
|
152
149
|
}
|
|
153
|
-
async getUserActions(userId,
|
|
150
|
+
async getUserActions(userId, companyId, branchId) {
|
|
154
151
|
const permissionRepo = await this.getPermissionRepository();
|
|
155
152
|
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
156
153
|
const where = {
|
|
@@ -160,7 +157,7 @@ export class PermissionService {
|
|
|
160
157
|
};
|
|
161
158
|
if (enableCompanyFeature) {
|
|
162
159
|
if (companyId) where.companyId = companyId;
|
|
163
|
-
where.branchId = branchId
|
|
160
|
+
if (branchId) where.branchId = branchId;
|
|
164
161
|
}
|
|
165
162
|
const permissions = await permissionRepo.find({
|
|
166
163
|
where
|
|
@@ -174,10 +171,11 @@ export class PermissionService {
|
|
|
174
171
|
const action = actionMap.get(p.targetId);
|
|
175
172
|
return {
|
|
176
173
|
id: p.id,
|
|
177
|
-
userId: p.
|
|
174
|
+
userId: p.sourceId,
|
|
178
175
|
actionId: action.id,
|
|
179
176
|
actionCode: action.code ?? '',
|
|
180
177
|
actionName: action.name,
|
|
178
|
+
companyId: ('companyId' in p ? p.companyId : null) ?? null,
|
|
181
179
|
branchId: ('branchId' in p ? p.branchId : null) ?? null,
|
|
182
180
|
createdAt: p.createdAt
|
|
183
181
|
};
|
|
@@ -231,7 +229,6 @@ export class PermissionService {
|
|
|
231
229
|
sourceId: dto.roleId,
|
|
232
230
|
targetType: IamEntityType.ACTION,
|
|
233
231
|
targetId: item.id,
|
|
234
|
-
userId: null,
|
|
235
232
|
companyId: enableCompanyFeature ? roleCompanyId : null,
|
|
236
233
|
branchId: null
|
|
237
234
|
}));
|
|
@@ -261,7 +258,7 @@ export class PermissionService {
|
|
|
261
258
|
});
|
|
262
259
|
removed = result.affected || 0;
|
|
263
260
|
}
|
|
264
|
-
await this.invalidateRoleMembersCache(dto.roleId);
|
|
261
|
+
await this.permissionCacheService.invalidateRoleMembersCache(dto.roleId);
|
|
265
262
|
return this.buildOperationResult(dto.items.length, added, removed);
|
|
266
263
|
}
|
|
267
264
|
async getRoleActions(roleId) {
|
|
@@ -293,94 +290,88 @@ export class PermissionService {
|
|
|
293
290
|
// Company-Action Permissions (Whitelist)
|
|
294
291
|
/** Assign or remove actions to/from a company (whitelist) */ async assignCompanyActions(dto) {
|
|
295
292
|
const permissionRepo = await this.getPermissionRepository();
|
|
296
|
-
const dataSource =
|
|
293
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
294
|
+
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
297
295
|
const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
|
|
298
296
|
let added = 0;
|
|
299
297
|
let removed = 0;
|
|
300
298
|
await dataSource.transaction(async (manager)=>{
|
|
301
299
|
const transactionalPermissionRepo = manager.getRepository(permissionRepo.target);
|
|
302
300
|
if (itemsToAdd.length > 0) {
|
|
303
|
-
|
|
301
|
+
const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
|
|
302
|
+
const existingPermissions = await transactionalPermissionRepo.find({
|
|
303
|
+
where: {
|
|
304
|
+
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
305
|
+
sourceType: IamEntityType.COMPANY,
|
|
306
|
+
sourceId: dto.companyId,
|
|
307
|
+
targetType: IamEntityType.ACTION,
|
|
308
|
+
targetId: In(actionIdsToAdd)
|
|
309
|
+
},
|
|
310
|
+
select: [
|
|
311
|
+
'targetId'
|
|
312
|
+
]
|
|
313
|
+
});
|
|
314
|
+
const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
|
|
315
|
+
const newActionIds = actionIdsToAdd.filter((id)=>!existingActionIds.has(id));
|
|
316
|
+
if (newActionIds.length > 0) {
|
|
317
|
+
const newPermissions = newActionIds.map((actionId)=>transactionalPermissionRepo.create({
|
|
318
|
+
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
319
|
+
sourceType: IamEntityType.COMPANY,
|
|
320
|
+
sourceId: dto.companyId,
|
|
321
|
+
targetType: IamEntityType.ACTION,
|
|
322
|
+
targetId: actionId
|
|
323
|
+
}));
|
|
324
|
+
await transactionalPermissionRepo.save(newPermissions);
|
|
325
|
+
added = newPermissions.length;
|
|
326
|
+
}
|
|
304
327
|
}
|
|
305
328
|
if (itemsToRemove.length > 0) {
|
|
306
329
|
const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
|
|
307
|
-
|
|
330
|
+
const companyResult = await transactionalPermissionRepo.delete({
|
|
331
|
+
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
332
|
+
sourceType: IamEntityType.COMPANY,
|
|
333
|
+
sourceId: dto.companyId,
|
|
334
|
+
targetType: IamEntityType.ACTION,
|
|
335
|
+
targetId: In(actionIdsToRemove)
|
|
336
|
+
});
|
|
337
|
+
removed = companyResult.affected || 0;
|
|
338
|
+
// Cascade: an action no longer on the company whitelist must not remain
|
|
339
|
+
// reachable via role grants or direct user grants scoped to this company.
|
|
340
|
+
const roleEntity = enableCompanyFeature ? RoleWithCompany : Role;
|
|
341
|
+
const roleRepo = manager.getRepository(roleEntity);
|
|
342
|
+
const companyRoles = await roleRepo.find({
|
|
343
|
+
where: {
|
|
344
|
+
companyId: dto.companyId,
|
|
345
|
+
deletedAt: IsNull()
|
|
346
|
+
},
|
|
347
|
+
select: [
|
|
348
|
+
'id'
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
if (companyRoles.length > 0) {
|
|
352
|
+
const roleIds = companyRoles.map((role)=>role.id);
|
|
353
|
+
await transactionalPermissionRepo.delete({
|
|
354
|
+
permissionType: IamPermissionType.ROLE_ACTION,
|
|
355
|
+
sourceType: IamEntityType.ROLE,
|
|
356
|
+
sourceId: In(roleIds),
|
|
357
|
+
targetType: IamEntityType.ACTION,
|
|
358
|
+
targetId: In(actionIdsToRemove)
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (enableCompanyFeature) {
|
|
362
|
+
await transactionalPermissionRepo.delete({
|
|
363
|
+
permissionType: IamPermissionType.USER_ACTION,
|
|
364
|
+
companyId: dto.companyId,
|
|
365
|
+
targetType: IamEntityType.ACTION,
|
|
366
|
+
targetId: In(actionIdsToRemove)
|
|
367
|
+
});
|
|
368
|
+
}
|
|
308
369
|
}
|
|
309
370
|
});
|
|
310
|
-
await this.invalidateCompanyMembersCache(dto.companyId);
|
|
371
|
+
await this.permissionCacheService.invalidateCompanyMembersCache(dto.companyId);
|
|
311
372
|
return this.buildOperationResult(dto.items.length, added, removed);
|
|
312
373
|
}
|
|
313
|
-
async
|
|
314
|
-
const existingPermissions = await permissionRepo.find({
|
|
315
|
-
where: {
|
|
316
|
-
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
317
|
-
sourceType: IamEntityType.COMPANY,
|
|
318
|
-
sourceId: companyId,
|
|
319
|
-
targetType: IamEntityType.ACTION,
|
|
320
|
-
targetId: In(actionIds)
|
|
321
|
-
},
|
|
322
|
-
select: [
|
|
323
|
-
'targetId'
|
|
324
|
-
]
|
|
325
|
-
});
|
|
326
|
-
const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
|
|
327
|
-
const newActionIds = actionIds.filter((id)=>!existingActionIds.has(id));
|
|
328
|
-
if (newActionIds.length === 0) {
|
|
329
|
-
return 0;
|
|
330
|
-
}
|
|
331
|
-
const newPermissions = newActionIds.map((actionId)=>permissionRepo.create({
|
|
332
|
-
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
333
|
-
sourceType: IamEntityType.COMPANY,
|
|
334
|
-
sourceId: companyId,
|
|
335
|
-
targetType: IamEntityType.ACTION,
|
|
336
|
-
targetId: actionId,
|
|
337
|
-
userId: null
|
|
338
|
-
}));
|
|
339
|
-
await permissionRepo.save(newPermissions);
|
|
340
|
-
return newPermissions.length;
|
|
341
|
-
}
|
|
342
|
-
async removeCompanyActionsWithCascade(manager, companyId, actionIds) {
|
|
343
|
-
const permissionEntity = this.iamConfigService.isCompanyFeatureEnabled() ? UserIamPermissionWithCompany : UserIamPermission;
|
|
344
|
-
const permissionRepo = manager.getRepository(permissionEntity);
|
|
345
|
-
const companyResult = await permissionRepo.delete({
|
|
346
|
-
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
347
|
-
sourceType: IamEntityType.COMPANY,
|
|
348
|
-
sourceId: companyId,
|
|
349
|
-
targetType: IamEntityType.ACTION,
|
|
350
|
-
targetId: In(actionIds)
|
|
351
|
-
});
|
|
352
|
-
const roleEntity = this.iamConfigService.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
|
|
353
|
-
const roleRepo = manager.getRepository(roleEntity);
|
|
354
|
-
const companyRoles = await roleRepo.find({
|
|
355
|
-
where: {
|
|
356
|
-
companyId,
|
|
357
|
-
deletedAt: IsNull()
|
|
358
|
-
},
|
|
359
|
-
select: [
|
|
360
|
-
'id'
|
|
361
|
-
]
|
|
362
|
-
});
|
|
363
|
-
if (companyRoles.length > 0) {
|
|
364
|
-
const roleIds = companyRoles.map((role)=>role.id);
|
|
365
|
-
await permissionRepo.delete({
|
|
366
|
-
permissionType: IamPermissionType.ROLE_ACTION,
|
|
367
|
-
sourceType: IamEntityType.ROLE,
|
|
368
|
-
sourceId: In(roleIds),
|
|
369
|
-
targetType: IamEntityType.ACTION,
|
|
370
|
-
targetId: In(actionIds)
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
if (this.iamConfigService.isCompanyFeatureEnabled()) {
|
|
374
|
-
await permissionRepo.delete({
|
|
375
|
-
permissionType: IamPermissionType.USER_ACTION,
|
|
376
|
-
companyId,
|
|
377
|
-
targetType: IamEntityType.ACTION,
|
|
378
|
-
targetId: In(actionIds)
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
return companyResult.affected || 0;
|
|
382
|
-
}
|
|
383
|
-
/** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId) {
|
|
374
|
+
/** Get all actions assigned to a company (whitelist) */ async getCompanyActions(companyId, isOnlyId = false) {
|
|
384
375
|
const permissionRepo = await this.getPermissionRepository();
|
|
385
376
|
const permissions = await permissionRepo.find({
|
|
386
377
|
where: {
|
|
@@ -393,6 +384,7 @@ export class PermissionService {
|
|
|
393
384
|
return [];
|
|
394
385
|
}
|
|
395
386
|
const actionIds = permissions.map((p)=>p.targetId);
|
|
387
|
+
if (isOnlyId) return actionIds;
|
|
396
388
|
const actionMap = await this.getActionsMap(actionIds);
|
|
397
389
|
return permissions.filter((p)=>actionMap.has(p.targetId)).map((p)=>{
|
|
398
390
|
const action = actionMap.get(p.targetId);
|
|
@@ -406,20 +398,6 @@ export class PermissionService {
|
|
|
406
398
|
};
|
|
407
399
|
});
|
|
408
400
|
}
|
|
409
|
-
/** Get action IDs whitelisted for a company */ async getCompanyActionIds(companyId) {
|
|
410
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
411
|
-
const permissions = await permissionRepo.find({
|
|
412
|
-
where: {
|
|
413
|
-
permissionType: IamPermissionType.COMPANY_ACTION,
|
|
414
|
-
sourceType: IamEntityType.COMPANY,
|
|
415
|
-
sourceId: companyId
|
|
416
|
-
},
|
|
417
|
-
select: [
|
|
418
|
-
'targetId'
|
|
419
|
-
]
|
|
420
|
-
});
|
|
421
|
-
return permissions.map((p)=>p.targetId);
|
|
422
|
-
}
|
|
423
401
|
// User-Role Permissions
|
|
424
402
|
/** Assign user to roles (branch-scoped when company feature is enabled) */ async assignUserRoles(dto) {
|
|
425
403
|
if (!this.iamConfigService.isRbacEnabled()) {
|
|
@@ -461,7 +439,6 @@ export class PermissionService {
|
|
|
461
439
|
sourceId: dto.userId,
|
|
462
440
|
targetType: IamEntityType.ROLE,
|
|
463
441
|
targetId: item.id,
|
|
464
|
-
userId: dto.userId,
|
|
465
442
|
companyId: enableCompanyFeature ? companyId : null,
|
|
466
443
|
branchId: enableCompanyFeature ? branchId : null
|
|
467
444
|
}));
|
|
@@ -512,7 +489,7 @@ export class PermissionService {
|
|
|
512
489
|
};
|
|
513
490
|
if (enableCompanyFeature) {
|
|
514
491
|
if (companyId) where.companyId = companyId;
|
|
515
|
-
where.branchId = branchId
|
|
492
|
+
if (branchId) where.branchId = branchId;
|
|
516
493
|
}
|
|
517
494
|
const permissions = await permissionRepo.find({
|
|
518
495
|
where
|
|
@@ -536,7 +513,7 @@ export class PermissionService {
|
|
|
536
513
|
const permissionEntity = p;
|
|
537
514
|
return {
|
|
538
515
|
id: p.id,
|
|
539
|
-
userId: p.
|
|
516
|
+
userId: p.sourceId,
|
|
540
517
|
roleId: role.id,
|
|
541
518
|
roleName: role.name,
|
|
542
519
|
branchId: enableCompanyFeature ? permissionEntity.branchId ?? null : null,
|
|
@@ -546,129 +523,7 @@ export class PermissionService {
|
|
|
546
523
|
}
|
|
547
524
|
// My Permissions
|
|
548
525
|
/** Get user's effective permissions (cache-first approach) */ async getMyPermissions(userId, branchId, companyId, parentCodes) {
|
|
549
|
-
|
|
550
|
-
const cacheOptions = {
|
|
551
|
-
userId,
|
|
552
|
-
companyId: enableCompanyFeature ? companyId : null,
|
|
553
|
-
branchId
|
|
554
|
-
};
|
|
555
|
-
const cachedData = await this.permissionCacheService.getMyPermissions(cacheOptions);
|
|
556
|
-
if (cachedData) {
|
|
557
|
-
return this.buildResponseFromCache(cachedData, parentCodes);
|
|
558
|
-
}
|
|
559
|
-
const freshData = await this.fetchAndCachePermissions(userId, branchId, companyId);
|
|
560
|
-
return this.buildResponseFromCache(freshData, parentCodes);
|
|
561
|
-
}
|
|
562
|
-
/** Build response from cached data, applying optional parentCodes filter */ async buildResponseFromCache(cachedData, parentCodes) {
|
|
563
|
-
let frontendActions = cachedData.frontendActions;
|
|
564
|
-
if (parentCodes?.length) {
|
|
565
|
-
const parentIds = await this.getParentIdsByCodesWithCache(parentCodes);
|
|
566
|
-
if (parentIds.size > 0) {
|
|
567
|
-
frontendActions = frontendActions.filter((a)=>a.parentId && parentIds.has(a.parentId));
|
|
568
|
-
} else {
|
|
569
|
-
frontendActions = [];
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return {
|
|
573
|
-
frontendActions: frontendActions.map((a)=>({
|
|
574
|
-
id: a.id,
|
|
575
|
-
code: a.code,
|
|
576
|
-
name: a.name,
|
|
577
|
-
description: a.description
|
|
578
|
-
})),
|
|
579
|
-
cachedEndpoints: cachedData.backendCodes.length
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/** Resolve action IDs for the given codes */ async getParentIdsByCodesWithCache(codes) {
|
|
583
|
-
const actionRepo = await this.getActionRepository();
|
|
584
|
-
const actions = await actionRepo.find({
|
|
585
|
-
where: {
|
|
586
|
-
code: In(codes)
|
|
587
|
-
},
|
|
588
|
-
select: [
|
|
589
|
-
'id',
|
|
590
|
-
'code'
|
|
591
|
-
]
|
|
592
|
-
});
|
|
593
|
-
return new Set(actions.map((action)=>action.id));
|
|
594
|
-
}
|
|
595
|
-
/** Fetch permissions from DB and cache them (empty permissions are also cached) */ async fetchAndCachePermissions(userId, branchId, companyId) {
|
|
596
|
-
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
597
|
-
const cacheOptions = {
|
|
598
|
-
userId,
|
|
599
|
-
companyId: enableCompanyFeature ? companyId : null,
|
|
600
|
-
branchId
|
|
601
|
-
};
|
|
602
|
-
const emptyData = {
|
|
603
|
-
frontendActions: [],
|
|
604
|
-
backendCodes: []
|
|
605
|
-
};
|
|
606
|
-
const allActionIds = await this.collectAllActionIds(userId, branchId, companyId);
|
|
607
|
-
if (allActionIds.size === 0) {
|
|
608
|
-
await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
|
|
609
|
-
return emptyData;
|
|
610
|
-
}
|
|
611
|
-
await this.applyCompanyWhitelist(allActionIds, companyId);
|
|
612
|
-
if (allActionIds.size === 0) {
|
|
613
|
-
await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
|
|
614
|
-
return emptyData;
|
|
615
|
-
}
|
|
616
|
-
return this.buildAndCachePermissionData(allActionIds, cacheOptions);
|
|
617
|
-
}
|
|
618
|
-
/** Collect all action IDs based on permission mode (RBAC, DIRECT, or FULL) */ async collectAllActionIds(userId, branchId, companyId) {
|
|
619
|
-
const permissionMode = this.iamConfigService.getPermissionMode();
|
|
620
|
-
const allActionIds = new Set();
|
|
621
|
-
if (permissionMode === IAMPermissionMode.RBAC || permissionMode === IAMPermissionMode.FULL) {
|
|
622
|
-
const userRoleIds = await this.getUserRoleIds(userId, branchId, companyId);
|
|
623
|
-
if (userRoleIds.length > 0) {
|
|
624
|
-
const roleActionIds = await this.getRoleActionIds(userRoleIds);
|
|
625
|
-
roleActionIds.forEach((id)=>allActionIds.add(id));
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
if (permissionMode === IAMPermissionMode.DIRECT || permissionMode === IAMPermissionMode.FULL) {
|
|
629
|
-
const userActionIds = await this.getUserActionIds(userId, branchId, companyId);
|
|
630
|
-
userActionIds.forEach((id)=>allActionIds.add(id));
|
|
631
|
-
}
|
|
632
|
-
return allActionIds;
|
|
633
|
-
}
|
|
634
|
-
/** Apply company whitelist filter to action IDs (mutates the set) */ async applyCompanyWhitelist(actionIds, companyId) {
|
|
635
|
-
if (!this.iamConfigService.isCompanyFeatureEnabled() || !companyId) {
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
const companyActionIds = await this.getCompanyActionIds(companyId);
|
|
639
|
-
if (companyActionIds.length === 0) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
const allowedActionIds = new Set(companyActionIds);
|
|
643
|
-
for (const actionId of actionIds){
|
|
644
|
-
if (!allowedActionIds.has(actionId)) {
|
|
645
|
-
actionIds.delete(actionId);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
/** Fetch actions from DB, separate by type, build cache data, and cache it */ async buildAndCachePermissionData(actionIds, cacheOptions) {
|
|
650
|
-
const actionRepo = await this.getActionRepository();
|
|
651
|
-
const actions = await actionRepo.find({
|
|
652
|
-
where: {
|
|
653
|
-
id: In(Array.from(actionIds)),
|
|
654
|
-
isActive: true
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
|
-
const backendActions = actions.filter((a)=>a.actionType === ActionType.BACKEND || a.actionType === ActionType.BOTH);
|
|
658
|
-
const frontendActions = actions.filter((a)=>a.actionType === ActionType.FRONTEND || a.actionType === ActionType.BOTH);
|
|
659
|
-
const backendCodes = backendActions.map((a)=>a.code).filter((c)=>!!c);
|
|
660
|
-
const cacheData = {
|
|
661
|
-
frontendActions: frontendActions.map((a)=>({
|
|
662
|
-
id: a.id,
|
|
663
|
-
code: a.code ?? '',
|
|
664
|
-
name: a.name,
|
|
665
|
-
description: a.description,
|
|
666
|
-
parentId: a.parentId
|
|
667
|
-
})),
|
|
668
|
-
backendCodes
|
|
669
|
-
};
|
|
670
|
-
await this.permissionCacheService.setMyPermissions(cacheOptions, cacheData);
|
|
671
|
-
return cacheData;
|
|
526
|
+
return this.permissionCacheService.getMyPermissionsResponse(userId, branchId, companyId, parentCodes);
|
|
672
527
|
}
|
|
673
528
|
// Helper Methods
|
|
674
529
|
/** Split permission items into add and remove arrays */ splitItemsByAction(items) {
|
|
@@ -684,176 +539,13 @@ export class PermissionService {
|
|
|
684
539
|
total: _totalItems
|
|
685
540
|
};
|
|
686
541
|
}
|
|
687
|
-
/** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
|
|
688
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
689
|
-
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
690
|
-
if (!enableCompanyFeature) {
|
|
691
|
-
const permissions = await permissionRepo.find({
|
|
692
|
-
where: {
|
|
693
|
-
permissionType: IamPermissionType.USER_ROLE,
|
|
694
|
-
sourceType: IamEntityType.USER,
|
|
695
|
-
sourceId: userId
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
699
|
-
}
|
|
700
|
-
const roleIds = new Set();
|
|
701
|
-
const baseWhere = {
|
|
702
|
-
permissionType: IamPermissionType.USER_ROLE,
|
|
703
|
-
sourceType: IamEntityType.USER,
|
|
704
|
-
sourceId: userId
|
|
705
|
-
};
|
|
706
|
-
if (companyId) {
|
|
707
|
-
baseWhere.companyId = companyId;
|
|
708
|
-
}
|
|
709
|
-
if (branchId) {
|
|
710
|
-
// Get company-wide + branch-specific roles
|
|
711
|
-
const companyWidePermissions = await permissionRepo.find({
|
|
712
|
-
where: {
|
|
713
|
-
...baseWhere,
|
|
714
|
-
branchId: IsNull()
|
|
715
|
-
}
|
|
716
|
-
});
|
|
717
|
-
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
718
|
-
const branchPermissions = await permissionRepo.find({
|
|
719
|
-
where: {
|
|
720
|
-
...baseWhere,
|
|
721
|
-
branchId
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
725
|
-
} else {
|
|
726
|
-
// No branch context: get all roles for this company
|
|
727
|
-
const allPermissions = await permissionRepo.find({
|
|
728
|
-
where: baseWhere
|
|
729
|
-
});
|
|
730
|
-
allPermissions.filter((p)=>p.isValid()).forEach((p)=>roleIds.add(p.targetId));
|
|
731
|
-
}
|
|
732
|
-
return Array.from(roleIds);
|
|
733
|
-
}
|
|
734
|
-
async getRoleActionIds(roleIds) {
|
|
735
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
736
|
-
const permissions = await permissionRepo.find({
|
|
737
|
-
where: {
|
|
738
|
-
permissionType: IamPermissionType.ROLE_ACTION,
|
|
739
|
-
sourceType: IamEntityType.ROLE,
|
|
740
|
-
sourceId: In(roleIds)
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
744
|
-
}
|
|
745
|
-
/** Get action IDs directly assigned to a user (merges company-wide + branch-specific actions) */ async getUserActionIds(userId, branchId, companyId) {
|
|
746
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
747
|
-
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
748
|
-
if (!enableCompanyFeature) {
|
|
749
|
-
const permissions = await permissionRepo.find({
|
|
750
|
-
where: {
|
|
751
|
-
permissionType: IamPermissionType.USER_ACTION,
|
|
752
|
-
sourceType: IamEntityType.USER,
|
|
753
|
-
sourceId: userId
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
return permissions.filter((p)=>p.isValid()).map((p)=>p.targetId);
|
|
757
|
-
}
|
|
758
|
-
const actionIds = new Set();
|
|
759
|
-
const baseWhere = {
|
|
760
|
-
permissionType: IamPermissionType.USER_ACTION,
|
|
761
|
-
sourceType: IamEntityType.USER,
|
|
762
|
-
sourceId: userId
|
|
763
|
-
};
|
|
764
|
-
if (companyId) {
|
|
765
|
-
baseWhere.companyId = companyId;
|
|
766
|
-
}
|
|
767
|
-
if (branchId) {
|
|
768
|
-
// Get company-wide + branch-specific actions
|
|
769
|
-
const companyWidePermissions = await permissionRepo.find({
|
|
770
|
-
where: {
|
|
771
|
-
...baseWhere,
|
|
772
|
-
branchId: IsNull()
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
companyWidePermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
776
|
-
const branchPermissions = await permissionRepo.find({
|
|
777
|
-
where: {
|
|
778
|
-
...baseWhere,
|
|
779
|
-
branchId
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
branchPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
783
|
-
} else {
|
|
784
|
-
// No branch context: get all actions for this company
|
|
785
|
-
const allPermissions = await permissionRepo.find({
|
|
786
|
-
where: baseWhere
|
|
787
|
-
});
|
|
788
|
-
allPermissions.filter((p)=>p.isValid()).forEach((p)=>actionIds.add(p.targetId));
|
|
789
|
-
}
|
|
790
|
-
return Array.from(actionIds);
|
|
791
|
-
}
|
|
792
|
-
async invalidateRoleMembersCache(roleId) {
|
|
793
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
794
|
-
const roleRepo = await this.getRoleRepository();
|
|
795
|
-
const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
|
|
796
|
-
const userRoles = await permissionRepo.find({
|
|
797
|
-
where: {
|
|
798
|
-
permissionType: IamPermissionType.USER_ROLE,
|
|
799
|
-
sourceType: IamEntityType.USER,
|
|
800
|
-
targetType: IamEntityType.ROLE,
|
|
801
|
-
targetId: roleId
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
const userIds = [
|
|
805
|
-
...new Set(userRoles.map((ur)=>ur.sourceId))
|
|
806
|
-
];
|
|
807
|
-
if (userIds.length === 0) {
|
|
808
|
-
return 0;
|
|
809
|
-
}
|
|
810
|
-
const role = await roleRepo.findOne({
|
|
811
|
-
where: {
|
|
812
|
-
id: roleId
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
const companyId = role?.companyId || null;
|
|
816
|
-
let branchIds = [
|
|
817
|
-
null
|
|
818
|
-
];
|
|
819
|
-
if (enableCompanyFeature && companyId) {
|
|
820
|
-
const userBranches = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.branch_id', 'branchId').where('p.user_id IN (:...userIds)', {
|
|
821
|
-
userIds
|
|
822
|
-
}).andWhere('p.company_id = :companyId', {
|
|
823
|
-
companyId
|
|
824
|
-
}).getRawMany();
|
|
825
|
-
branchIds = [
|
|
826
|
-
...new Set(userBranches.map((p)=>p.branchId))
|
|
827
|
-
];
|
|
828
|
-
}
|
|
829
|
-
return this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
|
|
830
|
-
}
|
|
831
|
-
/** Invalidate permission cache for all users in a company */ async invalidateCompanyMembersCache(companyId) {
|
|
832
|
-
if (!this.iamConfigService.isCompanyFeatureEnabled()) {
|
|
833
|
-
return 0;
|
|
834
|
-
}
|
|
835
|
-
const permissionRepo = await this.getPermissionRepository();
|
|
836
|
-
const userPermissions = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.user_id', 'userId').addSelect('p.branch_id', 'branchId').where('p.company_id = :companyId', {
|
|
837
|
-
companyId
|
|
838
|
-
}).andWhere('p.user_id IS NOT NULL').getRawMany();
|
|
839
|
-
const userIds = [
|
|
840
|
-
...new Set(userPermissions.map((p)=>p.userId).filter(Boolean))
|
|
841
|
-
];
|
|
842
|
-
const branchIds = [
|
|
843
|
-
...new Set(userPermissions.map((p)=>p.branchId))
|
|
844
|
-
]; // includes null
|
|
845
|
-
if (userIds.length === 0) {
|
|
846
|
-
return 0;
|
|
847
|
-
}
|
|
848
|
-
return await this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
|
|
849
|
-
}
|
|
850
542
|
async revokeCompanyPermissions(companyId) {
|
|
851
543
|
if (!this.iamConfigService.isCompanyFeatureEnabled()) {
|
|
852
544
|
return;
|
|
853
545
|
}
|
|
854
546
|
const permissionRepo = await this.getPermissionRepository();
|
|
855
547
|
const roleRepo = await this.getRoleRepository();
|
|
856
|
-
const dataSource =
|
|
548
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
857
549
|
const companyRoles = await roleRepo.find({
|
|
858
550
|
where: {
|
|
859
551
|
companyId,
|
|
@@ -870,14 +562,15 @@ export class PermissionService {
|
|
|
870
562
|
IamPermissionType.USER_ROLE,
|
|
871
563
|
IamPermissionType.USER_ACTION
|
|
872
564
|
]),
|
|
565
|
+
sourceType: IamEntityType.USER,
|
|
873
566
|
companyId
|
|
874
567
|
},
|
|
875
568
|
select: [
|
|
876
|
-
'
|
|
569
|
+
'sourceId'
|
|
877
570
|
]
|
|
878
571
|
});
|
|
879
572
|
const affectedUserIds = [
|
|
880
|
-
...new Set(affectedUserRows.map((row)=>row.
|
|
573
|
+
...new Set(affectedUserRows.map((row)=>row.sourceId))
|
|
881
574
|
];
|
|
882
575
|
await dataSource.transaction(async (manager)=>{
|
|
883
576
|
const txRepo = manager.getRepository(permissionRepo.target);
|
|
@@ -919,17 +612,20 @@ export class PermissionService {
|
|
|
919
612
|
IamPermissionType.USER_ROLE,
|
|
920
613
|
IamPermissionType.USER_ACTION
|
|
921
614
|
]),
|
|
922
|
-
|
|
923
|
-
|
|
615
|
+
companyId,
|
|
616
|
+
branchId
|
|
924
617
|
};
|
|
925
618
|
const affectedUserRows = await permissionRepo.find({
|
|
926
|
-
where
|
|
619
|
+
where: {
|
|
620
|
+
...where,
|
|
621
|
+
sourceType: IamEntityType.USER
|
|
622
|
+
},
|
|
927
623
|
select: [
|
|
928
|
-
'
|
|
624
|
+
'sourceId'
|
|
929
625
|
]
|
|
930
626
|
});
|
|
931
627
|
const affectedUserIds = [
|
|
932
|
-
...new Set(affectedUserRows.map((row)=>row.
|
|
628
|
+
...new Set(affectedUserRows.map((row)=>row.sourceId))
|
|
933
629
|
];
|
|
934
630
|
await permissionRepo.delete(where);
|
|
935
631
|
if (affectedUserIds.length > 0) {
|