@nymphjs/tilmeld 1.0.0-beta.61 → 1.0.0-beta.62

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/src/Group.ts CHANGED
@@ -1,4 +1,5 @@
1
- import {
1
+ import type {
2
+ Nymph,
2
3
  EntityData,
3
4
  EntityJson,
4
5
  EntityPatch,
@@ -17,7 +18,8 @@ import {
17
18
  BadUsernameError,
18
19
  CouldNotChangeDefaultPrimaryGroupError,
19
20
  } from './errors';
20
- import User, { UserData } from './User';
21
+ import type User from './User';
22
+ import type { UserData } from './User';
21
23
 
22
24
  export type GroupData = {
23
25
  /**
@@ -110,9 +112,13 @@ export default class Group extends AbleObject<GroupData> {
110
112
  protected $allowlistTags?: string[] = [];
111
113
 
112
114
  /**
113
- * This is explicitly used only during the registration proccess.
115
+ * This should only be used by the backend.
114
116
  */
115
117
  private $skipAcWhenSaving = false;
118
+ /**
119
+ * This should only be used by the backend.
120
+ */
121
+ private $skipAcWhenDeleting = false;
116
122
 
117
123
  static async factoryGroupname(
118
124
  groupname?: string,
@@ -135,15 +141,6 @@ export default class Group extends AbleObject<GroupData> {
135
141
  return entity;
136
142
  }
137
143
 
138
- constructor() {
139
- super();
140
-
141
- // Defaults.
142
- this.$data.enabled = true;
143
- this.$data.abilities = [];
144
- this.$updateDataProtection();
145
- }
146
-
147
144
  /**
148
145
  * Get all the groups that can be assigned as primary groups.
149
146
  *
@@ -229,6 +226,42 @@ export default class Group extends AbleObject<GroupData> {
229
226
  return assignableGroups;
230
227
  }
231
228
 
229
+ constructor() {
230
+ super();
231
+
232
+ // Defaults.
233
+ this.$data.enabled = true;
234
+ this.$data.abilities = [];
235
+ this.$updateDataProtection();
236
+ }
237
+
238
+ $setNymph(nymph: Nymph) {
239
+ this.$nymph = nymph;
240
+ if (!this.$asleep()) {
241
+ if (this.$data.user && this.$data.user.$nymph !== nymph) {
242
+ this.$data.user.$setNymph(nymph);
243
+ }
244
+ if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
245
+ this.$data.parent.$setNymph(nymph);
246
+ }
247
+ }
248
+ }
249
+
250
+ async $getUniques(): Promise<string[]> {
251
+ const tilmeld = enforceTilmeld(this);
252
+ const uniques = [`u:${this.$data.groupname}`];
253
+ if (
254
+ !tilmeld.config.emailUsernames &&
255
+ tilmeld.config.userFields.includes('email')
256
+ ) {
257
+ uniques.push(`e:${this.$data.email}`);
258
+ }
259
+ if (this.$data.defaultPrimary) {
260
+ uniques.push(`o:default-primary`);
261
+ }
262
+ return uniques;
263
+ }
264
+
232
265
  public $getAvatar() {
233
266
  if (this.$data.avatar != null && this.$data.avatar !== '') {
234
267
  return this.$data.avatar;
@@ -768,7 +801,11 @@ export default class Group extends AbleObject<GroupData> {
768
801
  );
769
802
  if (currentPrimary != null && !this.$is(currentPrimary)) {
770
803
  currentPrimary.defaultPrimary = false;
771
- if (!(await currentPrimary.$save())) {
804
+ if (
805
+ this.$skipAcWhenSaving
806
+ ? !(await currentPrimary.$saveSkipAC())
807
+ : !(await currentPrimary.$save())
808
+ ) {
772
809
  throw new CouldNotChangeDefaultPrimaryGroupError(
773
810
  'Could not change new user primary group from ' +
774
811
  `${currentPrimary.groupname}.`,
@@ -798,23 +835,27 @@ export default class Group extends AbleObject<GroupData> {
798
835
 
799
836
  public async $delete() {
800
837
  let tilmeld = enforceTilmeld(this);
801
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
838
+ if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
802
839
  throw new BadDataError("You don't have the authority to delete groups.");
803
840
  }
804
841
 
805
842
  const transaction = 'tilmeld-delete-' + this.guid;
806
843
  const nymph = this.$nymph;
807
844
  const tnymph = await nymph.startTransaction(transaction);
808
- this.$nymph = tnymph;
845
+ this.$setNymph(tnymph);
809
846
  tilmeld = enforceTilmeld(this);
810
847
 
811
848
  // Delete descendants.
812
849
  const descendants = await this.$getDescendants();
813
850
  if (descendants.length) {
814
851
  for (let curGroup of descendants) {
815
- if (!(await curGroup.$delete())) {
852
+ if (
853
+ this.$skipAcWhenDeleting
854
+ ? !(await curGroup.$deleteSkipAC())
855
+ : !(await curGroup.$delete())
856
+ ) {
816
857
  await tnymph.rollback(transaction);
817
- this.$nymph = nymph;
858
+ this.$setNymph(nymph);
818
859
  return false;
819
860
  }
820
861
  }
@@ -833,9 +874,13 @@ export default class Group extends AbleObject<GroupData> {
833
874
  );
834
875
  for (let user of primaryUsers) {
835
876
  delete user.group;
836
- if (!(await user.$save())) {
877
+ if (
878
+ this.$skipAcWhenDeleting
879
+ ? !(await user.$saveSkipAC())
880
+ : !(await user.$save())
881
+ ) {
837
882
  await tnymph.rollback(transaction);
838
- this.$nymph = nymph;
883
+ this.$setNymph(nymph);
839
884
  return false;
840
885
  }
841
886
  }
@@ -853,7 +898,11 @@ export default class Group extends AbleObject<GroupData> {
853
898
  );
854
899
  for (let user of secondaryUsers) {
855
900
  user.$delGroup(this);
856
- if (!(await user.$save())) {
901
+ if (
902
+ this.$skipAcWhenDeleting
903
+ ? !(await user.$saveSkipAC())
904
+ : !(await user.$save())
905
+ ) {
857
906
  await tnymph.rollback(transaction);
858
907
  return false;
859
908
  }
@@ -866,7 +915,23 @@ export default class Group extends AbleObject<GroupData> {
866
915
  } else {
867
916
  await tnymph.rollback(transaction);
868
917
  }
869
- this.$nymph = nymph;
918
+ this.$setNymph(nymph);
870
919
  return success;
871
920
  }
921
+
922
+ /*
923
+ * This should *never* be accessible on the client.
924
+ */
925
+ async $deleteSkipAC() {
926
+ this.$skipAcWhenDeleting = true;
927
+ return await this.$delete();
928
+ }
929
+
930
+ $tilmeldDeleteSkipAC() {
931
+ if (this.$skipAcWhenDeleting) {
932
+ this.$skipAcWhenDeleting = false;
933
+ return true;
934
+ }
935
+ return false;
936
+ }
872
937
  }
package/src/User.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SQLite3Driver } from '@nymphjs/driver-sqlite3';
2
- import { Nymph } from '@nymphjs/nymph';
2
+ import { Nymph, EntityUniqueConstraintError } from '@nymphjs/nymph';
3
3
 
4
4
  import Tilmeld from './Tilmeld';
5
5
  import defaults from './conf/defaults';
@@ -58,4 +58,80 @@ describe('User', () => {
58
58
  defaults.validatorUser({ config: defaults } as Tilmeld, invalidUser);
59
59
  }).toThrow('Invalid User: "unknown" is not allowed');
60
60
  });
