@nymphjs/tilmeld 1.0.0-beta.85 → 1.0.0-beta.87
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/Group.d.ts +6 -1
- package/dist/Group.js +140 -54
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.js +18 -0
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +16 -1
- package/dist/User.js +299 -53
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +63 -0
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +31 -0
- package/dist/conf/defaults.js +3 -0
- package/dist/conf/defaults.js.map +1 -1
- package/package.json +8 -8
- package/src/Group.ts +191 -66
- package/src/Tilmeld.ts +31 -1
- package/src/User.test.ts +81 -0
- package/src/User.ts +376 -63
- package/src/conf/d.ts +31 -0
- package/src/conf/defaults.ts +4 -0
package/src/Group.ts
CHANGED
|
@@ -8,11 +8,13 @@ import type {
|
|
|
8
8
|
Selector,
|
|
9
9
|
SerializedEntityData,
|
|
10
10
|
} from '@nymphjs/nymph';
|
|
11
|
-
import { difference } from 'lodash-es';
|
|
11
|
+
import { difference, xor } from 'lodash-es';
|
|
12
|
+
import { splitn } from '@sciactive/splitn';
|
|
12
13
|
|
|
13
14
|
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
14
15
|
import AbleObject from './AbleObject.js';
|
|
15
16
|
import {
|
|
17
|
+
AccessControlError,
|
|
16
18
|
BadDataError,
|
|
17
19
|
BadEmailError,
|
|
18
20
|
BadUsernameError,
|
|
@@ -119,6 +121,10 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
119
121
|
* This should only be used by the backend.
|
|
120
122
|
*/
|
|
121
123
|
private $skipAcWhenDeleting = false;
|
|
124
|
+
/**
|
|
125
|
+
* Used to save the current groupname for domain admin permissions.
|
|
126
|
+
*/
|
|
127
|
+
public $originalGroupname?: string;
|
|
122
128
|
|
|
123
129
|
static async factoryGroupname(
|
|
124
130
|
groupname?: string,
|
|
@@ -281,55 +287,37 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
281
287
|
}
|
|
282
288
|
|
|
283
289
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
284
|
-
const tilmeld = enforceTilmeld(this);
|
|
285
290
|
this.$check();
|
|
286
291
|
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
input.data.abilities?.includes('system/admin') !==
|
|
290
|
-
this.$data.abilities?.includes('system/admin') &&
|
|
291
|
-
(!tilmeld.currentUser ||
|
|
292
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
293
|
-
) {
|
|
294
|
-
throw new BadDataError(
|
|
295
|
-
"You don't have the authority to grant or revoke system/admin.",
|
|
296
|
-
);
|
|
292
|
+
if ('abilities' in input.data) {
|
|
293
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
297
294
|
}
|
|
298
295
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
input.data.abilities?.includes('tilmeld/admin') !==
|
|
302
|
-
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
303
|
-
(!tilmeld.currentUser ||
|
|
304
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
305
|
-
) {
|
|
306
|
-
throw new BadDataError(
|
|
307
|
-
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
308
|
-
);
|
|
309
|
-
}
|
|
296
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
297
|
+
}
|
|
310
298
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
(
|
|
316
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
317
|
-
) {
|
|
318
|
-
throw new BadDataError(
|
|
319
|
-
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
320
|
-
);
|
|
299
|
+
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
300
|
+
this.$check();
|
|
301
|
+
|
|
302
|
+
if ('abilities' in patch.set) {
|
|
303
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
321
304
|
}
|
|
322
305
|
|
|
323
|
-
super.$
|
|
306
|
+
super.$jsonAcceptPatch(patch, allowConflict);
|
|
324
307
|
}
|
|
325
308
|
|
|
326
|
-
|
|
309
|
+
private $checkIncomingAbilities(incoming: any) {
|
|
327
310
|
const tilmeld = enforceTilmeld(this);
|
|
328
|
-
this.$check();
|
|
329
311
|
|
|
330
312
|
if (
|
|
331
|
-
|
|
332
|
-
|
|
313
|
+
!Array.isArray(incoming) ||
|
|
314
|
+
incoming.find((ability) => typeof ability !== 'string')
|
|
315
|
+
) {
|
|
316
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
incoming.includes('system/admin') !==
|
|
333
321
|
this.$data.abilities?.includes('system/admin') &&
|
|
334
322
|
(!tilmeld.currentUser ||
|
|
335
323
|
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
@@ -340,8 +328,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
340
328
|
}
|
|
341
329
|
|
|
342
330
|
if (
|
|
343
|
-
'
|
|
344
|
-
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
331
|
+
incoming.includes('tilmeld/admin') !==
|
|
345
332
|
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
346
333
|
(!tilmeld.currentUser ||
|
|
347
334
|
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
@@ -352,8 +339,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
352
339
|
}
|
|
353
340
|
|
|
354
341
|
if (
|
|
355
|
-
'
|
|
356
|
-
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
342
|
+
incoming.includes('tilmeld/switch') !==
|
|
357
343
|
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
358
344
|
(!tilmeld.currentUser ||
|
|
359
345
|
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
@@ -363,11 +349,32 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
363
349
|
);
|
|
364
350
|
}
|
|
365
351
|
|
|
366
|
-
|
|
352
|
+
if (
|
|
353
|
+
xor(
|
|
354
|
+
incoming.filter((ability: string) => ability.startsWith('tilmeld/')),
|
|
355
|
+
this.$data.abilities?.filter((ability: string) =>
|
|
356
|
+
ability.startsWith('tilmeld/'),
|
|
357
|
+
) ?? [],
|
|
358
|
+
).length &&
|
|
359
|
+
(!tilmeld.currentUser ||
|
|
360
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
361
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))
|
|
362
|
+
) {
|
|
363
|
+
throw new BadDataError(
|
|
364
|
+
"You don't have the authority to grant or revoke Tilmeld abilities.",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
public $putData(
|
|
370
|
-
|
|
369
|
+
public $putData(
|
|
370
|
+
data: EntityData,
|
|
371
|
+
sdata?: SerializedEntityData,
|
|
372
|
+
source?: 'server',
|
|
373
|
+
) {
|
|
374
|
+
super.$putData(data, sdata, source);
|
|
375
|
+
if (source === 'server') {
|
|
376
|
+
this.$originalGroupname = this.$data.groupname;
|
|
377
|
+
}
|
|
371
378
|
this.$updateDataProtection();
|
|
372
379
|
}
|
|
373
380
|
|
|
@@ -379,6 +386,13 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
379
386
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
380
387
|
const tilmeld = enforceTilmeld(this);
|
|
381
388
|
let user = givenUser ?? tilmeld.User.current();
|
|
389
|
+
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
390
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
391
|
+
: [this.$data.groupname, null];
|
|
392
|
+
// Lowercase the domain part.
|
|
393
|
+
if (domain) {
|
|
394
|
+
domain = domain.toLowerCase();
|
|
395
|
+
}
|
|
382
396
|
|
|
383
397
|
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
384
398
|
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
@@ -402,6 +416,14 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
402
416
|
) {
|
|
403
417
|
// Users can see their group's data.
|
|
404
418
|
this.$privateData = [];
|
|
419
|
+
} else if (
|
|
420
|
+
user != null &&
|
|
421
|
+
tilmeld.config.domainSupport &&
|
|
422
|
+
domain &&
|
|
423
|
+
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)
|
|
424
|
+
) {
|
|
425
|
+
// Domain admins can see their groups' data.
|
|
426
|
+
this.$privateData = [];
|
|
405
427
|
}
|
|
406
428
|
}
|
|
407
429
|
|
|
@@ -567,10 +589,25 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
567
589
|
}> {
|
|
568
590
|
const tilmeld = enforceTilmeld(this);
|
|
569
591
|
if (!tilmeld.config.emailUsernames) {
|
|
570
|
-
|
|
592
|
+
let [groupname, domain] = tilmeld.config.domainSupport
|
|
593
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
594
|
+
: [this.$data.groupname, null];
|
|
595
|
+
|
|
596
|
+
// Lowercase the domain part.
|
|
597
|
+
if (domain) {
|
|
598
|
+
domain = domain.toLowerCase();
|
|
599
|
+
this.$data.groupname = `${groupname}@${domain}`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (
|
|
603
|
+
this.$data.groupname == null ||
|
|
604
|
+
!this.$data.groupname.length ||
|
|
605
|
+
groupname == null ||
|
|
606
|
+
!groupname.length
|
|
607
|
+
) {
|
|
571
608
|
return { result: false, message: 'Please specify a groupname.' };
|
|
572
609
|
}
|
|
573
|
-
if (
|
|
610
|
+
if (groupname.length < tilmeld.config.minUsernameLength) {
|
|
574
611
|
return {
|
|
575
612
|
result: false,
|
|
576
613
|
message:
|
|
@@ -579,7 +616,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
579
616
|
' characters.',
|
|
580
617
|
};
|
|
581
618
|
}
|
|
582
|
-
if (
|
|
619
|
+
if (groupname.length > tilmeld.config.maxUsernameLength) {
|
|
583
620
|
return {
|
|
584
621
|
result: false,
|
|
585
622
|
message:
|
|
@@ -589,22 +626,38 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
589
626
|
};
|
|
590
627
|
}
|
|
591
628
|
if (
|
|
592
|
-
difference(
|
|
593
|
-
|
|
594
|
-
tilmeld.config.validChars.split(''),
|
|
595
|
-
).length
|
|
629
|
+
difference(groupname.split(''), tilmeld.config.validChars.split(''))
|
|
630
|
+
.length
|
|
596
631
|
) {
|
|
597
632
|
return {
|
|
598
633
|
result: false,
|
|
599
634
|
message: tilmeld.config.validCharsNotice,
|
|
600
635
|
};
|
|
601
636
|
}
|
|
602
|
-
if (!tilmeld.config.validRegex.test(
|
|
637
|
+
if (!tilmeld.config.validRegex.test(groupname)) {
|
|
603
638
|
return {
|
|
604
639
|
result: false,
|
|
605
640
|
message: tilmeld.config.validRegexNotice,
|
|
606
641
|
};
|
|
607
642
|
}
|
|
643
|
+
if (domain != null) {
|
|
644
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
645
|
+
return {
|
|
646
|
+
result: false,
|
|
647
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (
|
|
651
|
+
!this.$is(tilmeld.currentUser?.group) &&
|
|
652
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
653
|
+
) {
|
|
654
|
+
return {
|
|
655
|
+
result: false,
|
|
656
|
+
message:
|
|
657
|
+
"You don't have the authority to manage groups on this domain.",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
608
661
|
|
|
609
662
|
const selector: Selector = {
|
|
610
663
|
type: '&',
|
|
@@ -707,6 +760,59 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
707
760
|
return false;
|
|
708
761
|
}
|
|
709
762
|
|
|
763
|
+
// Lowercase the domain part.
|
|
764
|
+
if (tilmeld.config.domainSupport) {
|
|
765
|
+
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
766
|
+
if (domain) {
|
|
767
|
+
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (tilmeld.config.domainSupport) {
|
|
772
|
+
const [_groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
773
|
+
const [_originalGroupname, originalDomain] = splitn(
|
|
774
|
+
this.$originalGroupname ?? '',
|
|
775
|
+
'@',
|
|
776
|
+
2,
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
if (
|
|
780
|
+
this.guid != null &&
|
|
781
|
+
((originalDomain == null && domain != null) ||
|
|
782
|
+
(domain == null && originalDomain != null))
|
|
783
|
+
) {
|
|
784
|
+
throw new AccessControlError(
|
|
785
|
+
"Groups can't switch to or from having a domain once created.",
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (
|
|
790
|
+
!this.$skipAcWhenSaving &&
|
|
791
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
792
|
+
!tilmeld.gatekeeper('system/admin')
|
|
793
|
+
) {
|
|
794
|
+
if (
|
|
795
|
+
domain != null &&
|
|
796
|
+
!this.$is(tilmeld.currentUser) &&
|
|
797
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
798
|
+
) {
|
|
799
|
+
throw new AccessControlError(
|
|
800
|
+
"You don't have the authority to modify groups on this domain.",
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (
|
|
804
|
+
originalDomain != null &&
|
|
805
|
+
originalDomain !== domain &&
|
|
806
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
807
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))
|
|
808
|
+
) {
|
|
809
|
+
throw new AccessControlError(
|
|
810
|
+
"You don't have the authority to modify groups on this domain.",
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
710
816
|
// Formatting.
|
|
711
817
|
this.$data.groupname = this.$data.groupname.trim();
|
|
712
818
|
if (tilmeld.config.emailUsernames) {
|
|
@@ -752,15 +858,23 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
752
858
|
delete this.$data.unverifiedSecondary;
|
|
753
859
|
}
|
|
754
860
|
|
|
755
|
-
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
756
|
-
// 'tilmeld/switch' abilities.
|
|
757
|
-
this.$data.abilities =
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
861
|
+
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
862
|
+
// 'tilmeld/switch', or domain admin abilities.
|
|
863
|
+
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
864
|
+
if (
|
|
865
|
+
['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
866
|
+
-1
|
|
867
|
+
) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
if (ability == null) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
if (ability.startsWith('tilmeld/domain/') && ability.endsWith('/admin')) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
return true;
|
|
877
|
+
}) as string[];
|
|
764
878
|
|
|
765
879
|
// Verification.
|
|
766
880
|
const unCheck = await this.$checkGroupname();
|
|
@@ -818,7 +932,11 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
818
932
|
}
|
|
819
933
|
}
|
|
820
934
|
|
|
821
|
-
|
|
935
|
+
const ret = await super.$save();
|
|
936
|
+
if (ret) {
|
|
937
|
+
this.$originalGroupname = this.$data.groupname;
|
|
938
|
+
}
|
|
939
|
+
return ret;
|
|
822
940
|
}
|
|
823
941
|
|
|
824
942
|
/*
|
|
@@ -840,7 +958,14 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
840
958
|
public async $delete() {
|
|
841
959
|
let tilmeld = enforceTilmeld(this);
|
|
842
960
|
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
843
|
-
throw new
|
|
961
|
+
throw new AccessControlError(
|
|
962
|
+
"You don't have the authority to delete groups.",
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
if (this.$data.groupname !== this.$originalGroupname) {
|
|
966
|
+
throw new AccessControlError(
|
|
967
|
+
"You can't delete a group while switching its groupname.",
|
|
968
|
+
);
|
|
844
969
|
}
|
|
845
970
|
|
|
846
971
|
const transaction = 'tilmeld-delete-' + this.guid;
|
|
@@ -913,9 +1038,9 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
913
1038
|
}
|
|
914
1039
|
|
|
915
1040
|
// Delete the group.
|
|
916
|
-
|
|
1041
|
+
let success = await super.$delete();
|
|
917
1042
|
if (success) {
|
|
918
|
-
await tnymph.commit(transaction);
|
|
1043
|
+
success = await tnymph.commit(transaction);
|
|
919
1044
|
} else {
|
|
920
1045
|
await tnymph.rollback(transaction);
|
|
921
1046
|
}
|
package/src/Tilmeld.ts
CHANGED
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
} from '@nymphjs/nymph';
|
|
11
11
|
import { Request, Response } from 'express';
|
|
12
12
|
import { xor } from 'lodash-es';
|
|
13
|
+
import { splitn } from '@sciactive/splitn';
|
|
13
14
|
|
|
14
15
|
import { Config, ConfigDefaults as defaults } from './conf/index.js';
|
|
15
16
|
import { AccessControlError } from './errors/index.js';
|
|
16
|
-
import Group from './Group.js';
|
|
17
|
+
import Group, { GroupData } from './Group.js';
|
|
17
18
|
import User, { UserData } from './User.js';
|
|
18
19
|
import { AccessControlData } from './Tilmeld.types.js';
|
|
19
20
|
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
@@ -108,6 +109,12 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
108
109
|
);
|
|
109
110
|
}
|
|
110
111
|
}
|
|
112
|
+
|
|
113
|
+
if (this.config.emailUsernames && this.config.domainSupport) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
'Email usernames and domain support cannot both be enabled.',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
/**
|
|
@@ -165,6 +172,8 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
165
172
|
user.mdate = currentUserMDate;
|
|
166
173
|
user.tags = currentUserTags;
|
|
167
174
|
user.$putData(currentUserData, currentUserSData);
|
|
175
|
+
user.$originalEmail = this.currentUser.$originalEmail;
|
|
176
|
+
user.$originalUsername = this.currentUser.$originalUsername;
|
|
168
177
|
this.currentUser = user;
|
|
169
178
|
this.currentUser.$updateDataProtection();
|
|
170
179
|
this.currentUser.$updateGroupDataProtection();
|
|
@@ -738,6 +747,27 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
738
747
|
return true;
|
|
739
748
|
}
|
|
740
749
|
|
|
750
|
+
// Users and groups that belong to a domain are readable and editable by
|
|
751
|
+
// that domain's admins.
|
|
752
|
+
if (
|
|
753
|
+
this.config.domainSupport &&
|
|
754
|
+
(entity instanceof User || entity instanceof Group)
|
|
755
|
+
) {
|
|
756
|
+
const [_local, domain] = splitn(
|
|
757
|
+
(entity instanceof User
|
|
758
|
+
? (entity as User & UserData).username
|
|
759
|
+
: (entity as Group & GroupData).groupname) ?? '',
|
|
760
|
+
'@',
|
|
761
|
+
2,
|
|
762
|
+
);
|
|
763
|
+
if (
|
|
764
|
+
domain &&
|
|
765
|
+
userOrEmpty.abilities?.includes(`tilmeld/domain/${domain}/admin`)
|
|
766
|
+
) {
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
741
771
|
// Load access control, since we need it now...
|
|
742
772
|
const acUser = acProperties.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
|
|
743
773
|
const acGroup = acProperties.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
|
package/src/User.test.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Nymph, EntityUniqueConstraintError } from '@nymphjs/nymph';
|
|
|
3
3
|
|
|
4
4
|
import Tilmeld from './Tilmeld.js';
|
|
5
5
|
import defaults from './conf/defaults.js';
|
|
6
|
+
import { AccessControlError } from './errors/index.js';
|
|
6
7
|
|
|
7
8
|
const tilmeld = new Tilmeld({
|
|
8
9
|
appName: 'My App',
|
|
@@ -11,6 +12,7 @@ const tilmeld = new Tilmeld({
|
|
|
11
12
|
cookiePath: '/',
|
|
12
13
|
setupPath: '/user',
|
|
13
14
|
emailUsernames: false,
|
|
15
|
+
domainSupport: true,
|
|
14
16
|
verifyRedirect: 'http://localhost:8080',
|
|
15
17
|
verifyChangeRedirect: 'http://localhost:8080',
|
|
16
18
|
cancelChangeRedirect: 'http://localhost:8080',
|
|
@@ -134,4 +136,83 @@ describe('User', () => {
|
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
});
|
|
139
|
+
|
|
140
|
+
it('allows a domain user', async () => {
|
|
141
|
+
const domainAdmin = await User.factory();
|
|
142
|
+
|
|
143
|
+
domainAdmin.username = 'admin';
|
|
144
|
+
domainAdmin.email = 'admin@localhost';
|
|
145
|
+
domainAdmin.nameFirst = 'Admin';
|
|
146
|
+
domainAdmin.nameLast = 'User';
|
|
147
|
+
domainAdmin.name = 'Admin User';
|
|
148
|
+
domainAdmin.$password('password');
|
|
149
|
+
domainAdmin.$grant('tilmeld/domain/example.com/admin');
|
|
150
|
+
|
|
151
|
+
expect(await domainAdmin.$saveSkipAC()).toEqual(true);
|
|
152
|
+
|
|
153
|
+
// Log in the domain admin.
|
|
154
|
+
await tilmeld.fillSession(domainAdmin);
|
|
155
|
+
|
|
156
|
+
const domainUser = await User.factory();
|
|
157
|
+
|
|
158
|
+
domainUser.username = 'new-user@example.com';
|
|
159
|
+
domainUser.email = 'newuser@localhost';
|
|
160
|
+
domainUser.nameFirst = 'New';
|
|
161
|
+
domainUser.nameLast = 'User';
|
|
162
|
+
domainUser.name = 'New User';
|
|
163
|
+
domainUser.$password('password');
|
|
164
|
+
|
|
165
|
+
expect(await domainUser.$save()).toEqual(true);
|
|
166
|
+
|
|
167
|
+
tilmeld.clearSession();
|
|
168
|
+
|
|
169
|
+
for (const user of [domainAdmin, domainUser]) {
|
|
170
|
+
if (user.guid != null) {
|
|
171
|
+
expect(await user.$deleteSkipAC()).toEqual(true);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('disallows a domain user with no admin permission', async () => {
|
|
177
|
+
const domainAdmin = await User.factory();
|
|
178
|
+
|
|
179
|
+
domainAdmin.username = 'admin';
|
|
180
|
+
domainAdmin.email = 'admin@localhost';
|
|
181
|
+
domainAdmin.nameFirst = 'Admin';
|
|
182
|
+
domainAdmin.nameLast = 'User';
|
|
183
|
+
domainAdmin.name = 'Admin User';
|
|
184
|
+
domainAdmin.$password('password');
|
|
185
|
+
domainAdmin.$grant('tilmeld/domain/example.com/admin');
|
|
186
|
+
|
|
187
|
+
expect(await domainAdmin.$saveSkipAC()).toEqual(true);
|
|
188
|
+
|
|
189
|
+
// Log in the domain admin.
|
|
190
|
+
await tilmeld.fillSession(domainAdmin);
|
|
191
|
+
|
|
192
|
+
const domainUser = await User.factory();
|
|
193
|
+
|
|
194
|
+
// The wrong domain name.
|
|
195
|
+
domainUser.username = 'new-user@example.net';
|
|
196
|
+
domainUser.email = 'newuser@localhost';
|
|
197
|
+
domainUser.nameFirst = 'New';
|
|
198
|
+
domainUser.nameLast = 'User';
|
|
199
|
+
domainUser.name = 'New User';
|
|
200
|
+
domainUser.$password('password');
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await domainUser.$save();
|
|
204
|
+
|
|
205
|
+
throw new Error("Shouldn't get here because of access control.");
|
|
206
|
+
} catch (e: any) {
|
|
207
|
+
expect(e).toBeInstanceOf(AccessControlError);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
tilmeld.clearSession();
|
|
211
|
+
|
|
212
|
+
for (const user of [domainAdmin, domainUser]) {
|
|
213
|
+
if (user.guid != null) {
|
|
214
|
+
expect(await user.$deleteSkipAC()).toEqual(true);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
137
218
|
});
|