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

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.
@@ -95,7 +95,7 @@ export function UserTest(
95
95
  expect(e).toBeInstanceOf(EntityUniqueConstraintError);
96
96
  }
97
97
 
98
- expect(await newUserA.$deleteSkipAC()).toEqual(true);
98
+ await newUserA.$deleteSkipAC();
99
99
 
100
100
  tilmeld.config.generatePrimary = true;
101
101
  });
@@ -139,7 +139,7 @@ export function UserTest(
139
139
  expect(e).toBeInstanceOf(EntityUniqueConstraintError);
140
140
  }
141
141
 
142
- expect(await newUserA.$deleteSkipAC()).toEqual(true);
142
+ await newUserA.$deleteSkipAC();
143
143
 
144
144
  tilmeld.config.generatePrimary = true;
145
145
  });
@@ -155,7 +155,7 @@ export function UserTest(
155
155
  domainAdmin.$password('password');
156
156
  domainAdmin.$grant('tilmeld/domain/example.com/admin');
157
157
 
158
- expect(await domainAdmin.$saveSkipAC()).toEqual(true);
158
+ await domainAdmin.$saveSkipAC();
159
159
 
160
160
  // Log in the domain admin.
161
161
  await tilmeld.fillSession(domainAdmin);
@@ -169,13 +169,13 @@ export function UserTest(
169
169
  domainUser.name = 'New User';
170
170
  domainUser.$password('password');
171
171
 
172
- expect(await domainUser.$save()).toEqual(true);
172
+ await domainUser.$save();
173
173
 
174
174
  tilmeld.clearSession();
175
175
 
176
176
  for (const user of [domainAdmin, domainUser]) {
177
177
  if (user.guid != null) {
178
- expect(await user.$deleteSkipAC()).toEqual(true);
178
+ await user.$deleteSkipAC();
179
179
  }
180
180
  }
181
181
  });
@@ -191,7 +191,7 @@ export function UserTest(
191
191
  domainAdmin.$password('password');
192
192
  domainAdmin.$grant('tilmeld/domain/example.com/admin');
193
193
 
194
- expect(await domainAdmin.$saveSkipAC()).toEqual(true);
194
+ await domainAdmin.$saveSkipAC();
195
195
 
196
196
  // Log in the domain admin.
197
197
  await tilmeld.fillSession(domainAdmin);
@@ -218,7 +218,7 @@ export function UserTest(
218
218
 
219
219
  for (const user of [domainAdmin, domainUser]) {
220
220
  if (user.guid != null) {
221
- expect(await user.$deleteSkipAC()).toEqual(true);
221
+ await user.$deleteSkipAC();
222
222
  }
223
223
  }
224
224
  });
package/src/User.ts CHANGED
@@ -1,11 +1,13 @@
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 Nymph,
4
+ type EntityData,
5
+ type EntityJson,
6
+ type EntityPatch,
7
+ type Selector,
8
+ type SerializedEntityData,
9
+ MethodFailedError,
10
+ HttpError,
9
11
  } from '@nymphjs/nymph';
10
12
  import { humanSecret, nanoid } from '@nymphjs/guid';
11
13
  import type { EmailOptions } from 'email-templates';
