@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/AbleObject.d.ts +15 -0
  3. package/dist/AbleObject.js +15 -0
  4. package/dist/AbleObject.js.map +1 -1
  5. package/dist/Group.d.ts +116 -0
  6. package/dist/Group.js +98 -0
  7. package/dist/Group.js.map +1 -1
  8. package/dist/Group.test.js +1 -0
  9. package/dist/Group.test.js.map +1 -1
  10. package/dist/Tilmeld.d.ts +226 -1
  11. package/dist/Tilmeld.js +304 -13
  12. package/dist/Tilmeld.js.map +1 -1
  13. package/dist/User.d.ts +265 -0
  14. package/dist/User.js +232 -0
  15. package/dist/User.js.map +1 -1
  16. package/dist/User.test.js +1 -0
  17. package/dist/User.test.js.map +1 -1
  18. package/dist/conf/d.d.ts +244 -0
  19. package/dist/conf/defaults.js +6 -3
  20. package/dist/conf/defaults.js.map +1 -1
  21. package/dist/errors/AccessControlError.d.ts +2 -1
  22. package/dist/errors/AccessControlError.js +3 -2
  23. package/dist/errors/AccessControlError.js.map +1 -1
  24. package/dist/errors/BadDataError.d.ts +2 -1
  25. package/dist/errors/BadDataError.js +3 -2
  26. package/dist/errors/BadDataError.js.map +1 -1
  27. package/dist/errors/BadEmailError.d.ts +2 -1
  28. package/dist/errors/BadEmailError.js +3 -2
  29. package/dist/errors/BadEmailError.js.map +1 -1
  30. package/dist/errors/BadUsernameError.d.ts +2 -1
  31. package/dist/errors/BadUsernameError.js +3 -2
  32. package/dist/errors/BadUsernameError.js.map +1 -1
  33. package/package.json +8 -7
  34. package/src/Tilmeld.ts +7 -3
  35. package/src/errors/AccessControlError.ts +4 -2
  36. package/src/errors/BadDataError.ts +4 -2
  37. package/src/errors/BadEmailError.ts +4 -2
  38. package/src/errors/BadUsernameError.ts +4 -2
  39. 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',