@nymphjs/tilmeld 1.0.0-beta.54 → 1.0.0-beta.56
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 +14 -0
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +15 -0
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +116 -0
- package/dist/Group.js +98 -0
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +1 -0
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +226 -1
- package/dist/Tilmeld.js +304 -13
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +265 -0
- package/dist/User.js +232 -0
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +1 -0
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +244 -0
- package/dist/conf/defaults.js +6 -3
- package/dist/conf/defaults.js.map +1 -1
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -2
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -2
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -2
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -2
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/package.json +8 -7
- package/src/Tilmeld.ts +7 -3
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/tsconfig.json +1 -1
package/dist/User.js
CHANGED
|
@@ -14,6 +14,15 @@ const qrcode_1 = require("qrcode");
|
|
|
14
14
|
const enforceTilmeld_1 = require("./enforceTilmeld");
|
|
15
15
|
const AbleObject_1 = __importDefault(require("./AbleObject"));
|
|
16
16
|
const errors_1 = require("./errors");
|
|
17
|
+
/**
|
|
18
|
+
* A user data model.
|
|
19
|
+
*
|
|
20
|
+
* Written by Hunter Perrin for SciActive.
|
|
21
|
+
*
|
|
22
|
+
* @author Hunter Perrin <hperrin@gmail.com>
|
|
23
|
+
* @copyright SciActive Inc
|
|
24
|
+
* @see http://nymph.io/
|
|
25
|
+
*/
|
|
17
26
|
class User extends AbleObject_1.default {
|
|
18
27
|
static async factory(guid) {
|
|
19
28
|
return (await super.factory(guid));
|
|
@@ -42,7 +51,11 @@ class User extends AbleObject_1.default {
|
|
|
42
51
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
43
52
|
this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
44
53
|
this.$allowlistTags = [];
|
|
54
|
+
/**
|
|
55
|
+
* This is explicitly used only during the registration proccess.
|
|
56
|
+
*/
|
|
45
57
|
this.$skipAcWhenSaving = false;
|
|
58
|
+
// Defaults.
|
|
46
59
|
this.$data.enabled = true;
|
|
47
60
|
this.$data.abilities = [];
|
|
48
61
|
this.$data.groups = [];
|
|
@@ -56,6 +69,12 @@ class User extends AbleObject_1.default {
|
|
|
56
69
|
}
|
|
57
70
|
return tilmeld.currentUser;
|
|
58
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Send an account recovery link.
|
|
74
|
+
*
|
|
75
|
+
* @param data The input data from the client.
|
|
76
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
77
|
+
*/
|
|
59
78
|
static async sendRecovery(data) {
|
|
60
79
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
61
80
|
if (!tilmeld.config.pwRecovery) {
|
|
@@ -67,6 +86,7 @@ class User extends AbleObject_1.default {
|
|
|
67
86
|
let user;
|
|
68
87
|
const options = {};
|
|
69
88
|
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
89
|
+
// Create a username recovery email.
|
|
70
90
|
const getUser = await this.nymph.getEntity({ class: tilmeld.User, skipAc: true }, {
|
|
71
91
|
type: '&',
|
|
72
92
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
@@ -77,6 +97,7 @@ class User extends AbleObject_1.default {
|
|
|
77
97
|
message: 'Requested account is not accessible.',
|
|
78
98
|
};
|
|
79
99
|
}
|
|
100
|
+
// Send the recovery email.
|
|
80
101
|
user = getUser;
|
|
81
102
|
options.template = 'RecoverUsername';
|
|
82
103
|
options.message = {
|
|
@@ -88,6 +109,7 @@ class User extends AbleObject_1.default {
|
|
|
88
109
|
options.locals = {};
|
|
89
110
|
}
|
|
90
111
|
else if (data.recoveryType === 'password') {
|
|
112
|
+
// Create a password recovery email.
|
|
91
113
|
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
92
114
|
if (getUser.guid == null) {
|
|
93
115
|
return {
|
|
@@ -95,11 +117,13 @@ class User extends AbleObject_1.default {
|
|
|
95
117
|
message: 'Requested account is not accessible.',
|
|
96
118
|
};
|
|
97
119
|
}
|
|
120
|
+
// Create a unique secret.
|
|
98
121
|
getUser.recoverSecret = (0, guid_1.humanSecret)();
|
|
99
122
|
getUser.recoverSecretDate = Date.now();
|
|
100
123
|
if (!(await getUser.$saveSkipAC())) {
|
|
101
124
|
return { result: false, message: "Couldn't save recovery secret." };
|
|
102
125
|
}
|
|
126
|
+
// Send the recovery email.
|
|
103
127
|
user = getUser;
|
|
104
128
|
options.template = 'RecoverPassword';
|
|
105
129
|
options.message = {
|
|
@@ -116,6 +140,7 @@ class User extends AbleObject_1.default {
|
|
|
116
140
|
else {
|
|
117
141
|
return { result: false, message: 'Invalid recovery type.' };
|
|
118
142
|
}
|
|
143
|
+
// Send the email.
|
|
119
144
|
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
120
145
|
return {
|
|
121
146
|
result: true,
|
|
@@ -127,6 +152,12 @@ class User extends AbleObject_1.default {
|
|
|
127
152
|
return { result: false, message: "Couldn't send recovery email." };
|
|
128
153
|
}
|
|
129
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Recover account details.
|
|
157
|
+
*
|
|
158
|
+
* @param data The input data from the client.
|
|
159
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
160
|
+
*/
|
|
130
161
|
static async recover(data) {
|
|
131
162
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
132
163
|
if (!tilmeld.config.pwRecovery) {
|
|
@@ -231,6 +262,7 @@ class User extends AbleObject_1.default {
|
|
|
231
262
|
message: e.message,
|
|
232
263
|
};
|
|
233
264
|
}
|
|
265
|
+
// Authentication was successful, attempt to login.
|
|
234
266
|
if (!(await tilmeld.login(this, true))) {
|
|
235
267
|
return { result: false, message: 'Incorrect login/password.' };
|
|
236
268
|
}
|
|
@@ -240,6 +272,7 @@ class User extends AbleObject_1.default {
|
|
|
240
272
|
await callback(this);
|
|
241
273
|
}
|
|
242
274
|
}
|
|
275
|
+
// Login was successful.
|
|
243
276
|
return { result: true, message: 'You are logged in.' };
|
|
244
277
|
}
|
|
245
278
|
async $switchUser(data) {
|
|
@@ -286,6 +319,7 @@ class User extends AbleObject_1.default {
|
|
|
286
319
|
message: e.message,
|
|
287
320
|
};
|
|
288
321
|
}
|
|
322
|
+
// Authentication was successful, attempt to login.
|
|
289
323
|
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
290
324
|
return { result: false, message: 'Incorrect login/password.' };
|
|
291
325
|
}
|
|
@@ -295,8 +329,13 @@ class User extends AbleObject_1.default {
|
|
|
295
329
|
await callback(this);
|
|
296
330
|
}
|
|
297
331
|
}
|
|
332
|
+
// Login was successful.
|
|
298
333
|
return { result: true, message: 'You are logged in.' };
|
|
299
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Log a user out of the system.
|
|
337
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
338
|
+
*/
|
|
300
339
|
async $logout() {
|
|
301
340
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
302
341
|
try {
|
|
@@ -347,6 +386,9 @@ class User extends AbleObject_1.default {
|
|
|
347
386
|
}
|
|
348
387
|
return null;
|
|
349
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Get the user's group descendants.
|
|
391
|
+
*/
|
|
350
392
|
async $getDescendantGroups() {
|
|
351
393
|
if (this.$descendantGroups == null) {
|
|
352
394
|
this.$descendantGroups = [];
|
|
@@ -419,6 +461,11 @@ class User extends AbleObject_1.default {
|
|
|
419
461
|
super.$putData(data, sdata);
|
|
420
462
|
this.$updateDataProtection();
|
|
421
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Update the data protection arrays for a user.
|
|
466
|
+
*
|
|
467
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
468
|
+
*/
|
|
422
469
|
$updateDataProtection(givenUser) {
|
|
423
470
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
424
471
|
let user = givenUser ?? tilmeld.User.current();
|
|
@@ -429,11 +476,14 @@ class User extends AbleObject_1.default {
|
|
|
429
476
|
this.$privateData.push('username');
|
|
430
477
|
}
|
|
431
478
|
const isCurrentUser = user != null && user.$is(this);
|
|
479
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
480
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
432
481
|
const abilities = (isCurrentUser || user == null
|
|
433
482
|
? this.$data.abilities
|
|
434
483
|
: user?.abilities) ?? [];
|
|
435
484
|
const isNewUser = this.guid == null;
|
|
436
485
|
if (isCurrentUser) {
|
|
486
|
+
// Users can check to see what abilities they have.
|
|
437
487
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
438
488
|
this.$clientEnabledMethods.push('$changePassword');
|
|
439
489
|
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
@@ -447,12 +497,14 @@ class User extends AbleObject_1.default {
|
|
|
447
497
|
if (user != null &&
|
|
448
498
|
(abilities.includes('tilmeld/admin') ||
|
|
449
499
|
abilities.includes('system/admin'))) {
|
|
500
|
+
// Users who can edit other users can see most of their data.
|
|
450
501
|
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
451
502
|
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
452
503
|
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
453
504
|
this.$allowlistData = undefined;
|
|
454
505
|
}
|
|
455
506
|
else if (isCurrentUser || isNewUser) {
|
|
507
|
+
// Users can see their own data, and edit some of it.
|
|
456
508
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
457
509
|
this.$allowlistData.push('username');
|
|
458
510
|
}
|
|
@@ -470,10 +522,24 @@ class User extends AbleObject_1.default {
|
|
|
470
522
|
this.$allowlistData.push('phone');
|
|
471
523
|
}
|
|
472
524
|
this.$privateData = [
|
|
525
|
+
// The things that are commented are there to show what is _not_ private.
|
|
526
|
+
// 'abilities',
|
|
527
|
+
// 'username',
|
|
528
|
+
// 'nameFirst',
|
|
529
|
+
// 'nameMiddle',
|
|
530
|
+
// 'nameLast',
|
|
531
|
+
// 'name',
|
|
532
|
+
// 'email',
|
|
533
|
+
// 'avatar',
|
|
534
|
+
// 'phone',
|
|
535
|
+
// 'group',
|
|
536
|
+
// 'groups',
|
|
537
|
+
// 'inheritAbilities',
|
|
473
538
|
'enabled',
|
|
474
539
|
'secret',
|
|
475
540
|
'emailChangeDate',
|
|
476
541
|
'newEmailSecret',
|
|
542
|
+
// 'newEmailAddress',
|
|
477
543
|
'cancelEmailSecret',
|
|
478
544
|
'cancelEmailAddress',
|
|
479
545
|
'recoverSecret',
|
|
@@ -481,6 +547,7 @@ class User extends AbleObject_1.default {
|
|
|
481
547
|
'salt',
|
|
482
548
|
'password',
|
|
483
549
|
'passwordTemp',
|
|
550
|
+
// 'revokeTokenDate',
|
|
484
551
|
'totpSecret',
|
|
485
552
|
];
|
|
486
553
|
}
|
|
@@ -488,6 +555,7 @@ class User extends AbleObject_1.default {
|
|
|
488
555
|
!isCurrentUser &&
|
|
489
556
|
(abilities.includes('tilmeld/switch') ||
|
|
490
557
|
abilities.includes('system/admin'))) {
|
|
558
|
+
// Users with this ability can switch to other users.
|
|
491
559
|
this.$clientEnabledMethods.push('$switchUser');
|
|
492
560
|
}
|
|
493
561
|
}
|
|
@@ -501,18 +569,36 @@ class User extends AbleObject_1.default {
|
|
|
501
569
|
}
|
|
502
570
|
}
|
|
503
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Check to see if a user has an ability.
|
|
574
|
+
*
|
|
575
|
+
* This function will check both user and group abilities, if the user is
|
|
576
|
+
* marked to inherit the abilities of its group.
|
|
577
|
+
*
|
|
578
|
+
* If `ability` is undefined, it will check to see if the user is currently
|
|
579
|
+
* logged in.
|
|
580
|
+
*
|
|
581
|
+
* If the user has the "system/admin" ability, this function will return true.
|
|
582
|
+
*
|
|
583
|
+
* @param ability The ability.
|
|
584
|
+
* @returns True or false.
|
|
585
|
+
*/
|
|
504
586
|
async $gatekeeper(ability) {
|
|
505
587
|
await this.$wake();
|
|
506
588
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
507
589
|
if (ability == null) {
|
|
508
590
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
509
591
|
}
|
|
592
|
+
// Check the cache to see if we've already checked this user.
|
|
510
593
|
if (this.$gatekeeperCache == null) {
|
|
511
594
|
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
512
595
|
}
|
|
513
596
|
return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
514
597
|
!!this.$data.abilities?.includes('system/admin'));
|
|
515
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Build a gatekeeper cache object.
|
|
601
|
+
*/
|
|
516
602
|
async $getGatekeeperCache() {
|
|
517
603
|
await this.$wake();
|
|
518
604
|
let abilities = this.$data.abilities ?? [];
|
|
@@ -537,6 +623,11 @@ class User extends AbleObject_1.default {
|
|
|
537
623
|
this.$gatekeeperCache = undefined;
|
|
538
624
|
return ret;
|
|
539
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Send the user email verification/change/cancellation links.
|
|
628
|
+
*
|
|
629
|
+
* @returns True on success, false on failure.
|
|
630
|
+
*/
|
|
540
631
|
async $sendEmailVerification() {
|
|
541
632
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
542
633
|
if (this.guid == null) {
|
|
@@ -601,6 +692,12 @@ class User extends AbleObject_1.default {
|
|
|
601
692
|
}
|
|
602
693
|
return success;
|
|
603
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Add the user to a (secondary) group.
|
|
697
|
+
*
|
|
698
|
+
* @param group The group.
|
|
699
|
+
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
700
|
+
*/
|
|
604
701
|
async $addGroup(group) {
|
|
605
702
|
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
606
703
|
let groups = this.$data.groups;
|
|
@@ -614,6 +711,12 @@ class User extends AbleObject_1.default {
|
|
|
614
711
|
}
|
|
615
712
|
return true;
|
|
616
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Check the given password against the user's.
|
|
716
|
+
*
|
|
717
|
+
* @param password The password in question.
|
|
718
|
+
* @returns True if the passwords match, otherwise false.
|
|
719
|
+
*/
|
|
617
720
|
$checkPassword(password) {
|
|
618
721
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
619
722
|
switch (tilmeld.config.pwMethod) {
|
|
@@ -627,6 +730,12 @@ class User extends AbleObject_1.default {
|
|
|
627
730
|
(0, sha256_1.default)(password + this.$data.salt).toString(enc_base64_1.default));
|
|
628
731
|
}
|
|
629
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Remove the user from a (secondary) group.
|
|
735
|
+
*
|
|
736
|
+
* @param group The group.
|
|
737
|
+
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
738
|
+
*/
|
|
630
739
|
async $delGroup(group) {
|
|
631
740
|
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
632
741
|
let groups = this.$data.groups || [];
|
|
@@ -642,6 +751,12 @@ class User extends AbleObject_1.default {
|
|
|
642
751
|
}
|
|
643
752
|
return true;
|
|
644
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Check whether the user is in a (primary or secondary) group.
|
|
756
|
+
*
|
|
757
|
+
* @param group The group, or the group's GUID.
|
|
758
|
+
* @returns True or false.
|
|
759
|
+
*/
|
|
645
760
|
async $inGroup(group) {
|
|
646
761
|
if (typeof group === 'string') {
|
|
647
762
|
if (this.$data.group?.guid === group) {
|
|
@@ -656,7 +771,14 @@ class User extends AbleObject_1.default {
|
|
|
656
771
|
}
|
|
657
772
|
return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
|
|
658
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* Check whether the user is a descendant of a group.
|
|
776
|
+
*
|
|
777
|
+
* @param group The group, or the group's GUID.
|
|
778
|
+
* @returns True or false.
|
|
779
|
+
*/
|
|
659
780
|
async $isDescendant(group) {
|
|
781
|
+
// Check to see if the user is in a descendant group of the given group.
|
|
660
782
|
await this.$data.group?.$wake();
|
|
661
783
|
if (this.$data.group != null &&
|
|
662
784
|
this.$data.group?.cdate != null &&
|
|
@@ -671,6 +793,12 @@ class User extends AbleObject_1.default {
|
|
|
671
793
|
}
|
|
672
794
|
return false;
|
|
673
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* A frontend accessible method to change the user's password.
|
|
798
|
+
*
|
|
799
|
+
* @param data The input data from the client.
|
|
800
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
801
|
+
*/
|
|
674
802
|
async $changePassword(data) {
|
|
675
803
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
676
804
|
return { result: false, message: 'Please specify a password.' };
|
|
@@ -693,6 +821,12 @@ class User extends AbleObject_1.default {
|
|
|
693
821
|
return { result: false, message: "Couldn't save new password." };
|
|
694
822
|
}
|
|
695
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Change the user's password.
|
|
826
|
+
*
|
|
827
|
+
* @param password The new password.
|
|
828
|
+
* @returns The resulting password or hash which is stored in the entity.
|
|
829
|
+
*/
|
|
696
830
|
$password(password) {
|
|
697
831
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
698
832
|
switch (tilmeld.config.pwMethod) {
|
|
@@ -712,6 +846,12 @@ class User extends AbleObject_1.default {
|
|
|
712
846
|
}
|
|
713
847
|
return this.$data.password;
|
|
714
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
851
|
+
*
|
|
852
|
+
* @param data The input data from the client.
|
|
853
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
854
|
+
*/
|
|
715
855
|
async $revokeCurrentTokens(data) {
|
|
716
856
|
if (!this.$checkPassword(data.password ?? '')) {
|
|
717
857
|
return { result: false, message: 'Incorrect password.' };
|
|
@@ -732,6 +872,12 @@ class User extends AbleObject_1.default {
|
|
|
732
872
|
async $hasTOTPSecret() {
|
|
733
873
|
return this.$data.totpSecret != null;
|
|
734
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* Check the given code against the user's TOTP secret.
|
|
877
|
+
*
|
|
878
|
+
* @param code The code in question.
|
|
879
|
+
* @returns True if the code is valid, otherwise false.
|
|
880
|
+
*/
|
|
735
881
|
$checkTOTPCode(code) {
|
|
736
882
|
if (this.$data.totpSecret == null) {
|
|
737
883
|
return false;
|
|
@@ -747,6 +893,11 @@ class User extends AbleObject_1.default {
|
|
|
747
893
|
});
|
|
748
894
|
return totp.validate({ token: code, window: 2 }) != null;
|
|
749
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
898
|
+
*
|
|
899
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
900
|
+
*/
|
|
750
901
|
async $getNewTOTPSecret() {
|
|
751
902
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
752
903
|
if (this.$data.totpSecret != null) {
|
|
@@ -769,6 +920,12 @@ class User extends AbleObject_1.default {
|
|
|
769
920
|
secret: secret.base32,
|
|
770
921
|
};
|
|
771
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
925
|
+
*
|
|
926
|
+
* @param data The input data from the client.
|
|
927
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
928
|
+
*/
|
|
772
929
|
async $saveTOTPSecret(data) {
|
|
773
930
|
if (!this.$checkPassword(data.password ?? '')) {
|
|
774
931
|
return { result: false, message: 'Incorrect password.' };
|
|
@@ -806,6 +963,13 @@ class User extends AbleObject_1.default {
|
|
|
806
963
|
return { result: false, message: "Couldn't save two factor secret." };
|
|
807
964
|
}
|
|
808
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
968
|
+
* account.
|
|
969
|
+
*
|
|
970
|
+
* @param data The input data from the client.
|
|
971
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
972
|
+
*/
|
|
809
973
|
async $removeTOTPSecret(data) {
|
|
810
974
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
811
975
|
if (data) {
|
|
@@ -849,6 +1013,11 @@ class User extends AbleObject_1.default {
|
|
|
849
1013
|
return { result: false, message: "Couldn't remove two factor secret." };
|
|
850
1014
|
}
|
|
851
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Check that a username is valid.
|
|
1018
|
+
*
|
|
1019
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1020
|
+
*/
|
|
852
1021
|
async $checkUsername() {
|
|
853
1022
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
854
1023
|
if (!tilmeld.config.emailUsernames) {
|
|
@@ -937,6 +1106,11 @@ class User extends AbleObject_1.default {
|
|
|
937
1106
|
return await this.$checkEmail();
|
|
938
1107
|
}
|
|
939
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Check that an email is unique.
|
|
1111
|
+
*
|
|
1112
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1113
|
+
*/
|
|
940
1114
|
async $checkEmail() {
|
|
941
1115
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
942
1116
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
@@ -972,6 +1146,11 @@ class User extends AbleObject_1.default {
|
|
|
972
1146
|
message: this.guid != null ? 'Email is valid.' : 'Email address is valid!',
|
|
973
1147
|
};
|
|
974
1148
|
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Check that a phone number is unique.
|
|
1151
|
+
*
|
|
1152
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1153
|
+
*/
|
|
975
1154
|
async $checkPhone() {
|
|
976
1155
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
977
1156
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
@@ -1040,6 +1219,7 @@ class User extends AbleObject_1.default {
|
|
|
1040
1219
|
if (tilmeld.config.emailUsernames) {
|
|
1041
1220
|
this.$data.email = this.$data.username;
|
|
1042
1221
|
}
|
|
1222
|
+
// Start transaction.
|
|
1043
1223
|
const transaction = 'tilmeld-register-' + (0, guid_1.nanoid)();
|
|
1044
1224
|
const nymph = this.$nymph;
|
|
1045
1225
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
@@ -1065,8 +1245,10 @@ class User extends AbleObject_1.default {
|
|
|
1065
1245
|
message: e.message,
|
|
1066
1246
|
};
|
|
1067
1247
|
}
|
|
1248
|
+
// Add primary group.
|
|
1068
1249
|
let generatedPrimaryGroup = null;
|
|
1069
1250
|
if (tilmeld.config.generatePrimary) {
|
|
1251
|
+
// Generate a new primary group for the user.
|
|
1070
1252
|
generatedPrimaryGroup = await tilmeld.Group.factory();
|
|
1071
1253
|
generatedPrimaryGroup.groupname = this.$data.username;
|
|
1072
1254
|
generatedPrimaryGroup.avatar = this.$data.avatar;
|
|
@@ -1095,6 +1277,7 @@ class User extends AbleObject_1.default {
|
|
|
1095
1277
|
this.$data.group = generatedPrimaryGroup;
|
|
1096
1278
|
}
|
|
1097
1279
|
else {
|
|
1280
|
+
// Add the default primary.
|
|
1098
1281
|
const group = await tnymph.getEntity({ class: tilmeld.Group }, {
|
|
1099
1282
|
type: '&',
|
|
1100
1283
|
equal: ['defaultPrimary', true],
|
|
@@ -1103,15 +1286,18 @@ class User extends AbleObject_1.default {
|
|
|
1103
1286
|
this.$data.group = group;
|
|
1104
1287
|
}
|
|
1105
1288
|
}
|
|
1289
|
+
// Add secondary groups.
|
|
1106
1290
|
if (tilmeld.config.userFields.includes('email') &&
|
|
1107
1291
|
tilmeld.config.verifyEmail &&
|
|
1108
1292
|
tilmeld.config.unverifiedAccess) {
|
|
1293
|
+
// Add the default secondaries for unverified users.
|
|
1109
1294
|
this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
|
|
1110
1295
|
type: '&',
|
|
1111
1296
|
equal: ['unverifiedSecondary', true],
|
|
1112
1297
|
});
|
|
1113
1298
|
}
|
|
1114
1299
|
else {
|
|
1300
|
+
// Add the default secondaries.
|
|
1115
1301
|
this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
|
|
1116
1302
|
type: '&',
|
|
1117
1303
|
equal: ['defaultSecondary', true],
|
|
@@ -1119,6 +1305,7 @@ class User extends AbleObject_1.default {
|
|
|
1119
1305
|
}
|
|
1120
1306
|
if (tilmeld.config.userFields.includes('email') &&
|
|
1121
1307
|
tilmeld.config.verifyEmail) {
|
|
1308
|
+
// The user will be enabled after verifying their e-mail address.
|
|
1122
1309
|
if (!tilmeld.config.unverifiedAccess) {
|
|
1123
1310
|
this.$data.enabled = false;
|
|
1124
1311
|
}
|
|
@@ -1126,6 +1313,8 @@ class User extends AbleObject_1.default {
|
|
|
1126
1313
|
else {
|
|
1127
1314
|
this.$data.enabled = true;
|
|
1128
1315
|
}
|
|
1316
|
+
// If create_admin is true and there are no other users, grant
|
|
1317
|
+
// "system/admin".
|
|
1129
1318
|
let madeAdmin = false;
|
|
1130
1319
|
if (tilmeld.config.createAdmin) {
|
|
1131
1320
|
const otherUsers = await tnymph.getEntities({
|
|
@@ -1134,6 +1323,7 @@ class User extends AbleObject_1.default {
|
|
|
1134
1323
|
limit: 1,
|
|
1135
1324
|
return: 'guid',
|
|
1136
1325
|
});
|
|
1326
|
+
// Make sure it's not just null, cause that means an error.
|
|
1137
1327
|
if (!otherUsers.length) {
|
|
1138
1328
|
this.$grant('system/admin');
|
|
1139
1329
|
this.$data.enabled = true;
|
|
@@ -1141,6 +1331,7 @@ class User extends AbleObject_1.default {
|
|
|
1141
1331
|
}
|
|
1142
1332
|
}
|
|
1143
1333
|
if (await this.$saveSkipAC()) {
|
|
1334
|
+
// Send the new user registered email.
|
|
1144
1335
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
1145
1336
|
await tilmeld.config.sendEmail(tilmeld, {
|
|
1146
1337
|
template: 'UserRegistered',
|
|
@@ -1157,6 +1348,7 @@ class User extends AbleObject_1.default {
|
|
|
1157
1348
|
},
|
|
1158
1349
|
}, this);
|
|
1159
1350
|
}
|
|
1351
|
+
// Save the primary group.
|
|
1160
1352
|
if (generatedPrimaryGroup != null) {
|
|
1161
1353
|
generatedPrimaryGroup.user = this;
|
|
1162
1354
|
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
@@ -1169,6 +1361,7 @@ class User extends AbleObject_1.default {
|
|
|
1169
1361
|
};
|
|
1170
1362
|
}
|
|
1171
1363
|
}
|
|
1364
|
+
// Finish up.
|
|
1172
1365
|
if (tilmeld.config.verifyEmail &&
|
|
1173
1366
|
!tilmeld.config.unverifiedAccess &&
|
|
1174
1367
|
!madeAdmin &&
|
|
@@ -1247,6 +1440,7 @@ class User extends AbleObject_1.default {
|
|
|
1247
1440
|
throw new errors_1.BadDataError("You don't have the authority to modify system admins.");
|
|
1248
1441
|
}
|
|
1249
1442
|
let sendVerification = false;
|
|
1443
|
+
// Formatting.
|
|
1250
1444
|
this.$data.username = this.$data.username.trim();
|
|
1251
1445
|
if (tilmeld.config.emailUsernames) {
|
|
1252
1446
|
this.$data.email = this.$data.username;
|
|
@@ -1260,6 +1454,7 @@ class User extends AbleObject_1.default {
|
|
|
1260
1454
|
(this.$data.nameMiddle ? ' ' + this.$data.nameMiddle : '') +
|
|
1261
1455
|
(this.$data.nameLast ? ' ' + this.$data.nameLast : '');
|
|
1262
1456
|
this.$data.enabled = !!this.$data.enabled;
|
|
1457
|
+
// Clear empty values.
|
|
1263
1458
|
if (this.$data.nameFirst === '' ||
|
|
1264
1459
|
!tilmeld.config.userFields.includes('name')) {
|
|
1265
1460
|
delete this.$data.nameFirst;
|
|
@@ -1327,6 +1522,7 @@ class User extends AbleObject_1.default {
|
|
|
1327
1522
|
if (this.$data.inheritAbilities === false) {
|
|
1328
1523
|
delete this.$data.inheritAbilities;
|
|
1329
1524
|
}
|
|
1525
|
+
// Verification.
|
|
1330
1526
|
const unCheck = await this.$checkUsername();
|
|
1331
1527
|
if (!unCheck.result) {
|
|
1332
1528
|
throw new errors_1.BadUsernameError(unCheck.message);
|
|
@@ -1338,10 +1534,16 @@ class User extends AbleObject_1.default {
|
|
|
1338
1534
|
throw new errors_1.BadEmailError(emCheck.message);
|
|
1339
1535
|
}
|
|
1340
1536
|
}
|
|
1537
|
+
// Email changes.
|
|
1341
1538
|
if (!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1342
1539
|
tilmeld.config.userFields.includes('email')) {
|
|
1540
|
+
// The user isn't an admin, so email address changes should contain some
|
|
1541
|
+
// security measures.
|
|
1343
1542
|
if (tilmeld.config.verifyEmail) {
|
|
1543
|
+
// The user needs to verify this new email address.
|
|
1344
1544
|
if (this.guid == null) {
|
|
1545
|
+
// If this is the first user, they'll be made an admin, so they don't
|
|
1546
|
+
// need to verify.
|
|
1345
1547
|
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1346
1548
|
this.$data.secret = (0, guid_1.nanoid)();
|
|
1347
1549
|
sendVerification = true;
|
|
@@ -1349,6 +1551,7 @@ class User extends AbleObject_1.default {
|
|
|
1349
1551
|
}
|
|
1350
1552
|
else if (this.$originalEmail != null &&
|
|
1351
1553
|
this.$originalEmail !== this.$data.email) {
|
|
1554
|
+
// The user already has an old email address.
|
|
1352
1555
|
if (tilmeld.config.emailRateLimit !== '' &&
|
|
1353
1556
|
this.$data.emailChangeDate != null &&
|
|
1354
1557
|
this.$data.emailChangeDate >=
|
|
@@ -1360,13 +1563,18 @@ class User extends AbleObject_1.default {
|
|
|
1360
1563
|
else {
|
|
1361
1564
|
if (this.$data.newEmailSecret == null &&
|
|
1362
1565
|
tilmeld.config.emailRateLimit !== '' &&
|
|
1566
|
+
// Make sure the user has at least the rate limit time to cancel
|
|
1567
|
+
// an email change.
|
|
1363
1568
|
(this.$data.emailChangeDate == null ||
|
|
1364
1569
|
this.$data.emailChangeDate <
|
|
1365
1570
|
(0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1571
|
+
// Save the old email in case the cancel change link is clicked.
|
|
1366
1572
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1367
1573
|
this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
|
|
1368
1574
|
this.$data.emailChangeDate = Date.now();
|
|
1369
1575
|
}
|
|
1576
|
+
// Save the new address and reset to the old one (until they verify
|
|
1577
|
+
// it).
|
|
1370
1578
|
this.$data.newEmailAddress = this.$data.email;
|
|
1371
1579
|
this.$data.email = this.$originalEmail;
|
|
1372
1580
|
if (tilmeld.config.emailUsernames) {
|
|
@@ -1380,9 +1588,13 @@ class User extends AbleObject_1.default {
|
|
|
1380
1588
|
else if (this.guid != null &&
|
|
1381
1589
|
this.$originalEmail != null &&
|
|
1382
1590
|
this.$originalEmail !== this.$data.email &&
|
|
1591
|
+
// Make sure the user has at least the rate limit time to cancel an
|
|
1592
|
+
// email change.
|
|
1383
1593
|
(this.$data.emailChangeDate == null ||
|
|
1384
1594
|
this.$data.emailChangeDate <
|
|
1385
1595
|
(0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1596
|
+
// The user doesn't need to verify their new email address, but should
|
|
1597
|
+
// be able to cancel the email change from their old address.
|
|
1386
1598
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1387
1599
|
this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
|
|
1388
1600
|
sendVerification = true;
|
|
@@ -1401,6 +1613,7 @@ class User extends AbleObject_1.default {
|
|
|
1401
1613
|
catch (e) {
|
|
1402
1614
|
throw new errors_1.BadDataError(e?.message);
|
|
1403
1615
|
}
|
|
1616
|
+
// Start transaction.
|
|
1404
1617
|
const transaction = 'tilmeld-save-' + (0, guid_1.nanoid)();
|
|
1405
1618
|
const nymph = this.$nymph;
|
|
1406
1619
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
@@ -1410,6 +1623,7 @@ class User extends AbleObject_1.default {
|
|
|
1410
1623
|
await group?.$wake();
|
|
1411
1624
|
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1412
1625
|
try {
|
|
1626
|
+
// Update the user's generated primary group.
|
|
1413
1627
|
group.groupname = this.$data.username;
|
|
1414
1628
|
group.avatar = this.$data.avatar;
|
|
1415
1629
|
if (tilmeld.config.userFields.includes('email')) {
|
|
@@ -1444,6 +1658,7 @@ class User extends AbleObject_1.default {
|
|
|
1444
1658
|
}
|
|
1445
1659
|
if (ret) {
|
|
1446
1660
|
if (sendVerification) {
|
|
1661
|
+
// The email has changed, so send a new verification email.
|
|
1447
1662
|
if (!(await this.$sendEmailVerification())) {
|
|
1448
1663
|
await tnymph.rollback(transaction);
|
|
1449
1664
|
this.guid = preGuid;
|
|
@@ -1458,6 +1673,7 @@ class User extends AbleObject_1.default {
|
|
|
1458
1673
|
await tnymph.commit(transaction);
|
|
1459
1674
|
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(nymph);
|
|
1460
1675
|
if (tilmeld.User.current(true).$is(this)) {
|
|
1676
|
+
// Update the user in the session cache.
|
|
1461
1677
|
await tilmeld.fillSession(this);
|
|
1462
1678
|
}
|
|
1463
1679
|
}
|
|
@@ -1467,6 +1683,9 @@ class User extends AbleObject_1.default {
|
|
|
1467
1683
|
this.$nymph = nymph;
|
|
1468
1684
|
return ret;
|
|
1469
1685
|
}
|
|
1686
|
+
/**
|
|
1687
|
+
* This should *never* be accessible on the client.
|
|
1688
|
+
*/
|
|
1470
1689
|
async $saveSkipAC() {
|
|
1471
1690
|
this.$skipAcWhenSaving = true;
|
|
1472
1691
|
return await this.$save();
|
|
@@ -1497,6 +1716,7 @@ class User extends AbleObject_1.default {
|
|
|
1497
1716
|
if (!(prop in this)) {
|
|
1498
1717
|
throw new Error('Invalid event type.');
|
|
1499
1718
|
}
|
|
1719
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1500
1720
|
this[prop].push(callback);
|
|
1501
1721
|
return () => this.off(event, callback);
|
|
1502
1722
|
}
|
|
@@ -1505,13 +1725,18 @@ class User extends AbleObject_1.default {
|
|
|
1505
1725
|
if (!(prop in this)) {
|
|
1506
1726
|
return false;
|
|
1507
1727
|
}
|
|
1728
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1508
1729
|
const i = this[prop].indexOf(callback);
|
|
1509
1730
|
if (i > -1) {
|
|
1731
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1510
1732
|
this[prop].splice(i, 1);
|
|
1511
1733
|
}
|
|
1512
1734
|
return true;
|
|
1513
1735
|
}
|
|
1514
1736
|
}
|
|
1737
|
+
/**
|
|
1738
|
+
* The instance of Tilmeld to use for queries.
|
|
1739
|
+
*/
|
|
1515
1740
|
User.ETYPE = 'tilmeld_user';
|
|
1516
1741
|
User.class = 'User';
|
|
1517
1742
|
User.checkUsernameCallbacks = [];
|
|
@@ -1529,8 +1754,15 @@ User.DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
|
1529
1754
|
'$register',
|
|
1530
1755
|
];
|
|
1531
1756
|
User.DEFAULT_PRIVATE_DATA = [
|
|
1757
|
+
// The things that are commented are there to show what is _not_ private.
|
|
1532
1758
|
'abilities',
|
|
1759
|
+
// 'username',
|
|
1760
|
+
// 'nameFirst',
|
|
1761
|
+
// 'nameMiddle',
|
|
1762
|
+
// 'nameLast',
|
|
1763
|
+
// 'name',
|
|
1533
1764
|
'email',
|
|
1765
|
+
// 'avatar',
|
|
1534
1766
|
'phone',
|
|
1535
1767
|
'group',
|
|
1536
1768
|
'groups',
|