@flusys/nestjs-iam 6.2.0 → 6.2.2

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.
@@ -544,18 +544,11 @@ _ts_decorate([
544
544
  ], UserRoleResponseDto.prototype, "createdAt", void 0);
545
545
  let FrontendActionDto = class FrontendActionDto {
546
546
  constructor(){
547
- _define_property(this, "id", void 0);
548
547
  _define_property(this, "code", void 0);
549
548
  _define_property(this, "name", void 0);
550
549
  _define_property(this, "description", void 0);
551
550
  }
552
551
  };
553
- _ts_decorate([
554
- (0, _swagger.ApiProperty)({
555
- description: 'Action ID'
556
- }),
557
- _ts_metadata("design:type", String)
558
- ], FrontendActionDto.prototype, "id", void 0);
559
552
  _ts_decorate([
560
553
  (0, _swagger.ApiProperty)({
561
554
  description: 'Action code'
@@ -166,7 +166,6 @@ let PermissionCacheService = class PermissionCacheService {
166
166
  }
167
167
  return {
168
168
  frontendActions: frontendActions.map((a)=>({
169
- id: a.id,
170
169
  code: a.code,
171
170
  name: a.name,
172
171
  description: a.description
@@ -392,7 +391,8 @@ let PermissionCacheService = class PermissionCacheService {
392
391
  } catch {
393
392
  trackedKeys = [];
394
393
  }
395
- if (!branchIds?.length) {
394
+ const hasCompanyWideScope = branchIds?.some((branchId)=>!branchId);
395
+ if (!branchIds?.length || hasCompanyWideScope) {
396
396
  trackedKeys.forEach((key)=>keysToDelete.add(key));
397
397
  keysToDelete.add(trackedKeysKey);
398
398
  await untrackScope(trackedKeysKey);
@@ -211,8 +211,6 @@ describe('PermissionCacheService', ()=>{
211
211
  expect(mockCacheManager.del).not.toHaveBeenCalledWith(expect.stringContaining('permission-tracked-scopes'));
212
212
  });
213
213
  it('invalidates only the requested branch keys, keeping remaining branches tracked', async ()=>{
214
- // company-scoped key format only differentiates by branchId when the guard config
215
- // enables the company feature — otherwise every branch maps to the same cache key.
216
214
  const guardConfig = {
217
215
  enableCompanyFeature: true
218
216
  };
@@ -229,18 +227,15 @@ describe('PermissionCacheService', ()=>{
229
227
  ];
230
228
  return undefined;
231
229
  });
232
- // companyId given directly invokes invalidateScope (bypasses the tracked-scopes fan-out)
233
230
  await service.invalidateUser('user-1', 'company-1', [
234
231
  'branch-a'
235
232
  ]);
236
- // the specific branch key gets deleted
237
233
  const deletedKeys = mockCacheManager.del.mock.calls.map(([key])=>key);
238
234
  expect(deletedKeys).toContain((0, _nestjsshared.buildPermissionCacheKey)({
239
235
  userId: 'user-1',
240
236
  companyId: 'company-1',
241
237
  branchId: 'branch-a'
242
238
  }, guardConfig));
243
- // remaining tracked keys get re-saved rather than the tracked-keys index being wiped
244
239
  expect(mockCacheManager.set).toHaveBeenCalledWith(expect.stringContaining('permission-tracked-keys'), expect.arrayContaining([
245
240
  keptKey,
246
241
  'some-other-tracked-key'
@@ -269,6 +264,37 @@ describe('PermissionCacheService', ()=>{
269
264
  expect(deletedKeys).toContain(onlyKey);
270
265
  expect(deletedKeys.some((k)=>String(k).includes('permission-tracked-keys'))).toBe(true);
271
266
  });
267
+ it('wipes every tracked branch key when a null branchId (company-wide grant) is in the scope list', async ()=>{
268
+ const guardConfig = {
269
+ enableCompanyFeature: true
270
+ };
271
+ const service = buildService(guardConfig);
272
+ const branchAKey = (0, _nestjsshared.buildPermissionCacheKey)({
273
+ userId: 'user-1',
274
+ companyId: 'company-1',
275
+ branchId: 'branch-a'
276
+ }, guardConfig);
277
+ const branchBKey = (0, _nestjsshared.buildPermissionCacheKey)({
278
+ userId: 'user-1',
279
+ companyId: 'company-1',
280
+ branchId: 'branch-b'
281
+ }, guardConfig);
282
+ const trackedKeysKey = 'permission-tracked-keys:user:user-1:company:company-1';
283
+ mockCacheManager.get.mockImplementation(async (key)=>{
284
+ if (key.includes('permission-tracked-keys')) return [
285
+ branchAKey,
286
+ branchBKey
287
+ ];
288
+ return undefined;
289
+ });
290
+ await service.invalidateUser('user-1', 'company-1', [
291
+ null
292
+ ]);
293
+ const deletedKeys = mockCacheManager.del.mock.calls.map(([key])=>key);
294
+ expect(deletedKeys).toContain(branchAKey);
295
+ expect(deletedKeys).toContain(branchBKey);
296
+ expect(deletedKeys.some((k)=>String(k).includes(trackedKeysKey))).toBe(true);
297
+ });
272
298
  it('without a companyId, invalidates every tracked scope for the user and clears the scopes index', async ()=>{
273
299
  const service = buildService();
274
300
  // scopes list mixes a nested tracked-keys index (company scope) with a raw cache key (untracked scope)
@@ -400,7 +426,7 @@ describe('PermissionCacheService', ()=>{
400
426
  'user'
401
427
  ]);
402
428
  expect(result.frontendActions).toHaveLength(1);
403
- expect(result.frontendActions[0].id).toBe('child-1');
429
+ expect(result.frontendActions[0].code).toBe('user.view');
404
430
  });
405
431
  it('returns an empty frontendActions list when parentCodes resolve to no known parents', async ()=>{
406
432
  const service = buildService();
@@ -563,7 +589,6 @@ describe('PermissionCacheService', ()=>{
563
589
  expect(result.cachedEndpoints).toBe(1);
564
590
  expect(result.frontendActions).toEqual([
565
591
  {
566
- id: 'action-1',
567
592
  code: 'user.view',
568
593
  name: 'View Users',
569
594
  description: null
@@ -606,9 +631,6 @@ describe('PermissionCacheService', ()=>{
606
631
  expect(result).toBe(2);
607
632
  });
608
633
  it('wipes every tracked cache entry per member instead of only branches with an existing permission row', async ()=>{
609
- // Company-wide action changes are merged into every branch a member queries, including
610
- // branches where the member holds no explicit permission row — so invalidation must not
611
- // be narrowed to whatever branch_ids happen to already exist in the permission table.
612
634
  const service = buildService();
613
635
  mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
614
636
  mockRawMany(mockPermissionRepo, [
@@ -665,9 +687,6 @@ describe('PermissionCacheService', ()=>{
665
687
  expect(result).toBe(1);
666
688
  });
667
689
  it('wipes every tracked cache entry for role members instead of only branches with an existing permission row', async ()=>{
668
- // Role actions are merged into every branch a member queries, including branches
669
- // where the member holds no explicit permission row — so invalidation must not be
670
- // narrowed to whatever branch_ids happen to already exist in the permission table.
671
690
  const service = buildService();
672
691
  mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
673
692
  mockPermissionRepo.find.mockResolvedValue([
@@ -579,7 +579,6 @@ describe('PermissionService', ()=>{
579
579
  const response = {
580
580
  frontendActions: [
581
581
  {
582
- id: 'action-1',
583
582
  code: 'user.view',
584
583
  name: 'View',
585
584
  description: null
@@ -77,7 +77,6 @@ export declare class UserRoleResponseDto {
77
77
  createdAt: Date;
78
78
  }
79
79
  export declare class FrontendActionDto {
80
- id: string;
81
80
  code: string;
82
81
  name: string;
83
82
  description: string | null;
@@ -478,18 +478,11 @@ _ts_decorate([
478
478
  ], UserRoleResponseDto.prototype, "createdAt", void 0);
479
479
  export class FrontendActionDto {
480
480
  constructor(){
481
- _define_property(this, "id", void 0);
482
481
  _define_property(this, "code", void 0);
483
482
  _define_property(this, "name", void 0);
484
483
  _define_property(this, "description", void 0);
485
484
  }
486
485
  }
487
- _ts_decorate([
488
- ApiProperty({
489
- description: 'Action ID'
490
- }),
491
- _ts_metadata("design:type", String)
492
- ], FrontendActionDto.prototype, "id", void 0);
493
486
  _ts_decorate([
494
487
  ApiProperty({
495
488
  description: 'Action code'
@@ -149,7 +149,6 @@ export class PermissionCacheService {
149
149
  }
150
150
  return {
151
151
  frontendActions: frontendActions.map((a)=>({
152
- id: a.id,
153
152
  code: a.code,
154
153
  name: a.name,
155
154
  description: a.description
@@ -375,7 +374,8 @@ export class PermissionCacheService {
375
374
  } catch {
376
375
  trackedKeys = [];
377
376
  }
378
- if (!branchIds?.length) {
377
+ const hasCompanyWideScope = branchIds?.some((branchId)=>!branchId);
378
+ if (!branchIds?.length || hasCompanyWideScope) {
379
379
  trackedKeys.forEach((key)=>keysToDelete.add(key));
380
380
  keysToDelete.add(trackedKeysKey);
381
381
  await untrackScope(trackedKeysKey);
@@ -207,8 +207,6 @@ describe('PermissionCacheService', ()=>{
207
207
  expect(mockCacheManager.del).not.toHaveBeenCalledWith(expect.stringContaining('permission-tracked-scopes'));
208
208
  });
209
209
  it('invalidates only the requested branch keys, keeping remaining branches tracked', async ()=>{
210
- // company-scoped key format only differentiates by branchId when the guard config
211
- // enables the company feature — otherwise every branch maps to the same cache key.
212
210
  const guardConfig = {
213
211
  enableCompanyFeature: true
214
212
  };
@@ -225,18 +223,15 @@ describe('PermissionCacheService', ()=>{
225
223
  ];
226
224
  return undefined;
227
225
  });
228
- // companyId given directly invokes invalidateScope (bypasses the tracked-scopes fan-out)
229
226
  await service.invalidateUser('user-1', 'company-1', [
230
227
  'branch-a'
231
228
  ]);
232
- // the specific branch key gets deleted
233
229
  const deletedKeys = mockCacheManager.del.mock.calls.map(([key])=>key);
234
230
  expect(deletedKeys).toContain(buildPermissionCacheKey({
235
231
  userId: 'user-1',
236
232
  companyId: 'company-1',
237
233
  branchId: 'branch-a'
238
234
  }, guardConfig));
239
- // remaining tracked keys get re-saved rather than the tracked-keys index being wiped
240
235
  expect(mockCacheManager.set).toHaveBeenCalledWith(expect.stringContaining('permission-tracked-keys'), expect.arrayContaining([
241
236
  keptKey,
242
237
  'some-other-tracked-key'
@@ -265,6 +260,37 @@ describe('PermissionCacheService', ()=>{
265
260
  expect(deletedKeys).toContain(onlyKey);
266
261
  expect(deletedKeys.some((k)=>String(k).includes('permission-tracked-keys'))).toBe(true);
267
262
  });
263
+ it('wipes every tracked branch key when a null branchId (company-wide grant) is in the scope list', async ()=>{
264
+ const guardConfig = {
265
+ enableCompanyFeature: true
266
+ };
267
+ const service = buildService(guardConfig);
268
+ const branchAKey = buildPermissionCacheKey({
269
+ userId: 'user-1',
270
+ companyId: 'company-1',
271
+ branchId: 'branch-a'
272
+ }, guardConfig);
273
+ const branchBKey = buildPermissionCacheKey({
274
+ userId: 'user-1',
275
+ companyId: 'company-1',
276
+ branchId: 'branch-b'
277
+ }, guardConfig);
278
+ const trackedKeysKey = 'permission-tracked-keys:user:user-1:company:company-1';
279
+ mockCacheManager.get.mockImplementation(async (key)=>{
280
+ if (key.includes('permission-tracked-keys')) return [
281
+ branchAKey,
282
+ branchBKey
283
+ ];
284
+ return undefined;
285
+ });
286
+ await service.invalidateUser('user-1', 'company-1', [
287
+ null
288
+ ]);
289
+ const deletedKeys = mockCacheManager.del.mock.calls.map(([key])=>key);
290
+ expect(deletedKeys).toContain(branchAKey);
291
+ expect(deletedKeys).toContain(branchBKey);
292
+ expect(deletedKeys.some((k)=>String(k).includes(trackedKeysKey))).toBe(true);
293
+ });
268
294
  it('without a companyId, invalidates every tracked scope for the user and clears the scopes index', async ()=>{
269
295
  const service = buildService();
270
296
  // scopes list mixes a nested tracked-keys index (company scope) with a raw cache key (untracked scope)
@@ -396,7 +422,7 @@ describe('PermissionCacheService', ()=>{
396
422
  'user'
397
423
  ]);
398
424
  expect(result.frontendActions).toHaveLength(1);
399
- expect(result.frontendActions[0].id).toBe('child-1');
425
+ expect(result.frontendActions[0].code).toBe('user.view');
400
426
  });
401
427
  it('returns an empty frontendActions list when parentCodes resolve to no known parents', async ()=>{
402
428
  const service = buildService();
@@ -559,7 +585,6 @@ describe('PermissionCacheService', ()=>{
559
585
  expect(result.cachedEndpoints).toBe(1);
560
586
  expect(result.frontendActions).toEqual([
561
587
  {
562
- id: 'action-1',
563
588
  code: 'user.view',
564
589
  name: 'View Users',
565
590
  description: null
@@ -602,9 +627,6 @@ describe('PermissionCacheService', ()=>{
602
627
  expect(result).toBe(2);
603
628
  });
604
629
  it('wipes every tracked cache entry per member instead of only branches with an existing permission row', async ()=>{
605
- // Company-wide action changes are merged into every branch a member queries, including
606
- // branches where the member holds no explicit permission row — so invalidation must not
607
- // be narrowed to whatever branch_ids happen to already exist in the permission table.
608
630
  const service = buildService();
609
631
  mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
610
632
  mockRawMany(mockPermissionRepo, [
@@ -661,9 +683,6 @@ describe('PermissionCacheService', ()=>{
661
683
  expect(result).toBe(1);
662
684
  });
663
685
  it('wipes every tracked cache entry for role members instead of only branches with an existing permission row', async ()=>{
664
- // Role actions are merged into every branch a member queries, including branches
665
- // where the member holds no explicit permission row — so invalidation must not be
666
- // narrowed to whatever branch_ids happen to already exist in the permission table.
667
686
  const service = buildService();
668
687
  mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
669
688
  mockPermissionRepo.find.mockResolvedValue([
@@ -575,7 +575,6 @@ describe('PermissionService', ()=>{
575
575
  const response = {
576
576
  frontendActions: [
577
577
  {
578
- id: 'action-1',
579
578
  code: 'user.view',
580
579
  name: 'View',
581
580
  description: null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-iam",
3
- "version": "6.2.0",
3
+ "version": "6.2.2",
4
4
  "description": "Identity and Access Management (IAM) module for NestJS applications",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -90,7 +90,7 @@
90
90
  "express": "^5.0.0"
91
91
  },
92
92
  "dependencies": {
93
- "@flusys/nestjs-core": "6.2.0",
94
- "@flusys/nestjs-shared": "6.2.0"
93
+ "@flusys/nestjs-core": "6.2.2",
94
+ "@flusys/nestjs-shared": "6.2.2"
95
95
  }
96
96
  }