@@ -465,7 +467,9 @@ export default class User extends AbleObject<UserData> {
465
467
  // Create a unique secret.
466
468
  getUser.recoverSecret = humanSecret();
467
469
  getUser.recoverSecretDate = Date.now();
468
- if (!(await getUser.$saveSkipAC())) {
470
+ try {
471
+ await getUser.$saveSkipAC();
472
+ } catch (e: any) {
469
473
  return { result: false, message: "Couldn't save recovery secret." };
470
474
  }
471
475
 
@@ -554,13 +558,14 @@ export default class User extends AbleObject<UserData> {
554
558
  user.$password(data.password);
555
559
  delete user.recoverSecret;
556
560
  delete user.recoverSecretDate;
557
- if (await user.$saveSkipAC()) {
561
+ try {
562
+ await user.$saveSkipAC();
558
563
  return {
559
564
  result: true,
560
565
  message:
561
566
  'Your password has been reset. You can now log in using your new password.',
562
567
  };
563
- } else {
568
+ } catch (e: any) {
564
569
  return { result: false, message: 'Error saving new password.' };
565
570
  }
566
571
  }
@@ -675,20 +680,6 @@ export default class User extends AbleObject<UserData> {
675
680
  this.$updateDataProtection();
676
681
  }
677
682
 
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
683
  async $getUniques(): Promise<string[]> {
693
684
  const tilmeld = enforceTilmeld(this);
694
685
  const uniques = [`u:${this.$data.username}`];
@@ -1581,13 +1572,14 @@ export default class User extends AbleObject<UserData> {
1581
1572
  if (data.revokeCurrentTokens) {
1582
1573
  this.$data.revokeTokenDate = Date.now();
1583
1574
  }
1584
- if (await this.$save()) {
1575
+ try {
1576
+ await this.$save();
1585
1577
  if (data.revokeCurrentTokens) {
1586
1578
  const tilmeld = enforceTilmeld(this);
1587
1579
  await tilmeld.login(this, true);
1588
1580
  }
1589
1581
  return { result: true, message: 'Your password has been changed.' };
1590
- } else {
1582
+ } catch (e: any) {
1591
1583
  return { result: false, message: "Couldn't save new password." };
1592
1584
  }
1593
1585
  }
@@ -1639,14 +1631,15 @@ export default class User extends AbleObject<UserData> {
1639
1631
 
1640
1632
  this.$data.revokeTokenDate = Date.now();
1641
1633
 
1642
- if (await this.$save()) {
1634
+ try {
1635
+ await this.$save();
1643
1636
  const tilmeld = enforceTilmeld(this);
1644
1637
  await tilmeld.login(this, true);
1645
1638
  return {
1646
1639
  result: true,
1647
1640
  message: 'You have logged out of all other sessions.',
1648
1641
  };
1649
- } else {
1642
+ } catch (e: any) {
1650
1643
  return { result: false, message: "Couldn't save revocation date." };
1651
1644
  }
1652
1645
  }
@@ -1766,9 +1759,10 @@ export default class User extends AbleObject<UserData> {
1766
1759
 
1767
1760
  this.$data.totpSecret = data.secret;
1768
1761
 
1769
- if (await this.$save()) {
1762
+ try {
1763
+ await this.$save();
1770
1764
  return { result: true, message: 'Your two factor secret is now set.' };
1771
- } else {
1765
+ } catch (e: any) {
1772
1766
  return { result: false, message: "Couldn't save two factor secret." };
1773
1767
  }
1774
1768
  }
@@ -1824,12 +1818,13 @@ export default class User extends AbleObject<UserData> {
1824
1818
 
1825
1819
  delete this.$data.totpSecret;
1826
1820
 
1827
- if (await this.$save()) {
1821
+ try {
1822
+ await this.$save();
1828
1823
  return {
1829
1824
  result: true,
1830
1825
  message: 'Two factor secret has been removed.',
1831
1826
  };
1832
- } else {
1827
+ } catch (e: any) {
1833
1828
  return { result: false, message: "Couldn't remove two factor secret." };
1834
1829
  }
1835
1830
  }
@@ -2198,7 +2193,8 @@ export default class User extends AbleObject<UserData> {
2198
2193
  'tilmeld',
2199
2194
  `Registering new user "${this.$data.username}".`,
2200
2195
  );
2201
- if (await this.$saveSkipAC()) {
2196
+ try {
2197
+ await this.$saveSkipAC();
2202
2198
  this.$nymph.config.debugLog(
2203
2199
  'tilmeld',
2204
2200
  `New user registered "${this.$data.username}".`,
@@ -2266,7 +2262,10 @@ export default class User extends AbleObject<UserData> {
2266
2262
  });
2267
2263
  }
2268
2264
  }
2269
- } else {
2265
+ } catch (e: any) {
2266
+ if (e instanceof HttpError) {
2267
+ throw e;
2268
+ }
2270
2269
  this.$nymph.config.debugError(
2271
2270
  'tilmeld',
2272
2271
  `Error registering new user "${this.$data.username}".`,
@@ -2307,7 +2306,7 @@ export default class User extends AbleObject<UserData> {
2307
2306
  public async $save() {
2308
2307
  let tilmeld = enforceTilmeld(this);
2309
2308
  if (this.$data.username == null || !this.$data.username.trim().length) {
2310
- return false;
2309
+ throw new BadUsernameError('Username must not be empty.');
2311
2310
  }
2312
2311
 
2313
2312
  if (
@@ -2673,9 +2672,7 @@ export default class User extends AbleObject<UserData> {
2673
2672
  changed = true;
2674
2673
  }
2675
2674
  if (changed || group.guid == null) {
2676
- if (!(await group.$saveSkipAC())) {
2677
- throw Error('Error updating primary group for user.');
2678
- }
2675
+ await group.$saveSkipAC();
2679
2676
  }
2680
2677
  this.$data.group = group;
2681
2678
  }
@@ -2719,7 +2716,6 @@ export default class User extends AbleObject<UserData> {
2719
2716
  throw e;
2720
2717
  }
2721
2718
 
2722
- let ret = false;
2723
2719
  let preGuid = this.guid;
2724
2720
  let preCdate = this.cdate;
2725
2721
  let preMdate = this.mdate;
@@ -2732,7 +2728,7 @@ export default class User extends AbleObject<UserData> {
2732
2728
  }
2733
2729
  }
2734
2730
 
2735
- ret = await super.$save();
2731
+ await super.$save();
2736
2732
  } catch (e: any) {
2737
2733
  await tnymph.rollback(transaction);
2738
2734
  this.guid = preGuid;
@@ -2742,45 +2738,41 @@ export default class User extends AbleObject<UserData> {
2742
2738
  throw e;
2743
2739
  }
2744
2740
 
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.");
2755
- }
2756
- }
2757
-
2758
- this.$descendantGroups = undefined;
2759
- this.$gatekeeperCache = undefined;
2760
-
2761
- try {
2762
- for (let callback of (this.constructor as typeof User)
2763
- .afterSaveCallbacks) {
2764
- if (callback) {
2765
- await callback(this);
2766
- }
2767
- }
2768
- } catch (e: any) {
2741
+ if (sendVerification) {
2742
+ // The email has changed, so send a new verification email.
2743
+ if (!(await this.$sendEmailVerification())) {
2769
2744
  await tnymph.rollback(transaction);
2770
2745
  this.guid = preGuid;
2771
2746
  this.cdate = preCdate;
2772
2747
  this.mdate = preMdate;
2773
2748
  this.$setNymph(nymph);
2774
- throw e;
2749
+ throw new Error("Couldn't send verification email.");
2775
2750
  }
2751
+ }
2776
2752
 
2777
- ret = await tnymph.commit(transaction);
2778
- } else {
2753
+ this.$descendantGroups = undefined;
2754
+ this.$gatekeeperCache = undefined;
2755
+
2756
+ try {
2757
+ for (let callback of (this.constructor as typeof User)
2758
+ .afterSaveCallbacks) {
2759
+ if (callback) {
2760
+ await callback(this);
2761
+ }
2762
+ }
2763
+ } catch (e: any) {
2779
2764
  await tnymph.rollback(transaction);
2765
+ this.guid = preGuid;
2766
+ this.cdate = preCdate;
2767
+ this.mdate = preMdate;
2768
+ this.$setNymph(nymph);
2769
+ throw e;
2780
2770
  }
2771
+
2772
+ const committed = await tnymph.commit(transaction);
2781
2773
  this.$setNymph(nymph);
2782
2774
 
2783
- if (ret) {
2775
+ if (committed) {
2784
2776
  this.$originalEmail = this.$data.email;
2785
2777
  this.$originalUsername = this.$data.username;
2786
2778
 
@@ -2790,9 +2782,9 @@ export default class User extends AbleObject<UserData> {
2790
2782
  // Update the user in the session cache.
2791
2783
  await tilmeld.fillSession(this);
2792
2784
  }
2785
+ } else {
2786
+ throw new MethodFailedError('Transaction could not be committed.');
2793
2787
  }
2794
-
2795
- return ret;
2796
2788
  }
2797
2789
 
2798
2790
  /**
@@ -2800,7 +2792,7 @@ export default class User extends AbleObject<UserData> {
2800
2792
  */
2801
2793
  public async $saveSkipAC() {
2802
2794
  this.$skipAcWhenSaving = true;
2803
- return await this.$save();
2795
+ await this.$save();
2804
2796
  }
2805
2797
 
2806
2798
  public $tilmeldSaveSkipAC() {
@@ -2838,8 +2830,6 @@ export default class User extends AbleObject<UserData> {
2838
2830
  const tnymph = await nymph.startTransaction(transaction);
2839
2831
  this.$setNymph(tnymph);
2840
2832
 
2841
- let success = false;
2842
-
2843
2833
  try {
2844
2834
  if (!(await this.$refresh())) {
2845
2835
  throw new BadDataError('User could not be refreshed.');
@@ -2871,15 +2861,15 @@ export default class User extends AbleObject<UserData> {
2871
2861
  const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
2872
2862
 
2873
2863
  // 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();
2864
+ await super.$delete();
2865
+ // Delete the generated group.
2866
+ if ($skipAcWhenDeleting) {
2867
+ await this.$data.group.$deleteSkipAC();
2868
+ } else {
2869
+ await this.$data.group.$delete();
2880
2870
  }
2881
2871
  } else {
2882
- success = await super.$delete();
2872
+ await super.$delete();
2883
2873
  }
2884
2874
  } catch (e: any) {
2885
2875
  await tnymph.rollback(transaction);
@@ -2887,25 +2877,37 @@ export default class User extends AbleObject<UserData> {
2887
2877
  throw e;
2888
2878
  }
2889
2879
 
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
- }
2880
+ try {
2881
+ for (let callback of (this.constructor as typeof User)
2882
+ .afterDeleteCallbacks) {
2883
+ if (callback) {
2884
+ await callback(this);
2897
2885
  }
2898
- } catch (e: any) {
2886
+ }
2887
+ } catch (e: any) {
2888
+ try {
2899
2889
  await tnymph.rollback(transaction);
2900
- this.$setNymph(nymph);
2901
- throw e;
2890
+ } catch (e: any) {
2891
+ nymph.config.debugError(
2892
+ 'tilmeld',
2893
+ `Rollback of transaction ${transaction} failed, reason: ${e.message}`,
2894
+ );
2902
2895
  }
2903
- success = await tnymph.commit(transaction);
2904
- } else {
2905
- await tnymph.rollback(transaction);
2896
+ this.$setNymph(nymph);
2897
+ throw e;
2898
+ }
2899
+
2900
+ let committed = false;
2901
+ try {
2902
+ committed = await tnymph.commit(transaction);
2903
+ } catch (e: any) {
2904
+ committed = false;
2906
2905
  }
2907
2906
  this.$setNymph(nymph);
2908
- return success;
2907
+
2908
+ if (!committed) {
2909
+ throw new MethodFailedError('Transaction could not be committed.');
2910
+ }
2909
2911
  }
2910
2912
 
2911
2913
  /*
@@ -2913,7 +2915,7 @@ export default class User extends AbleObject<UserData> {
2913
2915
  */
2914
2916
  async $deleteSkipAC() {
2915
2917
  this.$skipAcWhenDeleting = true;
2916
- return await this.$delete();
2918
+ await this.$delete();
2917
2919
  }
2918
2920
 
2919
2921
  $tilmeldDeleteSkipAC() {
@@ -0,0 +1,2 @@
1
+ export * from './requireAbility.js';
2
+ export * from './requireAuthentication.js';
@@ -0,0 +1,26 @@
1
+ import { Entity, HttpError } from '@nymphjs/nymph';
2
+
3
+ /**
4
+ * A decorator that checks for an ability before running the method.
5
+ *
6
+ * If the logged in user does not have the given ability, the decorator will
7
+ * cause the method to throw a 403 Forbidden HttpError.
8
+ *
9
+ * @param ability The ability to check for.
10
+ */
11
+ export function requireAbility(ability: string) {
12
+ return function (
13
+ target: (...args: any[]) => any,
14
+ _context: ClassMethodDecoratorContext,
15
+ ) {
16
+ return function (this: Entity, ...args: unknown[]) {
17
+ if (
18
+ this.$nymph.tilmeld == null ||
19
+ !this.$nymph.tilmeld.gatekeeper(ability)
20
+ ) {
21
+ throw new HttpError('Forbidden', 403);
22
+ }
23
+ return target.apply(this, args);
24
+ };
25
+ };
26
+ }
@@ -0,0 +1,19 @@
1
+ import { Entity, HttpError } from '@nymphjs/nymph';
2
+
3
+ /**
4
+ * A decorator that checks for authentication before running the method.
5
+ *
6
+ * If the user is not logged in, the decorator will cause the method to throw a
7
+ * 403 Forbidden HttpError.
8
+ */
9
+ export function requireAuthentication(
10
+ target: (...args: any[]) => any,
11
+ _context: ClassMethodDecoratorContext,
12
+ ) {
13
+ return function (this: Entity, ...args: unknown[]) {
14
+ if (this.$nymph.tilmeld == null || !this.$nymph.tilmeld.gatekeeper()) {
15
+ throw new HttpError('Forbidden', 403);
16
+ }
17
+ return target.apply(this, args);
18
+ };
19
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './conf/index.js';
2
2
 
3
3
  export * from './enforceTilmeld.js';
4
4
 
5
+ export * from './decorators/index.js';
5
6
  export * from './errors/index.js';
6
7
 
7
8
  import AbleObject from './AbleObject.js';