adapt-authoring-roles 1.5.1 → 1.5.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.
@@ -1,32 +1,17 @@
1
1
  name: Release
2
+
2
3
  on:
3
4
  push:
4
5
  branches:
5
6
  - master
6
7
 
8
+ permissions:
9
+ contents: write
10
+ issues: write
11
+ pull-requests: write
12
+ id-token: write
13
+ packages: write
14
+
7
15
  jobs:
8
16
  release:
9
- name: Release
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: write # to be able to publish a GitHub release
13
- issues: write # to be able to comment on released issues
14
- pull-requests: write # to be able to comment on released pull requests
15
- id-token: write # to enable use of OIDC for trusted publishing and npm provenance
16
- steps:
17
- - name: Checkout
18
- uses: actions/checkout@v3
19
- with:
20
- fetch-depth: 0
21
- - name: Setup Node.js
22
- uses: actions/setup-node@v3
23
- with:
24
- node-version: 'lts/*'
25
- - name: Update npm
26
- run: npm install -g npm@latest
27
- - name: Install dependencies
28
- run: npm install
29
- - name: Release
30
- env:
31
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
- run: npx semantic-release
17
+ uses: adaptlearning/semantic-release-config/.github/workflows/release.yml@master
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-roles",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Module for managing user roles",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-roles",
6
6
  "license": "GPL-3.0",
@@ -11,37 +11,11 @@
11
11
  "test": "node --test 'tests/**/*.spec.js'"
12
12
  },
13
13
  "devDependencies": {
14
- "@semantic-release/git": "^10.0.1",
15
- "conventional-changelog-eslint": "^6.0.0",
16
- "semantic-release": "^25.0.2",
14
+ "@adaptlearning/semantic-release-config": "^1.0.0",
17
15
  "standard": "^17.1.0"
18
16
  },
19
17
  "release": {
20
- "plugins": [
21
- [
22
- "@semantic-release/commit-analyzer",
23
- {
24
- "preset": "eslint"
25
- }
26
- ],
27
- [
28
- "@semantic-release/release-notes-generator",
29
- {
30
- "preset": "eslint"
31
- }
32
- ],
33
- "@semantic-release/npm",
34
- "@semantic-release/github",
35
- [
36
- "@semantic-release/git",
37
- {
38
- "assets": [
39
- "package.json"
40
- ],
41
- "message": "Chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
42
- }
43
- ]
44
- ]
18
+ "extends": "@adaptlearning/semantic-release-config"
45
19
  },