61
+
62
+ it("doesn't allow duplicate usernames", async () => {
63
+ const newUserA = await User.factory();
64
+
65
+ newUserA.username = 'new-user';
66
+ newUserA.email = 'newusera@localhost';
67
+ newUserA.nameFirst = 'New';
68
+ newUserA.nameLast = 'User';
69
+ newUserA.name = 'New User';
70
+ newUserA.$password('password');
71
+
72
+ const newUserB = await User.factory();
73
+
74
+ newUserB.username = 'new-user';
75
+ newUserB.email = 'newuserb@localhost';
76
+ newUserB.nameFirst = 'New';
77
+ newUserB.nameLast = 'User';
78
+ newUserB.name = 'New User';
79
+ newUserB.$password('password');
80
+
81
+ const promises = [newUserA.$saveSkipAC(), newUserB.$saveSkipAC()];
82
+
83
+ try {
84
+ await Promise.all(promises);
85
+
86
+ throw new Error("Shouldn't get here because of unique constraint.");
87
+ } catch (e: any) {
88
+ expect(e).toBeInstanceOf(EntityUniqueConstraintError);
89
+ }
90
+
91
+ await Promise.allSettled(promises);
92
+
93
+ for (const user of [newUserA, newUserB]) {
94
+ if (user.guid != null) {
95
+ expect(await user.$deleteSkipAC()).toEqual(true);
96
+ }
97
+ }
98
+ });
99
+
100
+ it("doesn't allow duplicate emails", async () => {
101
+ const newUserA = await User.factory();
102
+
103
+ newUserA.username = 'new-user-a';
104
+ newUserA.email = 'newuser@localhost';
105
+ newUserA.nameFirst = 'New';
106
+ newUserA.nameLast = 'User';
107
+ newUserA.name = 'New User';
108
+ newUserA.$password('password');
109
+
110
+ const newUserB = await User.factory();
111
+
112
+ newUserB.username = 'new-user-b';
113
+ newUserB.email = 'newuser@localhost';
114
+ newUserB.nameFirst = 'New';
115
+ newUserB.nameLast = 'User';
116
+ newUserB.name = 'New User';
117
+ newUserB.$password('password');
118
+
119
+ const promises = [newUserA.$saveSkipAC(), newUserB.$saveSkipAC()];
120
+
121
+ try {
122
+ await Promise.all(promises);
123
+
124
+ throw new Error("Shouldn't get here because of unique constraint.");
125
+ } catch (e: any) {
126
+ expect(e).toBeInstanceOf(EntityUniqueConstraintError);
127
+ }
128
+
129
+ await Promise.allSettled(promises);
130
+
131
+ for (const user of [newUserA, newUserB]) {
132
+ if (user.guid != null) {
133
+ expect(await user.$deleteSkipAC()).toEqual(true);
134
+ }
135
+ }
136
+ });
61
137
  });