@nymphjs/tilmeld 1.0.0-beta.121 → 1.0.0-beta.123

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 (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +6 -3
  3. package/dist/Group.d.ts +9 -9
  4. package/dist/Group.js +790 -793
  5. package/dist/Group.js.map +1 -1
  6. package/dist/Group.testArtifacts.js +3 -3
  7. package/dist/Group.testArtifacts.js.map +1 -1
  8. package/dist/Tilmeld.js +6 -15
  9. package/dist/Tilmeld.js.map +1 -1
  10. package/dist/User.d.ts +8 -9
  11. package/dist/User.js +1838 -1957
  12. package/dist/User.js.map +1 -1
  13. package/dist/User.testArtifacts.js +7 -7
  14. package/dist/User.testArtifacts.js.map +1 -1
  15. package/dist/decorators/index.d.ts +2 -0
  16. package/dist/decorators/index.js +3 -0
  17. package/dist/decorators/index.js.map +1 -0
  18. package/dist/decorators/requireAbility.d.ts +10 -0
  19. package/dist/decorators/requireAbility.js +21 -0
  20. package/dist/decorators/requireAbility.js.map +1 -0
  21. package/dist/decorators/requireAuthentication.d.ts +8 -0
  22. package/dist/decorators/requireAuthentication.js +16 -0
  23. package/dist/decorators/requireAuthentication.js.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/testArtifacts.d.ts +5 -1
  28. package/dist/testArtifacts.js +211 -67
  29. package/dist/testArtifacts.js.map +1 -1
  30. package/package.json +13 -7
  31. package/src/Group.testArtifacts.ts +3 -3
  32. package/src/Group.ts +108 -158
  33. package/src/Tilmeld.ts +6 -35
  34. package/src/User.testArtifacts.ts +7 -7
  35. package/src/User.ts +364 -515
  36. package/src/decorators/index.ts +2 -0
  37. package/src/decorators/requireAbility.ts +26 -0
  38. package/src/decorators/requireAuthentication.ts +19 -0
  39. package/src/index.ts +1 -0
  40. package/src/testArtifacts.ts +165 -46
package/src/User.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import crypto from 'node:crypto';
2
- import type {
3
- Nymph,
4
- EntityData,
5
- EntityJson,
6
- EntityPatch,
7
- Selector,
8
- SerializedEntityData,
2
+ import {
3
+ type EntityData,
4
+ type EntityJson,
5
+ type EntityPatch,
6
+ type Selector,
7
+ type SerializedEntityData,
8
+ transaction,
9
+ transactional,
9
10
  } from '@nymphjs/nymph';
10
11
  import { humanSecret, nanoid } from '@nymphjs/guid';
11
12
  import type { EmailOptions } from 'email-templates';
@@ -372,9 +373,7 @@ export default class User extends AbleObject<UserData> {
372
373
  const entity = new this();
373
374
  if (username != null) {
374
375
  const entity = await this.nymph.getEntity(
375
- {
376
- class: this,
377
- },
376
+ { class: this },
378
377
  {
379
378
  type: '&',
380
379
  ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
@@ -413,10 +412,7 @@ export default class User extends AbleObject<UserData> {
413
412
  }): Promise<{ result: boolean; message: string }> {
414
413
  const tilmeld = enforceTilmeld(this);
415
414
  if (!tilmeld.config.pwRecovery) {
416
- return {
417
- result: false,
418
- message: 'Account recovery is not allowed.',
419
- };
415
+ return { result: false, message: 'Account recovery is not allowed.' };
420
416
  }
421
417
 
422
418
  let user: User & UserData;
@@ -444,10 +440,7 @@ export default class User extends AbleObject<UserData> {
444
440
  user = getUser;
445
441
  options.template = 'RecoverUsername';
446
442
  options.message = {
447
- to: {
448
- name: user.name ?? '',
449
- address: user.email ?? '',
450
- },
443
+ to: { name: user.name ?? '', address: user.email ?? '' },
451
444
  };
452
445
  options.locals = {};
453
446
  } else if (data.recoveryType === 'password') {
@@ -465,7 +458,9 @@ export default class User extends AbleObject<UserData> {
465
458
  // Create a unique secret.
466
459
  getUser.recoverSecret = humanSecret();
467
460
  getUser.recoverSecretDate = Date.now();
468
- if (!(await getUser.$saveSkipAC())) {
461
+ try {
462
+ await getUser.$saveSkipAC();
463
+ } catch (e: any) {
469
464
  return { result: false, message: "Couldn't save recovery secret." };
470
465
  }
471
466
 
@@ -473,10 +468,7 @@ export default class User extends AbleObject<UserData> {
473
468
  user = getUser;
474
469
  options.template = 'RecoverPassword';
475
470
  options.message = {
476
- to: {
477
- name: user.name ?? '',
478
- address: user.email ?? '',
479
- },
471
+ to: { name: user.name ?? '', address: user.email ?? '' },
480
472
  };
481
473
  options.locals = {
482
474
  recoverCode: getUser.recoverSecret,
@@ -517,10 +509,7 @@ export default class User extends AbleObject<UserData> {
517
509
  }): Promise<{ result: boolean; message: string }> {
518
510
  const tilmeld = enforceTilmeld(this);
519
511
  if (!tilmeld.config.pwRecovery) {
520
- return {
521
- result: false,
522
- message: 'Account recovery is not allowed.',
523
- };
512
+ return { result: false, message: 'Account recovery is not allowed.' };
524
513
  }
525
514
 
526
515
  const user = await tilmeld.User.factoryUsername(data.username);
@@ -536,10 +525,7 @@ export default class User extends AbleObject<UserData> {
536
525
  1000 <
537
526
  Date.now()
538
527
  ) {
539
- return {
540
- result: false,
541
- message: 'The secret code does not match.',
542
- };
528
+ return { result: false, message: 'The secret code does not match.' };
543
529
  }
544
530
 
545
531
  if (!('password' in data) || !data.password.length) {
@@ -554,13 +540,14 @@ export default class User extends AbleObject<UserData> {
554
540
  user.$password(data.password);
555
541
  delete user.recoverSecret;
556
542
  delete user.recoverSecretDate;
557
- if (await user.$saveSkipAC()) {
543
+ try {
544
+ await user.$saveSkipAC();
558
545
  return {
559
546
  result: true,
560
547
  message:
561
548
  'Your password has been reset. You can now log in using your new password.',
562
549
  };
563
- } else {
550
+ } catch (e: any) {
564
551
  return { result: false, message: 'Error saving new password.' };
565
552
  }
566
553
  }
@@ -636,26 +623,10 @@ export default class User extends AbleObject<UserData> {
636
623
  return await this.nymph.getEntities(
637
624
  {
638
625
  class: this.nymph.getEntityClass(User),
639
- ...(options?.limit != null
640
- ? {
641
- limit: options.limit,
642
- }
643
- : {}),
644
- ...(options?.offset != null
645
- ? {
646
- offset: options.offset,
647
- }
648
- : {}),
649
- ...(options?.sort != null
650
- ? {
651
- sort: options.sort,
652
- }
653
- : {}),
654
- ...(options?.reverse != null
655
- ? {
656
- reverse: options.reverse,
657
- }
658
- : {}),
626
+ ...(options?.limit != null ? { limit: options.limit } : {}),
627
+ ...(options?.offset != null ? { offset: options.offset } : {}),
628
+ ...(options?.sort != null ? { sort: options.sort } : {}),
629
+ ...(options?.reverse != null ? { reverse: options.reverse } : {}),
659
630
  },
660
631
  {
661
632
  type: '&',
@@ -675,20 +646,6 @@ export default class User extends AbleObject<UserData> {
675
646
  this.$updateDataProtection();
676
647
  }
677
648
 
678
- $setNymph(nymph: Nymph) {
679
- this.$nymph = nymph;
680
- if (!this.$asleep()) {
681
- if (this.$data.group && this.$data.group.$nymph !== nymph) {
682
- this.$data.group.$setNymph(nymph);
683
- }
684
- for (const group of this.$data.groups ?? []) {
685
- if (group && group.$nymph !== nymph) {
686
- group.$setNymph(nymph);
687
- }
688
- }
689
- }
690
- }
691
-
692
649
  async $getUniques(): Promise<string[]> {
693
650
  const tilmeld = enforceTilmeld(this);
694
651
  const uniques = [`u:${this.$data.username}`];
@@ -754,10 +711,7 @@ export default class User extends AbleObject<UserData> {
754
711
  }
755
712
  }
756
713
  } catch (e: any) {
757
- return {
758
- result: false,
759
- message: e.message,
760
- };
714
+ return { result: false, message: e.message };
761
715
  }
762
716
 
763
717
  this.$nymph.config.debugLog(
@@ -781,7 +735,9 @@ export default class User extends AbleObject<UserData> {
781
735
  return { result: true, message: 'You are logged in.' };
782
736
  }
783
737
 
784
- public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
738
+ public async $switchUser(data?: {
739
+ additionalData?: { [k: string]: any };
740
+ }): Promise<{ result: boolean; message: string }> {
785
741
  const tilmeld = enforceTilmeld(this);
786
742
  if (this.guid == null) {
787
743
  return { result: false, message: 'Incorrect login/password.' };
@@ -831,10 +787,7 @@ export default class User extends AbleObject<UserData> {
831
787
  }
832
788
  }
833
789
  } catch (e: any) {
834
- return {
835
- result: false,
836
- message: e.message,
837
- };
790
+ return { result: false, message: e.message };
838
791
  }
839
792
 
840
793
  this.$nymph.config.debugLog(
@@ -862,7 +815,7 @@ export default class User extends AbleObject<UserData> {
862
815
  * Log a user out of the system.
863
816
  * @returns An object with a boolean 'result' entry and a 'message' entry.
864
817
  */
865
- public async $logout() {
818
+ public async $logout(): Promise<{ result: boolean; message: string }> {
866
819
  const tilmeld = enforceTilmeld(this);
867
820
 
868
821
  try {
@@ -873,10 +826,7 @@ export default class User extends AbleObject<UserData> {
873
826
  }
874
827
  }
875
828
  } catch (e: any) {
876
- return {
877
- result: false,
878
- message: e.message,
879
- };
829
+ return { result: false, message: e.message };
880
830
  }
881
831
 
882
832
  this.$nymph.config.debugLog(
@@ -896,7 +846,7 @@ export default class User extends AbleObject<UserData> {
896
846
  return { result: true, message: 'You have been logged out.' };
897
847
  }
898
848
 
899
- public $getAvatar() {
849
+ public $getAvatar(): string {
900
850
  if (this.$data.avatar != null && this.$data.avatar !== '') {
901
851
  return this.$data.avatar;
902
852
  }
@@ -1130,7 +1080,6 @@ export default class User extends AbleObject<UserData> {
1130
1080
  this.$clientEnabledMethods.push('$changePassword');
1131
1081
  this.$clientEnabledMethods.push('$revokeCurrentTokens');
1132
1082
  this.$clientEnabledMethods.push('$logout');
1133
- this.$clientEnabledMethods.push('$sendEmailVerification');
1134
1083
  this.$clientEnabledMethods.push('$hasTOTPSecret');
1135
1084
  this.$clientEnabledMethods.push('$getNewTOTPSecret');
1136
1085
  this.$clientEnabledMethods.push('$saveTOTPSecret');
@@ -1344,7 +1293,7 @@ export default class User extends AbleObject<UserData> {
1344
1293
  *
1345
1294
  * @returns True on success, false on failure.
1346
1295
  */
1347
- public async $sendEmailVerification() {
1296
+ public async $sendEmailVerification(): Promise<boolean> {
1348
1297
  const tilmeld = enforceTilmeld(this);
1349
1298
  if (this.guid == null) {
1350
1299
  return false;
@@ -1371,9 +1320,7 @@ export default class User extends AbleObject<UserData> {
1371
1320
  address: this.$data.email ?? '',
1372
1321
  },
1373
1322
  },
1374
- locals: {
1375
- verifyLink: link,
1376
- },
1323
+ locals: { verifyLink: link },
1377
1324
  },
1378
1325
  this,
1379
1326
  ));
@@ -1581,13 +1528,14 @@ export default class User extends AbleObject<UserData> {
1581
1528
  if (data.revokeCurrentTokens) {
1582
1529
  this.$data.revokeTokenDate = Date.now();
1583
1530
  }
1584
- if (await this.$save()) {
1531
+ try {
1532
+ await this.$save();
1585
1533
  if (data.revokeCurrentTokens) {
1586
1534
  const tilmeld = enforceTilmeld(this);
1587
1535
  await tilmeld.login(this, true);
1588
1536
  }
1589
1537
  return { result: true, message: 'Your password has been changed.' };
1590
- } else {
1538
+ } catch (e: any) {
1591
1539
  return { result: false, message: "Couldn't save new password." };
1592
1540
  }
1593
1541
  }
@@ -1639,19 +1587,20 @@ export default class User extends AbleObject<UserData> {
1639
1587
 
1640
1588
  this.$data.revokeTokenDate = Date.now();
1641
1589
 
1642
- if (await this.$save()) {
1590
+ try {
1591
+ await this.$save();
1643
1592
  const tilmeld = enforceTilmeld(this);
1644
1593
  await tilmeld.login(this, true);
1645
1594
  return {
1646
1595
  result: true,
1647
1596
  message: 'You have logged out of all other sessions.',
1648
1597
  };
1649
- } else {
1598
+ } catch (e: any) {
1650
1599
  return { result: false, message: "Couldn't save revocation date." };
1651
1600
  }
1652
1601
  }
1653
1602
 
1654
- public async $hasTOTPSecret() {
1603
+ public $hasTOTPSecret(): boolean {
1655
1604
  return this.$data.totpSecret != null;
1656
1605
  }
1657
1606
 
@@ -1685,7 +1634,11 @@ export default class User extends AbleObject<UserData> {
1685
1634
  *
1686
1635
  * @returns An object with 'uri', 'qrcode', and 'secret'.
1687
1636
  */
1688
- public async $getNewTOTPSecret() {
1637
+ public async $getNewTOTPSecret(): Promise<{
1638
+ uri: string;
1639
+ qrcode: string;
1640
+ secret: string;
1641
+ }> {
1689
1642
  const tilmeld = enforceTilmeld(this);
1690
1643
 
1691
1644
  if (this.$data.totpSecret != null) {
@@ -1707,11 +1660,7 @@ export default class User extends AbleObject<UserData> {
1707
1660
 
1708
1661
  const qrcode = await toDataURL(uri);
1709
1662
 
1710
- return {
1711
- uri,
1712
- qrcode,
1713
- secret: secret.base32,
1714
- };
1663
+ return { uri, qrcode, secret: secret.base32 };
1715
1664
  }
1716
1665
 
1717
1666
  /**
@@ -1766,9 +1715,10 @@ export default class User extends AbleObject<UserData> {
1766
1715
 
1767
1716
  this.$data.totpSecret = data.secret;
1768
1717
 
1769
- if (await this.$save()) {
1718
+ try {
1719
+ await this.$save();
1770
1720
  return { result: true, message: 'Your two factor secret is now set.' };
1771
- } else {
1721
+ } catch (e: any) {
1772
1722
  return { result: false, message: "Couldn't save two factor secret." };
1773
1723
  }
1774
1724
  }
@@ -1824,12 +1774,10 @@ export default class User extends AbleObject<UserData> {
1824
1774
 
1825
1775
  delete this.$data.totpSecret;
1826
1776
 
1827
- if (await this.$save()) {
1828
- return {
1829
- result: true,
1830
- message: 'Two factor secret has been removed.',
1831
- };
1832
- } else {
1777
+ try {
1778
+ await this.$save();
1779
+ return { result: true, message: 'Two factor secret has been removed.' };
1780
+ } catch (e: any) {
1833
1781
  return { result: false, message: "Couldn't remove two factor secret." };
1834
1782
  }
1835
1783
  }
@@ -1889,16 +1837,10 @@ export default class User extends AbleObject<UserData> {
1889
1837
  difference(username.split(''), tilmeld.config.validChars.split(''))
1890
1838
  .length
1891
1839
  ) {
1892
- return {
1893
- result: false,
1894
- message: tilmeld.config.validCharsNotice,
1895
- };
1840
+ return { result: false, message: tilmeld.config.validCharsNotice };
1896
1841
  }
1897
1842
  if (!tilmeld.config.validRegex.test(username)) {
1898
- return {
1899
- result: false,
1900
- message: tilmeld.config.validRegexNotice,
1901
- };
1843
+ return { result: false, message: tilmeld.config.validRegexNotice };
1902
1844
  }
1903
1845
  if (domain != null) {
1904
1846
  if (!tilmeld.config.validDomainRegex.test(domain)) {
@@ -2004,10 +1946,7 @@ export default class User extends AbleObject<UserData> {
2004
1946
  }
2005
1947
  }
2006
1948
  if (!tilmeld.config.validEmailRegex.test(this.$data.email)) {
2007
- return {
2008
- result: false,
2009
- message: tilmeld.config.validEmailRegexNotice,
2010
- };
1949
+ return { result: false, message: tilmeld.config.validEmailRegexNotice };
2011
1950
  }
2012
1951
  const selector: Selector = {
2013
1952
  type: '&',
@@ -2127,187 +2066,163 @@ export default class User extends AbleObject<UserData> {
2127
2066
  this.$data.email = this.$data.username;
2128
2067
  }
2129
2068
 
2130
- // Start transaction.
2131
- const transaction = 'tilmeld-register-' + nanoid();
2132
- const nymph = this.$nymph;
2133
- const tnymph = await this.$nymph.startTransaction(transaction);
2134
- this.$setNymph(tnymph);
2135
- tilmeld = enforceTilmeld(this);
2069
+ try {
2070
+ const result = await transaction(
2071
+ this.$nymph,
2072
+ 'tilmeld-register-' + nanoid(),
2073
+ async (nymph) => {
2074
+ this.$setNymph(nymph);
2075
+ const tilmeld = enforceTilmeld(this);
2136
2076
 
2137
- let message = '';
2138
- let loggedin = false;
2077
+ let message = '';
2078
+ let loggedin = false;
2139
2079
 
2140
- try {
2141
- try {
2142
- for (let callback of (this.constructor as typeof User)
2143
- .beforeRegisterCallbacks) {
2144
- if (callback) {
2145
- await callback(this, data);
2080
+ for (let callback of (this.constructor as typeof User)
2081
+ .beforeRegisterCallbacks) {
2082
+ if (callback) {
2083
+ await callback(this, data);
2084
+ }
2146
2085
  }
2147
- }
2148
- } catch (e: any) {
2149
- await tnymph.rollback(transaction);
2150
- this.$setNymph(nymph);
2151
- return {
2152
- result: false,
2153
- loggedin: false,
2154
- message: e.message,
2155
- };
2156
- }
2157
2086
 
2158
- if (
2159
- tilmeld.config.userFields.includes('email') &&
2160
- tilmeld.config.verifyEmail
2161
- ) {
2162
- // The user will be enabled after verifying their e-mail address.
2163
- if (!tilmeld.config.unverifiedAccess) {
2164
- this.$data.enabled = false;
2165
- }
2166
- } else {
2167
- this.$data.enabled = true;
2168
- }
2087
+ if (
2088
+ tilmeld.config.userFields.includes('email') &&
2089
+ tilmeld.config.verifyEmail
2090
+ ) {
2091
+ // The user will be enabled after verifying their e-mail address.
2092
+ if (!tilmeld.config.unverifiedAccess) {
2093
+ this.$data.enabled = false;
2094
+ }
2095
+ } else {
2096
+ this.$data.enabled = true;
2097
+ }
2169
2098
 
2170
- // If createAdmin is true and there are no other users, grant
2171
- // "system/admin".
2172
- let madeAdmin = false;
2173
- if (tilmeld.config.createAdmin) {
2174
- const otherUsers = await tnymph.getEntities({
2175
- class: tilmeld.User,
2176
- skipAc: true,
2177
- limit: 1,
2178
- return: 'guid',
2179
- });
2180
- // Make sure it's not just null, cause that means an error.
2181
- if (otherUsers == null) {
2182
- await tnymph.rollback(transaction);
2183
- this.$setNymph(nymph);
2184
- return {
2185
- result: false,
2186
- loggedin: false,
2187
- message: 'An error occurred.',
2188
- };
2189
- }
2190
- if (!otherUsers.length) {
2191
- this.$grant('system/admin');
2192
- this.$data.enabled = true;
2193
- madeAdmin = true;
2194
- }
2195
- }
2099
+ // If createAdmin is true and there are no other users, grant
2100
+ // "system/admin".
2101
+ let madeAdmin = false;
2102
+ if (tilmeld.config.createAdmin) {
2103
+ const otherUsers = await this.$nymph.getEntities({
2104
+ class: tilmeld.User,
2105
+ skipAc: true,
2106
+ limit: 1,
2107
+ return: 'guid',
2108
+ });
2109
+ // Make sure it's not just null, cause that means an error.
2110
+ if (otherUsers == null) {
2111
+ throw new Error('An error occurred.');
2112
+ }
2113
+ if (!otherUsers.length) {
2114
+ this.$grant('system/admin');
2115
+ this.$data.enabled = true;
2116
+ madeAdmin = true;
2117
+ }
2118
+ }
2196
2119
 
2197
- this.$nymph.config.debugLog(
2198
- 'tilmeld',
2199
- `Registering new user "${this.$data.username}".`,
2200
- );
2201
- if (await this.$saveSkipAC()) {
2202
- this.$nymph.config.debugLog(
2203
- 'tilmeld',
2204
- `New user registered "${this.$data.username}".`,
2205
- );
2206
- // Send the new user registered email.
2207
- if (tilmeld.config.userRegisteredRecipient != null) {
2208
- await tilmeld.config.sendEmail(
2209
- tilmeld,
2210
- {
2211
- template: 'UserRegistered',
2212
- message: {
2213
- to: tilmeld.config.userRegisteredRecipient,
2214
- },
2215
- locals: {
2216
- userUsername: this.$data.username,
2217
- userName: this.$data.name,
2218
- userFirstName: this.$data.nameFirst,
2219
- userLastName: this.$data.nameLast,
2220
- userEmail: this.$data.email,
2221
- userPhone: this.$data.phone,
2222
- },
2223
- },
2224
- this,
2120
+ this.$nymph.config.debugLog(
2121
+ 'tilmeld',
2122
+ `Registering new user "${this.$data.username}".`,
2225
2123
  );
2226
- }
2227
2124
 
2228
- // Finish up.
2229
- if (
2230
- tilmeld.config.verifyEmail &&
2231
- !tilmeld.config.unverifiedAccess &&
2232
- !madeAdmin &&
2233
- tilmeld.config.userFields.includes('email')
2234
- ) {
2235
- message +=
2236
- `Almost there. An email has been sent to ${this.$data.email} ` +
2237
- 'with a verification link for you to finish registration.';
2238
- } else if (
2239
- tilmeld.config.verifyEmail &&
2240
- tilmeld.config.unverifiedAccess &&
2241
- !madeAdmin &&
2242
- tilmeld.config.userFields.includes('email')
2243
- ) {
2244
- if (!(await tilmeld.login(this, true))) {
2245
- throw new Error('An error occurred trying to log you in.');
2125
+ await this.$saveSkipAC();
2126
+
2127
+ // Finish up.
2128
+ if (
2129
+ tilmeld.config.verifyEmail &&
2130
+ !tilmeld.config.unverifiedAccess &&
2131
+ !madeAdmin &&
2132
+ tilmeld.config.userFields.includes('email')
2133
+ ) {
2134
+ message +=
2135
+ `Almost there. An email has been sent to ${this.$data.email} ` +
2136
+ 'with a verification link for you to finish registration.';
2137
+ } else if (
2138
+ tilmeld.config.verifyEmail &&
2139
+ tilmeld.config.unverifiedAccess &&
2140
+ !madeAdmin &&
2141
+ tilmeld.config.userFields.includes('email')
2142
+ ) {
2143
+ if (!(await tilmeld.login(this, true))) {
2144
+ throw new Error('An error occurred trying to log you in.');
2145
+ }
2146
+ message +=
2147
+ "You're now logged in! An email has been sent to " +
2148
+ `${this.$data.email} with a verification link for you to finish ` +
2149
+ 'registration.';
2150
+ loggedin = true;
2151
+ } else {
2152
+ if (!(await tilmeld.login(this, true))) {
2153
+ throw new Error('An error occurred trying to log you in.');
2154
+ }
2155
+ message += "You're now registered and logged in!";
2156
+ loggedin = true;
2246
2157
  }
2247
- message +=
2248
- "You're now logged in! An email has been sent to " +
2249
- `${this.$data.email} with a verification link for you to finish ` +
2250
- 'registration.';
2251
- loggedin = true;
2252
- } else {
2253
- if (!(await tilmeld.login(this, true))) {
2254
- throw new Error('An error occurred trying to log you in.');
2158
+
2159
+ for (let callback of (this.constructor as typeof User)
2160
+ .afterRegisterCallbacks) {
2161
+ if (callback) {
2162
+ await callback(this, { loggedin, message });
2163
+ }
2255
2164
  }
2256
- message += "You're now registered and logged in!";
2257
- loggedin = true;
2258
- }
2259
2165
 
2260
- for (let callback of (this.constructor as typeof User)
2261
- .afterRegisterCallbacks) {
2262
- if (callback) {
2263
- await callback(this, {
2264
- loggedin,
2265
- message,
2266
- });
2166
+ return { result: true, loggedin, message };
2167
+ },
2168
+ async (nymph) => {
2169
+ this.$setNymph(nymph);
2170
+ },
2171
+ );
2172
+
2173
+ // Send the new user registered email.
2174
+ if (tilmeld.config.userRegisteredRecipient != null) {
2175
+ try {
2176
+ if (
2177
+ !(await tilmeld.config.sendEmail(
2178
+ tilmeld,
2179
+ {
2180
+ template: 'UserRegistered',
2181
+ message: { to: tilmeld.config.userRegisteredRecipient },
2182
+ locals: {
2183
+ userUsername: this.$data.username,
2184
+ userName: this.$data.name,
2185
+ userFirstName: this.$data.nameFirst,
2186
+ userLastName: this.$data.nameLast,
2187
+ userEmail: this.$data.email,
2188
+ userPhone: this.$data.phone,
2189
+ },
2190
+ },
2191
+ this,
2192
+ ))
2193
+ ) {
2194
+ throw new Error('Failed to send.');
2267
2195
  }
2196
+ } catch (e: any) {
2197
+ this.$nymph.config.debugError(
2198
+ 'tilmeld',
2199
+ `Couldn't send new user registered email for "${this.$data.username}": ${e.message}`,
2200
+ );
2268
2201
  }
2269
- } else {
2270
- this.$nymph.config.debugError(
2271
- 'tilmeld',
2272
- `Error registering new user "${this.$data.username}".`,
2273
- );
2274
- await tnymph.rollback(transaction);
2275
- this.$setNymph(nymph);
2276
- return {
2277
- result: false,
2278
- loggedin: false,
2279
- message: 'Error registering user.',
2280
- };
2281
2202
  }
2282
- } catch (e: any) {
2283
- this.$nymph.config.debugError(
2284
- 'tilmeld',
2285
- `Error registering new user "${this.$data.username}": ${e}`,
2286
- );
2287
- await tnymph.rollback(transaction);
2288
- this.$setNymph(nymph);
2289
- throw e;
2290
- }
2291
2203
 
2292
- try {
2293
- await tnymph.commit(transaction);
2294
- this.$setNymph(nymph);
2295
- await this.$nymph.tilmeld?.fillSession(this);
2204
+ if (result.loggedin) {
2205
+ try {
2206
+ await this.$nymph.tilmeld?.fillSession(this);
2207
+ } catch (e: any) {
2208
+ return {
2209
+ result: true,
2210
+ loggedin: false,
2211
+ message: `You are registered, but an error occurred logging you in: ${e.message}`,
2212
+ };
2213
+ }
2214
+ }
2215
+
2216
+ return result;
2296
2217
  } catch (e: any) {
2297
- throw e;
2218
+ return { result: false, loggedin: false, message: e.message };
2298
2219
  }
2299
-
2300
- return {
2301
- result: true,
2302
- loggedin,
2303
- message,
2304
- };
2305
2220
  }
2306
2221
 
2307
2222
  public async $save() {
2308
2223
  let tilmeld = enforceTilmeld(this);
2309
2224
  if (this.$data.username == null || !this.$data.username.trim().length) {
2310
- return false;
2225
+ throw new BadUsernameError('Username must not be empty.');
2311
2226
  }
2312
2227
 
2313
2228
  if (
@@ -2591,208 +2506,167 @@ export default class User extends AbleObject<UserData> {
2591
2506
  throw new BadDataError('A password is required.');
2592
2507
  }
2593
2508
 
2594
- // Start transaction.
2595
- const transaction = 'tilmeld-save-user-' + (this.guid || nanoid());
2596
- const nymph = this.$nymph;
2597
- const tnymph = await this.$nymph.startTransaction(transaction);
2598
- this.$setNymph(tnymph);
2599
- tilmeld = enforceTilmeld(this);
2600
- const Group = tnymph.getEntityClass(GroupClass);
2509
+ let preGuid = this.guid;
2510
+ let preCdate = this.cdate;
2511
+ let preMdate = this.mdate;
2512
+ return await transaction(
2513
+ this.$nymph,
2514
+ 'tilmeld-save-user-' + (this.guid || nanoid()),
2515
+ async (nymph) => {
2516
+ this.$setNymph(nymph);
2517
+ const tilmeld = enforceTilmeld(this);
2518
+ const Group = this.$nymph.getEntityClass(GroupClass);
2601
2519
 
2602
- try {
2603
- let group = this.$data.group;
2604
- if (group != null) {
2605
- // Make sure to get fresh group data.
2606
- if (group.$asleep()) {
2607
- await group.$wake();
2608
- } else {
2609
- if (!(await group.$refresh())) {
2610
- throw new Error("Couldn't retrieve primary group info.");
2520
+ let group = this.$data.group;
2521
+ if (group != null) {
2522
+ // Make sure to get fresh group data.
2523
+ if (group.$asleep()) {
2524
+ await group.$wake();
2525
+ } else {
2526
+ if (!(await group.$refresh())) {
2527
+ throw new Error("Couldn't retrieve primary group info.");
2528
+ }
2611
2529
  }
2612
2530
  }
2613
- }
2614
- if (group == null && this.guid == null) {
2615
- if (tilmeld.config.generatePrimary) {
2616
- // Generate a new primary group for the user.
2617
- group = await Group.factory();
2618
- group.user = this;
2619
- const parent = await tnymph.getEntity(
2620
- { class: Group },
2621
- {
2622
- type: '&',
2623
- equal: ['defaultPrimary', true],
2624
- },
2625
- );
2626
- if (parent != null) {
2627
- group.parent = parent;
2628
- }
2629
- } else {
2630
- // Add the default primary.
2631
- const group = await tnymph.getEntity(
2632
- { class: Group },
2633
- {
2634
- type: '&',
2635
- equal: ['defaultPrimary', true],
2636
- },
2637
- );
2638
- if (group != null) {
2639
- this.$data.group = group;
2531
+ if (group == null && this.guid == null) {
2532
+ if (tilmeld.config.generatePrimary) {
2533
+ // Generate a new primary group for the user.
2534
+ group = await Group.factory();
2535
+ group.user = this;
2536
+ const parent = await this.$nymph.getEntity(
2537
+ { class: Group },
2538
+ { type: '&', equal: ['defaultPrimary', true] },
2539
+ );
2540
+ if (parent != null) {
2541
+ group.parent = parent;
2542
+ }
2543
+ } else {
2544
+ // Add the default primary.
2545
+ const group = await this.$nymph.getEntity(
2546
+ { class: Group },
2547
+ { type: '&', equal: ['defaultPrimary', true] },
2548
+ );
2549
+ if (group != null) {
2550
+ this.$data.group = group;
2551
+ }
2640
2552
  }
2641
2553
  }
2642
- }
2643
- if (group != null && group.user != null && this.$is(group.user)) {
2644
- // Update the user's generated primary group.
2645
- let changed = false;
2646
- if (group.groupname !== this.$data.username) {
2647
- group.groupname = this.$data.username;
2648
- changed = true;
2649
- }
2650
- if (group.avatar !== this.$data.avatar) {
2651
- group.avatar = this.$data.avatar;
2652
- changed = true;
2653
- }
2654
- if (
2655
- tilmeld.config.userFields.includes('email') &&
2656
- group.email !== this.$data.email
2657
- ) {
2658
- group.email = this.$data.email;
2659
- changed = true;
2660
- }
2661
- if (
2662
- tilmeld.config.userFields.includes('name') &&
2663
- group.name !== this.$data.name
2664
- ) {
2665
- group.name = this.$data.name;
2666
- changed = true;
2667
- }
2668
- if (
2669
- tilmeld.config.userFields.includes('phone') &&
2670
- group.phone !== this.$data.phone
2671
- ) {
2672
- group.phone = this.$data.phone;
2673
- changed = true;
2674
- }
2675
- if (changed || group.guid == null) {
2676
- if (!(await group.$saveSkipAC())) {
2677
- throw Error('Error updating primary group for user.');
2554
+ if (group != null && group.user != null && this.$is(group.user)) {
2555
+ // Update the user's generated primary group.
2556
+ let changed = false;
2557
+ if (group.groupname !== this.$data.username) {
2558
+ group.groupname = this.$data.username;
2559
+ changed = true;
2560
+ }
2561
+ if (group.avatar !== this.$data.avatar) {
2562
+ group.avatar = this.$data.avatar;
2563
+ changed = true;
2564
+ }
2565
+ if (
2566
+ tilmeld.config.userFields.includes('email') &&
2567
+ group.email !== this.$data.email
2568
+ ) {
2569
+ group.email = this.$data.email;
2570
+ changed = true;
2678
2571
  }
2572
+ if (
2573
+ tilmeld.config.userFields.includes('name') &&
2574
+ group.name !== this.$data.name
2575
+ ) {
2576
+ group.name = this.$data.name;
2577
+ changed = true;
2578
+ }
2579
+ if (
2580
+ tilmeld.config.userFields.includes('phone') &&
2581
+ group.phone !== this.$data.phone
2582
+ ) {
2583
+ group.phone = this.$data.phone;
2584
+ changed = true;
2585
+ }
2586
+ if (changed || group.guid == null) {
2587
+ await group.$saveSkipAC();
2588
+ }
2589
+ this.$data.group = group;
2679
2590
  }
2680
- this.$data.group = group;
2681
- }
2682
2591
 
2683
- if (this.$data.groups == null || this.$data.groups.length === 0) {
2684
- // Add secondary groups.
2685
- if (
2686
- tilmeld.config.userFields.includes('email') &&
2687
- tilmeld.config.verifyEmail &&
2688
- tilmeld.config.unverifiedAccess &&
2689
- this.$data.secret != null
2690
- ) {
2691
- // Add the default secondaries for unverified users.
2692
- this.$data.groups = await tnymph.getEntities(
2693
- { class: Group },
2694
- {
2695
- type: '&',
2696
- equal: ['unverifiedSecondary', true],
2697
- },
2698
- );
2699
- } else {
2700
- // Add the default secondaries.
2701
- this.$data.groups = await tnymph.getEntities(
2702
- { class: Group },
2703
- {
2704
- type: '&',
2705
- equal: ['defaultSecondary', true],
2706
- },
2707
- );
2592
+ if (this.$data.groups == null || this.$data.groups.length === 0) {
2593
+ // Add secondary groups.
2594
+ if (
2595
+ tilmeld.config.userFields.includes('email') &&
2596
+ tilmeld.config.verifyEmail &&
2597
+ tilmeld.config.unverifiedAccess &&
2598
+ this.$data.secret != null
2599
+ ) {
2600
+ // Add the default secondaries for unverified users.
2601
+ this.$data.groups = await this.$nymph.getEntities(
2602
+ { class: Group },
2603
+ { type: '&', equal: ['unverifiedSecondary', true] },
2604
+ );
2605
+ } else {
2606
+ // Add the default secondaries.
2607
+ this.$data.groups = await this.$nymph.getEntities(
2608
+ { class: Group },
2609
+ { type: '&', equal: ['defaultSecondary', true] },
2610
+ );
2611
+ }
2708
2612
  }
2709
- }
2710
2613
 
2711
- try {
2712
- tilmeld.config.validatorUser(tilmeld, this);
2713
- } catch (e: any) {
2714
- throw new BadDataError(e?.message);
2715
- }
2716
- } catch (e: any) {
2717
- await tnymph.rollback(transaction);
2718
- this.$setNymph(nymph);
2719
- throw e;
2720
- }
2721
-
2722
- let ret = false;
2723
- let preGuid = this.guid;
2724
- let preCdate = this.cdate;
2725
- let preMdate = this.mdate;
2614
+ try {
2615
+ tilmeld.config.validatorUser(tilmeld, this);
2616
+ } catch (e: any) {
2617
+ throw new BadDataError(e?.message);
2618
+ }
2726
2619
 
2727
- try {
2728
- for (let callback of (this.constructor as typeof User)
2729
- .beforeSaveCallbacks) {
2730
- if (callback) {
2731
- await callback(this);
2620
+ for (let callback of (this.constructor as typeof User)
2621
+ .beforeSaveCallbacks) {
2622
+ if (callback) {
2623
+ await callback(this);
2624
+ }
2732
2625
  }
2733
- }
2734
2626
 
2735
- ret = await super.$save();
2736
- } catch (e: any) {
2737
- await tnymph.rollback(transaction);
2738
- this.guid = preGuid;
2739
- this.cdate = preCdate;
2740
- this.mdate = preMdate;
2741
- this.$setNymph(nymph);
2742
- throw e;
2743
- }
2744
-
2745
- if (ret) {
2746
- if (sendVerification) {
2747
- // The email has changed, so send a new verification email.
2748
- if (!(await this.$sendEmailVerification())) {
2749
- await tnymph.rollback(transaction);
2750
- this.guid = preGuid;
2751
- this.cdate = preCdate;
2752
- this.mdate = preMdate;
2753
- this.$setNymph(nymph);
2754
- throw new Error("Couldn't send verification email.");
2627
+ await super.$save();
2628
+
2629
+ if (sendVerification) {
2630
+ // The email has changed, so send a new verification email.
2631
+ if (!(await this.$sendEmailVerification())) {
2632
+ throw new Error("Couldn't send verification email.");
2633
+ }
2755
2634
  }
2756
- }
2757
2635
 
2758
- this.$descendantGroups = undefined;
2759
- this.$gatekeeperCache = undefined;
2636
+ this.$descendantGroups = undefined;
2637
+ this.$gatekeeperCache = undefined;
2760
2638
 
2761
- try {
2762
2639
  for (let callback of (this.constructor as typeof User)
2763
2640
  .afterSaveCallbacks) {
2764
2641
  if (callback) {
2765
2642
  await callback(this);
2766
2643
  }
2767
2644
  }
2768
- } catch (e: any) {
2769
- await tnymph.rollback(transaction);
2770
- this.guid = preGuid;
2771
- this.cdate = preCdate;
2772
- this.mdate = preMdate;
2645
+ },
2646
+ async (nymph, committed) => {
2773
2647
  this.$setNymph(nymph);
2774
- throw e;
2775
- }
2776
-
2777
- ret = await tnymph.commit(transaction);
2778
- } else {
2779
- await tnymph.rollback(transaction);
2780
- }
2781
- this.$setNymph(nymph);
2782
-
2783
- if (ret) {
2784
- this.$originalEmail = this.$data.email;
2785
- this.$originalUsername = this.$data.username;
2786
2648
 
2787
- const tilmeld = enforceTilmeld(nymph);
2649
+ if (committed) {
2650
+ this.$originalEmail = this.$data.email;
2651
+ this.$originalUsername = this.$data.username;
2788
2652
 
2789
- if (tilmeld.User.current(true).$is(this)) {
2790
- // Update the user in the session cache.
2791
- await tilmeld.fillSession(this);
2792
- }
2793
- }
2653
+ try {
2654
+ const tilmeld = enforceTilmeld(nymph);
2794
2655
 
2795
- return ret;
2656
+ if (tilmeld.User.current(true).$is(this)) {
2657
+ // Update the user in the session cache.
2658
+ await tilmeld.fillSession(this);
2659
+ }
2660
+ } catch (e: any) {
2661
+ // Ignore.
2662
+ }
2663
+ } else {
2664
+ this.guid = preGuid;
2665
+ this.cdate = preCdate;
2666
+ this.mdate = preMdate;
2667
+ }
2668
+ },
2669
+ );
2796
2670
  }
2797
2671
 
2798
2672
  /**
@@ -2800,7 +2674,7 @@ export default class User extends AbleObject<UserData> {
2800
2674
  */
2801
2675
  public async $saveSkipAC() {
2802
2676
  this.$skipAcWhenSaving = true;
2803
- return await this.$save();
2677
+ await this.$save();
2804
2678
  }
2805
2679
 
2806
2680
  public $tilmeldSaveSkipAC() {
@@ -2811,6 +2685,7 @@ export default class User extends AbleObject<UserData> {
2811
2685
  return false;
2812
2686
  }
2813
2687
 
2688
+ @transactional
2814
2689
  public async $delete() {
2815
2690
  const tilmeld = enforceTilmeld(this);
2816
2691
  if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
@@ -2833,79 +2708,53 @@ export default class User extends AbleObject<UserData> {
2833
2708
  );
2834
2709
  }
2835
2710
 
2836
- const transaction = 'tilmeld-delete-user-' + this.guid;
2837
- const nymph = this.$nymph;
2838
- const tnymph = await nymph.startTransaction(transaction);
2839
- this.$setNymph(tnymph);
2840
-
2841
- let success = false;
2842
-
2843
- try {
2844
- if (!(await this.$refresh())) {
2845
- throw new BadDataError('User could not be refreshed.');
2846
- }
2711
+ if (!(await this.$refresh())) {
2712
+ throw new BadDataError('User could not be refreshed.');
2713
+ }
2847
2714
 
2848
- if (this.$data.group != null) {
2849
- if (this.$data.group.$asleep()) {
2850
- await this.$data.group.$wake();
2851
- } else {
2852
- if (!(await this.$data.group.$refresh())) {
2853
- throw new BadDataError('Group could not be refreshed.');
2854
- }
2715
+ if (this.$data.group != null) {
2716
+ if (this.$data.group.$asleep()) {
2717
+ await this.$data.group.$wake();
2718
+ } else {
2719
+ if (!(await this.$data.group.$refresh())) {
2720
+ throw new BadDataError('Group could not be refreshed.');
2855
2721
  }
2856
2722
  }
2723
+ }
2857
2724
 
2858
- for (let callback of (this.constructor as typeof User)
2859
- .beforeDeleteCallbacks) {
2860
- if (callback) {
2861
- await callback(this);
2862
- }
2725
+ for (let callback of (this.constructor as typeof User)
2726
+ .beforeDeleteCallbacks) {
2727
+ if (callback) {
2728
+ await callback(this);
2863
2729
  }
2730
+ }
2864
2731
 
2865
- if (tilmeld.User.current(true).$is(this)) {
2866
- await this.$logout();
2867
- }
2732
+ if (tilmeld.User.current(true).$is(this)) {
2733
+ await this.$logout();
2734
+ }
2868
2735
 
2869
- if (this.$data.group != null && this.$is(this.$data.group.user)) {
2870
- // Read the skip ac value here, because super.$delete changes it.
2871
- const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
2872
-
2873
- // Delete the user.
2874
- success = await super.$delete();
2875
- if (success) {
2876
- // Delete the generated group.
2877
- success = $skipAcWhenDeleting
2878
- ? await this.$data.group.$deleteSkipAC()
2879
- : await this.$data.group.$delete();
2880
- }
2736
+ if (this.$data.group != null && this.$is(this.$data.group.user)) {
2737
+ // Read the skip ac value here, because super.$delete changes it.
2738
+ const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
2739
+
2740
+ // Delete the user.
2741
+ await super.$delete();
2742
+ // Delete the generated group.
2743
+ if ($skipAcWhenDeleting) {
2744
+ await this.$data.group.$deleteSkipAC();
2881
2745
  } else {
2882
- success = await super.$delete();
2746
+ await this.$data.group.$delete();
2883
2747
  }
2884
- } catch (e: any) {
2885
- await tnymph.rollback(transaction);
2886
- this.$setNymph(nymph);
2887
- throw e;
2748
+ } else {
2749
+ await super.$delete();
2888
2750
  }
2889
2751
 
2890
- if (success) {
2891
- try {
2892
- for (let callback of (this.constructor as typeof User)
2893
- .afterDeleteCallbacks) {
2894
- if (callback) {
2895
- await callback(this);
2896
- }
2897
- }
2898
- } catch (e: any) {
2899
- await tnymph.rollback(transaction);
2900
- this.$setNymph(nymph);
2901
- throw e;
2752
+ for (let callback of (this.constructor as typeof User)
2753
+ .afterDeleteCallbacks) {
2754
+ if (callback) {
2755
+ await callback(this);
2902
2756
  }
2903
- success = await tnymph.commit(transaction);
2904
- } else {
2905
- await tnymph.rollback(transaction);
2906
2757
  }
2907
- this.$setNymph(nymph);
2908
- return success;
2909
2758
  }
2910
2759
 
2911
2760
  /*
@@ -2913,7 +2762,7 @@ export default class User extends AbleObject<UserData> {
2913
2762
  */
2914
2763
  async $deleteSkipAC() {
2915
2764
  this.$skipAcWhenDeleting = true;
2916
- return await this.$delete();
2765
+ await this.$delete();
2917
2766
  }
2918
2767
 
2919
2768
  $tilmeldDeleteSkipAC() {