@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/dist/User.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { humanSecret, nanoid } from '@nymphjs/guid';
|
|
3
3
|
import strtotime from 'locutus/php/datetime/strtotime.js';
|
|
4
|
-
import { difference } from 'lodash-es';
|
|
4
|
+
import { difference, xor } from 'lodash-es';
|
|
5
5
|
import { TOTP, Secret } from 'otpauth';
|
|
6
6
|
import { toDataURL } from 'qrcode';
|
|
7
|
+
import { splitn } from '@sciactive/splitn';
|
|
7
8
|
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
8
9
|
import AbleObject from './AbleObject.js';
|
|
9
|
-
import { BadDataError, BadEmailError, BadUsernameError, EmailChangeRateLimitExceededError, } from './errors/index.js';
|
|
10
|
+
import { AccessControlError, BadDataError, BadEmailError, BadUsernameError, EmailChangeRateLimitExceededError, } from './errors/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* A user data model.
|
|
12
13
|
*
|
|
@@ -73,6 +74,7 @@ export default class User extends AbleObject {
|
|
|
73
74
|
'sendRecovery',
|
|
74
75
|
'recover',
|
|
75
76
|
'getClientConfig',
|
|
77
|
+
'getDomainUsers',
|
|
76
78
|
];
|
|
77
79
|
$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
78
80
|
static searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -105,6 +107,10 @@ export default class User extends AbleObject {
|
|
|
105
107
|
* verification, don't set this.
|
|
106
108
|
*/
|
|
107
109
|
$originalEmail;
|
|
110
|
+
/**
|
|
111
|
+
* Used to save the current username for domain admin permissions.
|
|
112
|
+
*/
|
|
113
|
+
$originalUsername;
|
|
108
114
|
static async factoryUsername(username) {
|
|
109
115
|
const entity = new this();
|
|
110
116
|
if (username != null) {
|
|
@@ -260,6 +266,7 @@ export default class User extends AbleObject {
|
|
|
260
266
|
regFields: tilmeld.config.regFields,
|
|
261
267
|
userFields: tilmeld.config.userFields,
|
|
262
268
|
emailUsernames: tilmeld.config.emailUsernames,
|
|
269
|
+
domainSupport: tilmeld.config.domainSupport,
|
|
263
270
|
allowRegistration: tilmeld.config.allowRegistration,
|
|
264
271
|
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
265
272
|
pwRecovery: tilmeld.config.pwRecovery,
|
|
@@ -280,6 +287,51 @@ export default class User extends AbleObject {
|
|
|
280
287
|
}
|
|
281
288
|
return result;
|
|
282
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Get the users in a domain. This is only accessible to the domain's admins.
|
|
292
|
+
*/
|
|
293
|
+
static async getDomainUsers(domain, options) {
|
|
294
|
+
const tilmeld = enforceTilmeld(this);
|
|
295
|
+
if (!tilmeld.config.domainSupport) {
|
|
296
|
+
throw new AccessControlError('Domain support is not enabled.');
|
|
297
|
+
}
|
|
298
|
+
if (!domain) {
|
|
299
|
+
throw new BadDataError('Domain is required.');
|
|
300
|
+
}
|
|
301
|
+
domain = domain.toLowerCase();
|
|
302
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
303
|
+
throw new BadDataError(tilmeld.config.validDomainRegexNotice);
|
|
304
|
+
}
|
|
305
|
+
if (!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
306
|
+
throw new AccessControlError('You do not have the authority to manage this domain.');
|
|
307
|
+
}
|
|
308
|
+
return await this.nymph.getEntities({
|
|
309
|
+
class: this.nymph.getEntityClass(User),
|
|
310
|
+
...(options?.limit != null
|
|
311
|
+
? {
|
|
312
|
+
limit: options.limit,
|
|
313
|
+
}
|
|
314
|
+
: {}),
|
|
315
|
+
...(options?.offset != null
|
|
316
|
+
? {
|
|
317
|
+
offset: options.offset,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
...(options?.sort != null
|
|
321
|
+
? {
|
|
322
|
+
sort: options.sort,
|
|
323
|
+
}
|
|
324
|
+
: {}),
|
|
325
|
+
...(options?.reverse != null
|
|
326
|
+
? {
|
|
327
|
+
reverse: options.reverse,
|
|
328
|
+
}
|
|
329
|
+
: {}),
|
|
330
|
+
}, {
|
|
331
|
+
type: '&',
|
|
332
|
+
like: ['username', `%@${domain.replace(/([\\%_])/g, (s) => `\\${s}`)}`],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
283
335
|
constructor() {
|
|
284
336
|
super();
|
|
285
337
|
// Defaults.
|
|
@@ -507,59 +559,84 @@ export default class User extends AbleObject {
|
|
|
507
559
|
$jsonAcceptData(input, allowConflict = false) {
|
|
508
560
|
const tilmeld = enforceTilmeld(this);
|
|
509
561
|
this.$check();
|
|
510
|
-
if ('abilities' in input.data
|
|
511
|
-
input.data.abilities
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (
|
|
518
|
-
input.data.
|
|
519
|
-
|
|
520
|
-
(
|
|
521
|
-
|
|
522
|
-
|
|
562
|
+
if ('abilities' in input.data) {
|
|
563
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
564
|
+
}
|
|
565
|
+
// If the user is a new domain user, the data protection needs to be updated
|
|
566
|
+
// before the data is accepted, or else some things won't be accepted
|
|
567
|
+
// correctly (enabled, passwordTemp).
|
|
568
|
+
let reloadDataProtection = false;
|
|
569
|
+
if (tilmeld.config.domainSupport) {
|
|
570
|
+
let [_username, domain] = splitn(input.data.username ?? '', '@', 2);
|
|
571
|
+
// Lowercase the domain part.
|
|
572
|
+
if (domain) {
|
|
573
|
+
domain = domain.toLowerCase();
|
|
574
|
+
}
|
|
575
|
+
const user = tilmeld.User.current();
|
|
576
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
577
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
578
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
579
|
+
const abilities = (isCurrentUser || user == null
|
|
580
|
+
? this.$data.abilities
|
|
581
|
+
: user?.abilities) ?? [];
|
|
582
|
+
const isNewUser = input.guid == null;
|
|
583
|
+
const isDomainUser = !isCurrentUser &&
|
|
584
|
+
user != null &&
|
|
585
|
+
domain &&
|
|
586
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
587
|
+
reloadDataProtection = !!(isNewUser && isDomainUser);
|
|
523
588
|
}
|
|
524
|
-
if ('abilities' in input.data &&
|
|
525
|
-
input.data.abilities?.includes('tilmeld/switch') !==
|
|
526
|
-
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
527
|
-
(!tilmeld.currentUser ||
|
|
528
|
-
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
529
|
-
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
530
|
-
}
|
|
531
|
-
this.$originalEmail = this.$data.email;
|
|
532
589
|
super.$jsonAcceptData(input, allowConflict);
|
|
590
|
+
if (reloadDataProtection) {
|
|
591
|
+
// Load it again after data protection is run.
|
|
592
|
+
this.$updateDataProtection();
|
|
593
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
594
|
+
}
|
|
533
595
|
}
|
|
534
596
|
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
535
|
-
const tilmeld = enforceTilmeld(this);
|
|
536
597
|
this.$check();
|
|
537
|
-
if ('abilities' in patch.set
|
|
538
|
-
patch.set.abilities
|
|
539
|
-
|
|
598
|
+
if ('abilities' in patch.set) {
|
|
599
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
600
|
+
}
|
|
601
|
+
super.$jsonAcceptPatch(patch, allowConflict);
|
|
602
|
+
}
|
|
603
|
+
$checkIncomingAbilities(incoming) {
|
|
604
|
+
const tilmeld = enforceTilmeld(this);
|
|
605
|
+
if (!Array.isArray(incoming) ||
|
|
606
|
+
incoming.find((ability) => typeof ability !== 'string')) {
|
|
607
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
608
|
+
}
|
|
609
|
+
if (incoming.includes('system/admin') !==
|
|
610
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
540
611
|
(!tilmeld.currentUser ||
|
|
541
612
|
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
542
613
|
throw new BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
543
614
|
}
|
|
544
|
-
if ('
|
|
545
|
-
|
|
546
|
-
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
615
|
+
if (incoming.includes('tilmeld/admin') !==
|
|
616
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
547
617
|
(!tilmeld.currentUser ||
|
|
548
618
|
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
549
619
|
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
550
620
|
}
|
|
551
|
-
if ('
|
|
552
|
-
|
|
553
|
-
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
621
|
+
if (incoming.includes('tilmeld/switch') !==
|
|
622
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
554
623
|
(!tilmeld.currentUser ||
|
|
555
624
|
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
556
625
|
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
557
626
|
}
|
|
558
|
-
|
|
559
|
-
|
|
627
|
+
if (xor(incoming.filter((ability) => ability.startsWith('tilmeld/')), this.$data.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ?? []).length &&
|
|
628
|
+
(!tilmeld.currentUser ||
|
|
629
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
630
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))) {
|
|
631
|
+
throw new BadDataError("You don't have the authority to grant or revoke Tilmeld abilities.");
|
|
632
|
+
}
|
|
560
633
|
}
|
|
561
|
-
$putData(data, sdata) {
|
|
562
|
-
super.$putData(data, sdata);
|
|
634
|
+
$putData(data, sdata, source) {
|
|
635
|
+
super.$putData(data, sdata, source);
|
|
636
|
+
if (source === 'server') {
|
|
637
|
+
this.$originalEmail = this.$data.email;
|
|
638
|
+
this.$originalUsername = this.$data.username;
|
|
639
|
+
}
|
|
563
640
|
this.$updateDataProtection();
|
|
564
641
|
}
|
|
565
642
|
/**
|
|
@@ -570,6 +647,13 @@ export default class User extends AbleObject {
|
|
|
570
647
|
$updateDataProtection(givenUser) {
|
|
571
648
|
const tilmeld = enforceTilmeld(this);
|
|
572
649
|
let user = givenUser ?? tilmeld.User.current();
|
|
650
|
+
let [_username, domain] = tilmeld.config.domainSupport
|
|
651
|
+
? splitn(this.$data.username ?? '', '@', 2)
|
|
652
|
+
: [this.$data.username, null];
|
|
653
|
+
// Lowercase the domain part.
|
|
654
|
+
if (domain) {
|
|
655
|
+
domain = domain.toLowerCase();
|
|
656
|
+
}
|
|
573
657
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
574
658
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
575
659
|
this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
@@ -583,6 +667,11 @@ export default class User extends AbleObject {
|
|
|
583
667
|
? this.$data.abilities
|
|
584
668
|
: user?.abilities) ?? [];
|
|
585
669
|
const isNewUser = this.guid == null;
|
|
670
|
+
const isDomainUser = tilmeld.config.domainSupport &&
|
|
671
|
+
!isCurrentUser &&
|
|
672
|
+
user != null &&
|
|
673
|
+
domain &&
|
|
674
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
586
675
|
if (isCurrentUser) {
|
|
587
676
|
// Users can check to see what abilities they have.
|
|
588
677
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
@@ -595,6 +684,16 @@ export default class User extends AbleObject {
|
|
|
595
684
|
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
596
685
|
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
597
686
|
}
|
|
687
|
+
if (isDomainUser) {
|
|
688
|
+
// Domain admins can check to see what abilities their users have.
|
|
689
|
+
this.$clientEnabledMethods.push('$gatekeeper');
|
|
690
|
+
this.$clientEnabledMethods.push('$changePassword');
|
|
691
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
692
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
693
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
694
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
695
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
696
|
+
}
|
|
598
697
|
if (user != null &&
|
|
599
698
|
(abilities.includes('tilmeld/admin') ||
|
|
600
699
|
abilities.includes('system/admin'))) {
|
|
@@ -604,6 +703,56 @@ export default class User extends AbleObject {
|
|
|
604
703
|
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
605
704
|
this.$allowlistData = undefined;
|
|
606
705
|
}
|
|
706
|
+
else if (isDomainUser) {
|
|
707
|
+
// Domain admins can see their users' data, and edit some of it.
|
|
708
|
+
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
709
|
+
this.$allowlistData.push('username');
|
|
710
|
+
}
|
|
711
|
+
this.$allowlistData.push('avatar');
|
|
712
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
713
|
+
this.$allowlistData.push('nameFirst');
|
|
714
|
+
this.$allowlistData.push('nameMiddle');
|
|
715
|
+
this.$allowlistData.push('nameLast');
|
|
716
|
+
this.$allowlistData.push('name');
|
|
717
|
+
}
|
|
718
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
719
|
+
this.$allowlistData.push('email');
|
|
720
|
+
}
|
|
721
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
722
|
+
this.$allowlistData.push('phone');
|
|
723
|
+
}
|
|
724
|
+
this.$allowlistData.push('enabled');
|
|
725
|
+
this.$allowlistData.push('passwordTemp');
|
|
726
|
+
this.$privateData = [
|
|
727
|
+
// The things commented are there to show what is _not_ private.
|
|
728
|
+
// 'abilities',
|
|
729
|
+
// 'username',
|
|
730
|
+
// 'nameFirst',
|
|
731
|
+
// 'nameMiddle',
|
|
732
|
+
// 'nameLast',
|
|
733
|
+
// 'name',
|
|
734
|
+
// 'email',
|
|
735
|
+
// 'avatar',
|
|
736
|
+
// 'phone',
|
|
737
|
+
// 'group',
|
|
738
|
+
// 'groups',
|
|
739
|
+
// 'inheritAbilities',
|
|
740
|
+
// 'enabled',
|
|
741
|
+
'secret',
|
|
742
|
+
'emailChangeDate',
|
|
743
|
+
'newEmailSecret',
|
|
744
|
+
// 'newEmailAddress',
|
|
745
|
+
'cancelEmailSecret',
|
|
746
|
+
'cancelEmailAddress',
|
|
747
|
+
'recoverSecret',
|
|
748
|
+
'recoverSecretDate',
|
|
749
|
+
'salt',
|
|
750
|
+
'password',
|
|
751
|
+
// 'passwordTemp',
|
|
752
|
+
// 'revokeTokenDate',
|
|
753
|
+
'totpSecret',
|
|
754
|
+
];
|
|
755
|
+
}
|
|
607
756
|
else if (isCurrentUser || isNewUser) {
|
|
608
757
|
// Users can see their own data, and edit some of it.
|
|
609
758
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
@@ -623,7 +772,7 @@ export default class User extends AbleObject {
|
|
|
623
772
|
this.$allowlistData.push('phone');
|
|
624
773
|
}
|
|
625
774
|
this.$privateData = [
|
|
626
|
-
// The things
|
|
775
|
+
// The things commented are there to show what is _not_ private.
|
|
627
776
|
// 'abilities',
|
|
628
777
|
// 'username',
|
|
629
778
|
// 'nameFirst',
|
|
@@ -1135,10 +1284,21 @@ export default class User extends AbleObject {
|
|
|
1135
1284
|
const tilmeld = enforceTilmeld(this);
|
|
1136
1285
|
this.$nymph.config.debugInfo('tilmeld', `Username check "${this.$data.username}".`);
|
|
1137
1286
|
if (!tilmeld.config.emailUsernames) {
|
|
1138
|
-
|
|
1287
|
+
let [username, domain] = tilmeld.config.domainSupport
|
|
1288
|
+
? splitn(this.$data.username ?? '', '@', 2)
|
|
1289
|
+
: [this.$data.username, null];
|
|
1290
|
+
// Lowercase the domain part.
|
|
1291
|
+
if (domain) {
|
|
1292
|
+
domain = domain.toLowerCase();
|
|
1293
|
+
this.$data.username = `${username}@${domain}`;
|
|
1294
|
+
}
|
|
1295
|
+
if (this.$data.username == null ||
|
|
1296
|
+
!this.$data.username.length ||
|
|
1297
|
+
username == null ||
|
|
1298
|
+
!username.length) {
|
|
1139
1299
|
return { result: false, message: 'Please specify a username.' };
|
|
1140
1300
|
}
|
|
1141
|
-
if (
|
|
1301
|
+
if (username.length < tilmeld.config.minUsernameLength) {
|
|
1142
1302
|
return {
|
|
1143
1303
|
result: false,
|
|
1144
1304
|
message: 'Usernames must be at least ' +
|
|
@@ -1146,7 +1306,7 @@ export default class User extends AbleObject {
|
|
|
1146
1306
|
' characters.',
|
|
1147
1307
|
};
|
|
1148
1308
|
}
|
|
1149
|
-
if (
|
|
1309
|
+
if (username.length > tilmeld.config.maxUsernameLength) {
|
|
1150
1310
|
return {
|
|
1151
1311
|
result: false,
|
|
1152
1312
|
message: 'Usernames must not exceed ' +
|
|
@@ -1154,18 +1314,34 @@ export default class User extends AbleObject {
|
|
|
1154
1314
|
' characters.',
|
|
1155
1315
|
};
|
|
1156
1316
|
}
|
|
1157
|
-
if (difference(
|
|
1317
|
+
if (difference(username.split(''), tilmeld.config.validChars.split(''))
|
|
1318
|
+
.length) {
|
|
1158
1319
|
return {
|
|
1159
1320
|
result: false,
|
|
1160
1321
|
message: tilmeld.config.validCharsNotice,
|
|
1161
1322
|
};
|
|
1162
1323
|
}
|
|
1163
|
-
if (!tilmeld.config.validRegex.test(
|
|
1324
|
+
if (!tilmeld.config.validRegex.test(username)) {
|
|
1164
1325
|
return {
|
|
1165
1326
|
result: false,
|
|
1166
1327
|
message: tilmeld.config.validRegexNotice,
|
|
1167
1328
|
};
|
|
1168
1329
|
}
|
|
1330
|
+
if (domain != null) {
|
|
1331
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
1332
|
+
return {
|
|
1333
|
+
result: false,
|
|
1334
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (!this.$is(tilmeld.currentUser) &&
|
|
1338
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
1339
|
+
return {
|
|
1340
|
+
result: false,
|
|
1341
|
+
message: "You don't have the authority to manage users on this domain.",
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1169
1345
|
const selector = {
|
|
1170
1346
|
type: '&',
|
|
1171
1347
|
ilike: [
|
|
@@ -1487,7 +1663,38 @@ export default class User extends AbleObject {
|
|
|
1487
1663
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1488
1664
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1489
1665
|
this.$data.abilities?.includes('system/admin')) {
|
|
1490
|
-
throw new
|
|
1666
|
+
throw new AccessControlError("You don't have the authority to modify system admins.");
|
|
1667
|
+
}
|
|
1668
|
+
// Lowercase the domain part.
|
|
1669
|
+
if (tilmeld.config.domainSupport) {
|
|
1670
|
+
const [username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
1671
|
+
if (domain) {
|
|
1672
|
+
this.$data.username = `${username}@${domain.toLowerCase()}`;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
if (tilmeld.config.domainSupport) {
|
|
1676
|
+
const [_username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
1677
|
+
const [_originalUsername, originalDomain] = splitn(this.$originalUsername ?? '', '@', 2);
|
|
1678
|
+
if (this.guid != null &&
|
|
1679
|
+
((originalDomain == null && domain != null) ||
|
|
1680
|
+
(domain == null && originalDomain != null))) {
|
|
1681
|
+
throw new AccessControlError("Users can't switch to or from having a domain once created.");
|
|
1682
|
+
}
|
|
1683
|
+
if (!this.$skipAcWhenSaving &&
|
|
1684
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1685
|
+
!tilmeld.gatekeeper('system/admin')) {
|
|
1686
|
+
if (domain != null &&
|
|
1687
|
+
!this.$is(tilmeld.currentUser) &&
|
|
1688
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
1689
|
+
throw new AccessControlError("You don't have the authority to modify users on this domain.");
|
|
1690
|
+
}
|
|
1691
|
+
if (originalDomain != null &&
|
|
1692
|
+
originalDomain !== domain &&
|
|
1693
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
1694
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
1695
|
+
throw new AccessControlError("You don't have the authority to modify users on this domain.");
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1491
1698
|
}
|
|
1492
1699
|
let sendVerification = false;
|
|
1493
1700
|
// Formatting.
|
|
@@ -1761,7 +1968,7 @@ export default class User extends AbleObject {
|
|
|
1761
1968
|
this.$setNymph(nymph);
|
|
1762
1969
|
throw e;
|
|
1763
1970
|
}
|
|
1764
|
-
let ret;
|
|
1971
|
+
let ret = false;
|
|
1765
1972
|
let preGuid = this.guid;
|
|
1766
1973
|
let preCdate = this.cdate;
|
|
1767
1974
|
let preMdate = this.mdate;
|
|
@@ -1783,6 +1990,9 @@ export default class User extends AbleObject {
|
|
|
1783
1990
|
}
|
|
1784
1991
|
catch (e) {
|
|
1785
1992
|
await tnymph.rollback(transaction);
|
|
1993
|
+
this.guid = preGuid;
|
|
1994
|
+
this.cdate = preCdate;
|
|
1995
|
+
this.mdate = preMdate;
|
|
1786
1996
|
this.$setNymph(nymph);
|
|
1787
1997
|
throw e;
|
|
1788
1998
|
}
|
|
@@ -1800,17 +2010,21 @@ export default class User extends AbleObject {
|
|
|
1800
2010
|
}
|
|
1801
2011
|
this.$descendantGroups = undefined;
|
|
1802
2012
|
this.$gatekeeperCache = undefined;
|
|
1803
|
-
await tnymph.commit(transaction);
|
|
2013
|
+
ret = await tnymph.commit(transaction);
|
|
2014
|
+
}
|
|
2015
|
+
else {
|
|
2016
|
+
await tnymph.rollback(transaction);
|
|
2017
|
+
}
|
|
2018
|
+
this.$setNymph(nymph);
|
|
2019
|
+
if (ret) {
|
|
2020
|
+
this.$originalEmail = this.$data.email;
|
|
2021
|
+
this.$originalUsername = this.$data.username;
|
|
1804
2022
|
const tilmeld = enforceTilmeld(nymph);
|
|
1805
2023
|
if (tilmeld.User.current(true).$is(this)) {
|
|
1806
2024
|
// Update the user in the session cache.
|
|
1807
2025
|
await tilmeld.fillSession(this);
|
|
1808
2026
|
}
|
|
1809
2027
|
}
|
|
1810
|
-
else {
|
|
1811
|
-
await tnymph.rollback(transaction);
|
|
1812
|
-
}
|
|
1813
|
-
this.$setNymph(nymph);
|
|
1814
2028
|
return ret;
|
|
1815
2029
|
}
|
|
1816
2030
|
/**
|
|
@@ -1830,17 +2044,49 @@ export default class User extends AbleObject {
|
|
|
1830
2044
|
async $delete() {
|
|
1831
2045
|
const tilmeld = enforceTilmeld(this);
|
|
1832
2046
|
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1833
|
-
throw new
|
|
2047
|
+
throw new AccessControlError("You don't have the authority to delete users.");
|
|
1834
2048
|
}
|
|
1835
2049
|
if (!this.$skipAcWhenDeleting &&
|
|
1836
2050
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1837
2051
|
this.$data.abilities?.includes('system/admin')) {
|
|
1838
|
-
throw new
|
|
2052
|
+
throw new AccessControlError("You don't have the authority to delete system admins.");
|
|
2053
|
+
}
|
|
2054
|
+
if (this.$data.username !== this.$originalUsername) {
|
|
2055
|
+
throw new AccessControlError("You can't delete a user while switching their username.");
|
|
1839
2056
|
}
|
|
1840
2057
|
if (tilmeld.User.current(true).$is(this)) {
|
|
1841
2058
|
await this.$logout();
|
|
1842
2059
|
}
|
|
1843
|
-
|
|
2060
|
+
if (this.$data.group != null && this.$is(this.$data.group.user)) {
|
|
2061
|
+
const transaction = 'tilmeld-delete-' + this.guid;
|
|
2062
|
+
const nymph = this.$nymph;
|
|
2063
|
+
const tnymph = await nymph.startTransaction(transaction);
|
|
2064
|
+
this.$setNymph(tnymph);
|
|
2065
|
+
// Read the skip ac value here, because super.$delete changes it.
|
|
2066
|
+
const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
|
|
2067
|
+
// Delete the user.
|
|
2068
|
+
let success = await super.$delete();
|
|
2069
|
+
if (!success) {
|
|
2070
|
+
await tnymph.rollback(transaction);
|
|
2071
|
+
this.$setNymph(nymph);
|
|
2072
|
+
return success;
|
|
2073
|
+
}
|
|
2074
|
+
// Delete the generated group.
|
|
2075
|
+
success = $skipAcWhenDeleting
|
|
2076
|
+
? await this.$data.group.$deleteSkipAC()
|
|
2077
|
+
: await this.$data.group.$delete();
|
|
2078
|
+
if (success) {
|
|
2079
|
+
success = await tnymph.commit(transaction);
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
await tnymph.rollback(transaction);
|
|
2083
|
+
}
|
|
2084
|
+
this.$setNymph(nymph);
|
|
2085
|
+
return success;
|
|
2086
|
+
}
|
|
2087
|
+
else {
|
|
2088
|
+
return await super.$delete();
|
|
2089
|
+
}
|
|
1844
2090
|
}
|
|
1845
2091
|
/*
|
|
1846
2092
|
* This should *never* be accessible on the client.
|