46
20
  "dependencies": {
47
21
  "adapt-authoring-api": "^3.0.0"
@@ -49,7 +23,7 @@
49
23
  "peerDependencies": {
50
24
  "adapt-authoring-auth": "^2.0.0",
51
25
  "adapt-authoring-auth-local": "^2.0.0",
52
- "adapt-authoring-core": "^2.0.0",
26
+ "adapt-authoring-core": "^3.0.0",
53
27
  "adapt-authoring-mongodb": "^3.0.0",
54
28
  "adapt-authoring-users": "^1.0.2"
55
29
  }
@@ -41,6 +41,58 @@ function createInstance (overrides) {
41
41
  return instance
42
42
  }
43
43
 
44
+ /** Build an instance pre-wired for isTargetSuper tests */
45
+ function createIsTargetSuperInstance (userRoles) {
46
+ const usersModule = {
47
+ find: mock.fn(async () => [{ roles: userRoles }])
48
+ }
49
+ const inst = createInstance({
50
+ app: {
51
+ waitForModule: mock.fn(async () => usersModule),
52
+ errors: {}
53
+ },
54
+ getSuperRoleId: mock.fn(async () => 'super1')
55
+ })
56
+ return { inst, usersModule }
57
+ }
58
+
59
+ /** Build an instance pre-wired for onUpdateRoles disavow tests */
60
+ function createDisavowInstance () {
61
+ const disavowMock = mock.fn(async () => {})
62
+ const authModule = { authentication: { disavowUser: disavowMock } }
63
+ const inst = createInstance({
64
+ app: {
65
+ waitForModule: mock.fn(async () => authModule),
66
+ errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
67
+ }
68
+ })
69
+ return { inst, disavowMock }
70
+ }
71
+
72
+ /** Build an instance pre-wired for initConfigRoles tests */
73
+ function createConfigRolesInstance (roleDefinitions, overrides = {}) {
74
+ const mongodbMock = {
75
+ replace: mock.fn(async () => {}),
76
+ ...overrides.mongodb
77
+ }
78
+ const inst = createInstance({
79
+ find: mock.fn(async () => []),
80
+ insert: mock.fn(async (data) => data),
81
+ getConfig: mock.fn((key) => {
82
+ if (key === 'roleDefinitions') return roleDefinitions
83
+ return []
84
+ }),
85
+ collectionName: 'roles',
86
+ schemaName: 'role',
87
+ app: {
88
+ waitForModule: mock.fn(async () => mongodbMock),
89
+ errors: {}
90
+ },
91
+ ...overrides
92
+ })
93
+ return { inst, mongodbMock }
94
+ }
95
+
44
96
  // ── Method references (copied from source for isolated testing) ─────
45
97
 
46
98
  async function setValues () {
@@ -335,76 +387,31 @@ describe('RolesModule', () => {
335
387
 
336
388
  describe('isTargetSuper', () => {
337
389
  it('should return true if user has only the super role', async () => {
338
- const usersModule = {
339
- find: mock.fn(async () => [{ roles: ['super1'] }])
340
- }
341
- const inst = createInstance({
342
- app: {
343
- waitForModule: mock.fn(async () => usersModule),
344
- errors: {}
345
- },
346
- getSuperRoleId: mock.fn(async () => 'super1')
347
- })
390
+ const { inst } = createIsTargetSuperInstance(['super1'])
348
391
  const result = await isTargetSuper.call(inst, 'user1')
349
392
  assert.equal(result, true)
350
393
  })
351
394
 
352
395
  it('should return false if user has multiple roles', async () => {
353
- const usersModule = {
354
- find: mock.fn(async () => [{ roles: ['super1', 'other'] }])
355
- }
356
- const inst = createInstance({
357
- app: {
358
- waitForModule: mock.fn(async () => usersModule),
359
- errors: {}
360
- },
361
- getSuperRoleId: mock.fn(async () => 'super1')
362
- })
396
+ const { inst } = createIsTargetSuperInstance(['super1', 'other'])
363
397
  const result = await isTargetSuper.call(inst, 'user1')
364
398
  assert.equal(result, false)
365
399
  })
366
400
 
367
401
  it('should return false if user has a non-super role', async () => {
368
- const usersModule = {
369
- find: mock.fn(async () => [{ roles: ['regular1'] }])
370
- }
371
- const inst = createInstance({
372
- app: {
373
- waitForModule: mock.fn(async () => usersModule),
374
- errors: {}
375
- },
376
- getSuperRoleId: mock.fn(async () => 'super1')
377
- })
402
+ const { inst } = createIsTargetSuperInstance(['regular1'])
378
403
  const result = await isTargetSuper.call(inst, 'user1')
379
404
  assert.equal(result, false)
380
405
  })
381
406
 
382
407
  it('should return false if user has no roles', async () => {
383
- const usersModule = {
384
- find: mock.fn(async () => [{ roles: [] }])
385
- }
386
- const inst = createInstance({
387
- app: {
388
- waitForModule: mock.fn(async () => usersModule),
389
- errors: {}
390
- },
391
- getSuperRoleId: mock.fn(async () => 'super1')
392
- })
408
+ const { inst } = createIsTargetSuperInstance([])
393
409
  const result = await isTargetSuper.call(inst, 'user1')
394
410
  assert.equal(result, false)
395
411
  })
396
412
 
397
413
  it('should pass projection for roles only', async () => {
398
- const usersModule = {
399
- find: mock.fn(async () => [{ roles: ['super1'] }])
400
- }
401
- const inst = createInstance({
402
- app: {
403
- waitForModule: mock.fn(async () => usersModule),
404
- errors: {}
405
- },
406
- getSuperRoleId: mock.fn(async () => 'super1')
407
- })
414
+ const { inst, usersModule } = createIsTargetSuperInstance(['super1'])
408
415
  await isTargetSuper.call(inst, 'user1')
409
416
  const findArgs = usersModule.find.mock.calls[0].arguments
410
417
  assert.deepEqual(findArgs[1], { projection: { roles: 1 } })
@@ -460,14 +467,7 @@ describe('RolesModule', () => {
460
467
  })
461
468
 
462
469
  it('should not return early for DELETE even without roles', async () => {
463
- const disavowMock = mock.fn(async () => {})
464
- const authModule = { authentication: { disavowUser: disavowMock } }
465
- const inst = createInstance({
466
- app: {
467
- waitForModule: mock.fn(async () => authModule),
468
- errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
469
- }
470
- })
470
+ const { inst, disavowMock } = createDisavowInstance()
471
471
  const req = createReq({
472
472
  method: 'DELETE',
473
473
  apiData: { modifying: false, data: {}, query: { _id: 'target1' } },
@@ -482,14 +482,7 @@ describe('RolesModule', () => {
482
482
  })
483
483
 
484
484
  it('should skip auth checks for super users', async () => {
485
- const disavowMock = mock.fn(async () => {})
486
- const authModule = { authentication: { disavowUser: disavowMock } }
487
- const inst = createInstance({
488
- app: {
489
- waitForModule: mock.fn(async () => authModule),
490
- errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
491
- }
492
- })
485
+ const { inst, disavowMock } = createDisavowInstance()
493
486
  const req = createReq({
494
487
  auth: {
495
488
  isSuper: true,
@@ -557,14 +550,7 @@ describe('RolesModule', () => {
557
550
  })
558
551
 
559
552
  it('should disavow user for non-POST methods', async () => {
560
- const disavowMock = mock.fn(async () => {})
561
- const authModule = { authentication: { disavowUser: disavowMock } }
562
- const inst = createInstance({
563
- app: {
564
- waitForModule: mock.fn(async () => authModule),
565
- errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
566
- }
567
- })
553
+ const { inst, disavowMock } = createDisavowInstance()
568
554
  const req = createReq({
569
555
  method: 'PUT',
570
556
  params: { _id: 'param-id' },
@@ -582,14 +568,7 @@ describe('RolesModule', () => {
582
568
  })
583
569
 
584
570
  it('should use body._id if params._id is not set', async () => {
585
- const disavowMock = mock.fn(async () => {})
586
- const authModule = { authentication: { disavowUser: disavowMock } }
587
- const inst = createInstance({
588
- app: {
589
- waitForModule: mock.fn(async () => authModule),
590
- errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
591
- }
592
- })
571
+ const { inst, disavowMock } = createDisavowInstance()
593
572
  const req = createReq({
594
573
  method: 'PUT',
595
574
  params: {},
@@ -608,14 +587,7 @@ describe('RolesModule', () => {
608
587
  })
609
588
 
610
589
  it('should not disavow user for POST method', async () => {
611
- const disavowMock = mock.fn(async () => {})
612
- const authModule = { authentication: { disavowUser: disavowMock } }
613
- const inst = createInstance({
614
- app: {
615
- waitForModule: mock.fn(async () => authModule),
616
- errors: { UNAUTHORISED: new Error('UNAUTHORISED') }
617
- }
618
- })
590
+ const { inst, disavowMock } = createDisavowInstance()
619
591
  const req = createReq({
620
592
  method: 'POST',
621
593
  auth: {
@@ -691,105 +663,42 @@ describe('RolesModule', () => {
691
663
 
692
664
  describe('initConfigRoles', () => {
693
665
  it('should insert new roles that do not exist', async () => {
694
- const insertMock = mock.fn(async (data) => data)
695
- const inst = createInstance({
696
- find: mock.fn(async () => []),
697
- insert: insertMock,
698
- getConfig: mock.fn((key) => {
699
- if (key === 'roleDefinitions') {
700
- return [{
701
- shortName: 'newrole',
702
- displayName: 'New Role',
703
- scopes: ['read:all']
704
- }]
705
- }
706
- return []
707
- }),
708
- collectionName: 'roles',
709
- schemaName: 'role',
710
- app: {
711
- waitForModule: mock.fn(async () => ({ replace: mock.fn() })),
712
- errors: {}
713
- }
714
- })
666
+ const { inst } = createConfigRolesInstance([
667
+ { shortName: 'newrole', displayName: 'New Role', scopes: ['read:all'] }
668
+ ])
715
669
  await initConfigRoles.call(inst)
716
- assert.equal(insertMock.mock.callCount(), 1)
670
+ assert.equal(inst.insert.mock.callCount(), 1)
717
671
  assert.equal(
718
- insertMock.mock.calls[0].arguments[0].shortName, 'newrole'
672
+ inst.insert.mock.calls[0].arguments[0].shortName, 'newrole'
719
673
  )
720
674
  })
721
675
 
722
676
  it('should replace existing roles', async () => {
723
- const replaceMock = mock.fn(async () => {})
724
- const inst = createInstance({
725
- find: mock.fn(async () => [{ _id: 'existing1', shortName: 'admin' }]),
726
- insert: mock.fn(async () => {}),
727
- getConfig: mock.fn((key) => {
728
- if (key === 'roleDefinitions') {
729
- return [{
730
- shortName: 'admin', displayName: 'Admin', scopes: ['*:*']
731
- }]
732
- }
733
- return []
734
- }),
735
- collectionName: 'roles',
736
- schemaName: 'role',
737
- app: {
738
- waitForModule: mock.fn(async () => ({ replace: replaceMock })),
739
- errors: {}
740
- }
741
- })
677
+ const { inst, mongodbMock } = createConfigRolesInstance(
678
+ [{ shortName: 'admin', displayName: 'Admin', scopes: ['*:*'] }],
679
+ { find: mock.fn(async () => [{ _id: 'existing1', shortName: 'admin' }]) }
680
+ )
742
681
  await initConfigRoles.call(inst)
743
- assert.equal(replaceMock.mock.callCount(), 1)
682
+ assert.equal(mongodbMock.replace.mock.callCount(), 1)
744
683
  assert.deepEqual(
745
- replaceMock.mock.calls[0].arguments[1], { _id: 'existing1' }
684
+ mongodbMock.replace.mock.calls[0].arguments[1], { _id: 'existing1' }
746
685
  )
747
686
  })
748
687
 
749
688
  it('should log debug on successful insert', async () => {
750
- const inst = createInstance({
751
- find: mock.fn(async () => []),
752
- insert: mock.fn(async () => {}),
753
- getConfig: mock.fn((key) => {
754
- if (key === 'roleDefinitions') {
755
- return [{
756
- shortName: 'testrole', displayName: 'Test', scopes: []
757
- }]
758
- }
759
- return []
760
- }),
761
- collectionName: 'roles',
762
- schemaName: 'role',
763
- app: {
764
- waitForModule: mock.fn(async () => ({})),
765
- errors: {}
766
- }
767
- })
689
+ const { inst } = createConfigRolesInstance([
690
+ { shortName: 'testrole', displayName: 'Test', scopes: [] }
691
+ ], { insert: mock.fn(async () => {}) })
768
692
  await initConfigRoles.call(inst)
769
693
  assert.equal(inst.log.mock.calls[0].arguments[0], 'debug')
770
694
  assert.equal(inst.log.mock.calls[0].arguments[1], 'INSERT')
771
695
  })
772
696
 
773
697
  it('should log debug on successful replace', async () => {
774
- const inst = createInstance({
775
- find: mock.fn(async () => [{ _id: 'id1', shortName: 'admin' }]),
776
- getConfig: mock.fn((key) => {
777
- if (key === 'roleDefinitions') {
778
- return [{
779
- shortName: 'admin', displayName: 'Admin', scopes: []
780
- }]
781
- }
782
- return []
783
- }),
784
- collectionName: 'roles',
785
- schemaName: 'role',
786
- app: {
787
- waitForModule: mock.fn(async () => ({
788
- replace: mock.fn(async () => {})
789
- })),
790
- errors: {}
791
- }
792
- })
698
+ const { inst } = createConfigRolesInstance(
699
+ [{ shortName: 'admin', displayName: 'Admin', scopes: [] }],
700
+ { find: mock.fn(async () => [{ _id: 'id1', shortName: 'admin' }]) }
701
+ )
793
702
  await initConfigRoles.call(inst)
794
703
  assert.equal(inst.log.mock.calls[0].arguments[0], 'debug')
795
704
  assert.equal(inst.log.mock.calls[0].arguments[1], 'REPLACE')
@@ -798,24 +707,10 @@ describe('RolesModule', () => {
798
707
  it('should suppress duplicate key errors on insert', async () => {
799
708
  const dupError = new Error('duplicate key')
800
709
  dupError.code = 11000
801
- const inst = createInstance({
802
- find: mock.fn(async () => []),
803
- insert: mock.fn(async () => { throw dupError }),
804
- getConfig: mock.fn((key) => {
805
- if (key === 'roleDefinitions') {
806
- return [{
807
- shortName: 'dup', displayName: 'Dup', scopes: []
808
- }]
809
- }
810
- return []
811
- }),
812
- collectionName: 'roles',
813
- schemaName: 'role',
814
- app: {
815
- waitForModule: mock.fn(async () => ({})),
816
- errors: {}
817
- }
818
- })
710
+ const { inst } = createConfigRolesInstance(
711
+ [{ shortName: 'dup', displayName: 'Dup', scopes: [] }],
712
+ { insert: mock.fn(async () => { throw dupError }) }
713
+ )
819
714
  await initConfigRoles.call(inst)
820
715
  const warnCalls = inst.log.mock.calls.filter(
821
716
  c => c.arguments[0] === 'warn'
@@ -826,24 +721,10 @@ describe('RolesModule', () => {
826
721
  it('should log warning for non-duplicate insert errors', async () => {
827
722
  const error = new Error('some error')
828
723
  error.code = 500
829
- const inst = createInstance({
830
- find: mock.fn(async () => []),
831
- insert: mock.fn(async () => { throw error }),
832
- getConfig: mock.fn((key) => {
833
- if (key === 'roleDefinitions') {
834
- return [{
835
- shortName: 'fail', displayName: 'Fail', scopes: []
836
- }]
837
- }
838
- return []
839
- }),
840
- collectionName: 'roles',
841
- schemaName: 'role',
842
- app: {
843
- waitForModule: mock.fn(async () => ({})),
844
- errors: {}
845
- }
846
- })
724
+ const { inst } = createConfigRolesInstance(
725
+ [{ shortName: 'fail', displayName: 'Fail', scopes: [] }],
726
+ { insert: mock.fn(async () => { throw error }) }
727
+ )
847
728
  await initConfigRoles.call(inst)
848
729
  const warnCalls = inst.log.mock.calls.filter(
849
730
  c => c.arguments[0] === 'warn'
@@ -855,25 +736,15 @@ describe('RolesModule', () => {
855
736
  it('should suppress duplicate key errors on replace', async () => {
856
737
  const dupError = new Error('duplicate key')
857
738
  dupError.code = 11000
858
- const inst = createInstance({
859
- find: mock.fn(async () => [{ _id: 'id1', shortName: 'admin' }]),
860
- getConfig: mock.fn((key) => {
861
- if (key === 'roleDefinitions') {
862
- return [{
863
- shortName: 'admin', displayName: 'Admin', scopes: []
864
- }]
865
- }
866
- return []
867
- }),
868
- collectionName: 'roles',
869
- schemaName: 'role',
870
- app: {
871
- waitForModule: mock.fn(async () => ({
739
+ const { inst } = createConfigRolesInstance(
740
+ [{ shortName: 'admin', displayName: 'Admin', scopes: [] }],
741
+ {
742
+ find: mock.fn(async () => [{ _id: 'id1', shortName: 'admin' }]),
743
+ mongodb: {
872
744
  replace: mock.fn(async () => { throw dupError })
873
- })),
874
- errors: {}
745
+ }
875
746
  }
876
- })
747
+ )
877
748
  await initConfigRoles.call(inst)
878
749
  const warnCalls = inst.log.mock.calls.filter(
879
750
  c => c.arguments[0] === 'warn'
@@ -882,13 +753,7 @@ describe('RolesModule', () => {
882
753
  })
883
754
 
884
755
  it('should handle empty roleDefinitions', async () => {
885
- const inst = createInstance({
886
- getConfig: mock.fn(() => []),
887
- app: {
888
- waitForModule: mock.fn(async () => ({})),
889
- errors: {}
890
- }
891
- })
756
+ const { inst } = createConfigRolesInstance([])
892
757
  const result = await initConfigRoles.call(inst)
893
758
  assert.ok(Array.isArray(result))
894
759
  assert.equal(result.length, 0)