@nymphjs/tilmeld 1.0.0-beta.120 → 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.
Files changed (47) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +6 -3
  3. package/dist/Group.d.ts +5 -6
  4. package/dist/Group.js +83 -50
  5. package/dist/Group.js.map +1 -1
  6. package/dist/Group.testArtifacts.d.ts +3 -0
  7. package/dist/Group.testArtifacts.js +120 -0
  8. package/dist/Group.testArtifacts.js.map +1 -0
  9. package/dist/User.d.ts +5 -6
  10. package/dist/User.js +86 -84
  11. package/dist/User.js.map +1 -1
  12. package/dist/User.testArtifacts.d.ts +3 -0
  13. package/dist/User.testArtifacts.js +175 -0
  14. package/dist/User.testArtifacts.js.map +1 -0
  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 +6 -2
  28. package/dist/testArtifacts.js +1254 -1101
  29. package/dist/testArtifacts.js.map +1 -1
  30. package/package.json +13 -7
  31. package/src/Group.testArtifacts.ts +159 -0
  32. package/src/Group.ts +93 -68
  33. package/src/User.testArtifacts.ts +226 -0
  34. package/src/User.ts +97 -95
  35. package/src/decorators/index.ts +2 -0
  36. package/src/decorators/requireAbility.ts +26 -0
  37. package/src/decorators/requireAuthentication.ts +19 -0
  38. package/src/index.ts +1 -0
  39. package/src/testArtifacts.ts +1640 -1508
  40. package/dist/Group.test.d.ts +0 -1
  41. package/dist/Group.test.js +0 -121
  42. package/dist/Group.test.js.map +0 -1
  43. package/dist/User.test.d.ts +0 -1
  44. package/dist/User.test.js +0 -171
  45. package/dist/User.test.js.map +0 -1
  46. package/src/Group.test.ts +0 -159
  47. package/src/User.test.ts +0 -217
package/src/Group.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import crypto from 'node:crypto';
2
- import type {
3
- Nymph,
4
- EntityData,
5
- EntityJson,
6
- EntityPatch,
7
- Options,
8
- Selector,
9
- SerializedEntityData,
2
+ import {
3
+ type Nymph,
4
+ type EntityData,
5
+ type EntityJson,
6
+ type EntityPatch,
7
+ type Options,
8
+ type Selector,
9
+ type SerializedEntityData,
10
+ MethodFailedError,
10
11
  } from '@nymphjs/nymph';
11
12
  import { nanoid } from '@nymphjs/guid';
12
13
  import { difference, xor } from 'lodash-es';
@@ -310,18 +311,6 @@ export default class Group extends AbleObject<GroupData> {
310
311
  this.$updateDataProtection();
311
312
  }
312
313
 
313
- $setNymph(nymph: Nymph) {
314
- this.$nymph = nymph;
315
- if (!this.$asleep()) {
316
- if (this.$data.user && this.$data.user.$nymph !== nymph) {
317
- this.$data.user.$setNymph(nymph);
318
- }
319
- if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
320
- this.$data.parent.$setNymph(nymph);
321
- }
322
- }
323
- }
324
-
325
314
  async $getUniques(): Promise<string[]> {
326
315
  const tilmeld = enforceTilmeld(this);
327
316
  const uniques = [`u:${this.$data.groupname}`];
@@ -845,7 +834,7 @@ export default class Group extends AbleObject<GroupData> {
845
834
  public async $save() {
846
835
  let tilmeld = enforceTilmeld(this);
847
836
  if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
848
- return false;
837
+ throw new BadUsernameError('Groupname must not be empty.');
849
838
  }
850
839
 
851
840
  // Lowercase the domain part.
@@ -1007,11 +996,13 @@ export default class Group extends AbleObject<GroupData> {
1007
996
  );
1008
997
  if (currentPrimary != null && !this.$is(currentPrimary)) {
1009
998
  currentPrimary.defaultPrimary = false;
1010
- if (
1011
- this.$skipAcWhenSaving
1012
- ? !(await currentPrimary.$saveSkipAC())
1013
- : !(await currentPrimary.$save())
1014
- ) {
999
+ try {
1000
+ if (this.$skipAcWhenSaving) {
1001
+ await currentPrimary.$saveSkipAC();
1002
+ } else {
1003
+ await currentPrimary.$save();
1004
+ }
1005
+ } catch (e: any) {
1015
1006
  throw new CouldNotChangeDefaultPrimaryGroupError(
1016
1007
  'Could not change new user primary group from ' +
1017
1008
  `${currentPrimary.groupname}.`,
@@ -1038,27 +1029,43 @@ export default class Group extends AbleObject<GroupData> {
1038
1029
  }
1039
1030
  }
1040
1031
 
1041
- let ret = await super.$save();
1042
- if (ret) {
1043
- this.$originalGroupname = this.$data.groupname;
1044
- }
1032
+ await super.$save();
1033
+
1034
+ this.$originalGroupname = this.$data.groupname;
1035
+
1045
1036
  for (let callback of (this.constructor as typeof Group)
1046
1037
  .afterSaveCallbacks) {
1047
1038
  if (callback) {
1048
1039
  await callback(this);
1049
1040
  }
1050
1041
  }
1051
- ret = await tnymph.commit(transaction);
1052
- this.$setNymph(nymph);
1053
- return ret;
1054
1042
  } catch (e: any) {
1055
- await tnymph.rollback(transaction);
1043
+ try {
1044
+ await tnymph.rollback(transaction);
1045
+ } catch (e: any) {
1046
+ nymph.config.debugError(
1047
+ 'tilmeld',
1048
+ `Rollback of transaction ${transaction} failed, reason: ${e.message}`,
1049
+ );
1050
+ }
1056
1051
  this.guid = preGuid;
1057
1052
  this.cdate = preCdate;
1058
1053
  this.mdate = preMdate;
1059
1054
  this.$setNymph(nymph);
1060
1055
  throw e;
1061
1056
  }
1057
+
1058
+ let committed = false;
1059
+ try {
1060
+ committed = await tnymph.commit(transaction);
1061
+ } catch (e: any) {
1062
+ committed = false;
1063
+ }
1064
+ this.$setNymph(nymph);
1065
+
1066
+ if (!committed) {
1067
+ throw new MethodFailedError('Transaction could not be committed.');
1068
+ }
1062
1069
  }
1063
1070
 
1064
1071
  /*
@@ -1066,7 +1073,7 @@ export default class Group extends AbleObject<GroupData> {
1066
1073
  */
1067
1074
  public async $saveSkipAC() {
1068
1075
  this.$skipAcWhenSaving = true;
1069
- return await this.$save();
1076
+ await this.$save();
1070
1077
  }
1071
1078
 
1072
1079
  public $tilmeldSaveSkipAC() {
@@ -1109,14 +1116,16 @@ export default class Group extends AbleObject<GroupData> {
1109
1116
  const descendants = await this.$getDescendants();
1110
1117
  if (descendants.length) {
1111
1118
  for (let curGroup of descendants) {
1112
- if (
1113
- this.$skipAcWhenDeleting
1114
- ? !(await curGroup.$deleteSkipAC())
1115
- : !(await curGroup.$delete())
1116
- ) {
1119
+ try {
1120
+ if (this.$skipAcWhenDeleting) {
1121
+ await curGroup.$deleteSkipAC();
1122
+ } else {
1123
+ await curGroup.$delete();
1124
+ }
1125
+ } catch (e: any) {
1117
1126
  await tnymph.rollback(transaction);
1118
1127
  this.$setNymph(nymph);
1119
- return false;
1128
+ throw e;
1120
1129
  }
1121
1130
  }
1122
1131
  }
@@ -1134,14 +1143,16 @@ export default class Group extends AbleObject<GroupData> {
1134
1143
  );
1135
1144
  for (let user of primaryUsers) {
1136
1145
  delete user.group;
1137
- if (
1138
- this.$skipAcWhenDeleting
1139
- ? !(await user.$saveSkipAC())
1140
- : !(await user.$save())
1141
- ) {
1146
+ try {
1147
+ if (this.$skipAcWhenDeleting) {
1148
+ await user.$saveSkipAC();
1149
+ } else {
1150
+ await user.$save();
1151
+ }
1152
+ } catch (e: any) {
1142
1153
  await tnymph.rollback(transaction);
1143
1154
  this.$setNymph(nymph);
1144
- return false;
1155
+ throw e;
1145
1156
  }
1146
1157
  }
1147
1158
 
@@ -1158,38 +1169,52 @@ export default class Group extends AbleObject<GroupData> {
1158
1169
  );
1159
1170
  for (let user of secondaryUsers) {
1160
1171
  user.$delGroup(this);
1161
- if (
1162
- this.$skipAcWhenDeleting
1163
- ? !(await user.$saveSkipAC())
1164
- : !(await user.$save())
1165
- ) {
1172
+ try {
1173
+ if (this.$skipAcWhenDeleting) {
1174
+ await user.$saveSkipAC();
1175
+ } else {
1176
+ await user.$save();
1177
+ }
1178
+ } catch (e: any) {
1166
1179
  await tnymph.rollback(transaction);
1167
1180
  this.$setNymph(nymph);
1168
- return false;
1181
+ throw e;
1169
1182
  }
1170
1183
  }
1171
1184
 
1172
1185
  // Delete the group.
1173
- let success = await super.$delete();
1174
- if (success) {
1175
- for (let callback of (this.constructor as typeof Group)
1176
- .afterDeleteCallbacks) {
1177
- if (callback) {
1178
- await callback(this);
1179
- }
1180
- }
1186
+ await super.$delete();
1181
1187
 
1182
- success = await tnymph.commit(transaction);
1183
- } else {
1184
- await tnymph.rollback(transaction);
1188
+ for (let callback of (this.constructor as typeof Group)
1189
+ .afterDeleteCallbacks) {
1190
+ if (callback) {
1191
+ await callback(this);
1192
+ }
1185
1193
  }
1186
- this.$setNymph(nymph);
1187
- return success;
1188
1194
  } catch (e: any) {
1189
- await tnymph.rollback(transaction);
1195
+ try {
1196
+ await tnymph.rollback(transaction);
1197
+ } catch (e: any) {
1198
+ nymph.config.debugError(
1199
+ 'tilmeld',
1200
+ `Rollback of transaction ${transaction} failed, reason: ${e.message}`,
1201
+ );
1202
+ }
1190
1203
  this.$setNymph(nymph);
1191
1204
  throw e;
1192
1205
  }
1206
+
1207
+ let committed = false;
1208
+ try {
1209
+ committed = await tnymph.commit(transaction);
1210
+ } catch (e: any) {
1211
+ committed = false;
1212
+ }
1213
+ this.$setNymph(nymph);
1214
+
1215
+ if (!committed) {
1216
+ throw new MethodFailedError('Transaction could not be committed.');
1217
+ }
1193
1218
  }
1194
1219
 
1195
1220
  /*
@@ -1197,7 +1222,7 @@ export default class Group extends AbleObject<GroupData> {
1197
1222
  */
1198
1223
  async $deleteSkipAC() {
1199
1224
  this.$skipAcWhenDeleting = true;
1200
- return await this.$delete();
1225
+ await this.$delete();
1201
1226
  }
1202
1227
 
1203
1228
  $tilmeldDeleteSkipAC() {
@@ -0,0 +1,226 @@
1
+ import { type Nymph, EntityUniqueConstraintError } from '@nymphjs/nymph';
2
+
3
+ import type Tilmeld from './Tilmeld.js';
4
+ import defaults from './conf/defaults.js';
5
+ import { AccessControlError } from './errors/index.js';
6
+
7
+ export function UserTest(
8
+ nymph: Nymph,
9
+ tilmeld: Tilmeld,
10
+ describe: (name: string, fn: () => void) => void,
11
+ it: (name: string, fn: () => void) => void,
12
+ ) {
13
+ const User = tilmeld.User;
14
+ const Group = tilmeld.Group;
15
+
16
+ describe('User', () => {
17
+ beforeAll(async () => {
18
+ // Ensure the tables exist.
19
+ try {
20
+ await User.factoryUsername('user');
21
+ await Group.factoryGroupname('group');
22
+ } catch (e) {
23
+ // ignore
24
+ }
25
+ });
26
+
27
+ async function makeNewUser() {
28
+ const newUser = await User.factory();
29
+
30
+ newUser.username = 'new-user';
31
+ newUser.email = 'newuser@localhost';
32
+ newUser.nameFirst = 'New';
33
+ newUser.nameLast = 'User';
34
+ newUser.name = 'New User';
35
+ newUser.$password('password');
36
+
37
+ return newUser;
38
+ }
39
+
40
+ it('new user passes validation', async () => {
41
+ const validUser = await makeNewUser();
42
+
43
+ expect(() => {
44
+ defaults.validatorUser({ config: defaults } as Tilmeld, validUser);
45
+ }).not.toThrow();
46
+ });
47
+
48
+ it("doesn't allow unknown keys", async () => {
49
+ const invalidUser = await makeNewUser();
50
+
51
+ // @ts-ignore: testing unknown keys
52
+ invalidUser.unknown = 'I should cause an error.';
53
+
54
+ expect(() => {
55
+ defaults.validatorUser({ config: defaults } as Tilmeld, invalidUser);
56
+ }).toThrow('Invalid User: "unknown" is not allowed');
57
+ });
58
+
59
+ it("doesn't allow duplicate usernames", async () => {
60
+ tilmeld.config.generatePrimary = false;
61
+ class NoUsernameCheckUser extends User {
62
+ public async $checkUsername() {
63
+ return {
64
+ result: true,
65
+ message: 'No problem here!',
66
+ };
67
+ }
68
+ }
69
+
70
+ const newUserA = await NoUsernameCheckUser.factory();
71
+
72
+ newUserA.username = 'new-user';
73
+ newUserA.email = 'newusera@localhost';
74
+ newUserA.nameFirst = 'New';
75
+ newUserA.nameLast = 'User';
76
+ newUserA.name = 'New User';
77
+ newUserA.$password('password');
78
+
79
+ await newUserA.$saveSkipAC();
80
+
81
+ const newUserB = await NoUsernameCheckUser.factory();
82
+
83
+ newUserB.username = 'new-user';
84
+ newUserB.email = 'newuserb@localhost';
85
+ newUserB.nameFirst = 'New';
86
+ newUserB.nameLast = 'User';
87
+ newUserB.name = 'New User';
88
+ newUserB.$password('password');
89
+
90
+ try {
91
+ await newUserB.$saveSkipAC();
92
+
93
+ throw new Error("Shouldn't get here because of unique constraint.");
94
+ } catch (e: any) {
95
+ expect(e).toBeInstanceOf(EntityUniqueConstraintError);
96
+ }
97
+
98
+ await newUserA.$deleteSkipAC();
99
+
100
+ tilmeld.config.generatePrimary = true;
101
+ });
102
+
103
+ it("doesn't allow duplicate emails", async () => {
104
+ tilmeld.config.generatePrimary = false;
105
+ class NoEmailCheckUser extends User {
106
+ public async $checkEmail() {
107
+ return {
108
+ result: true,
109
+ message: 'No problem here!',
110
+ };
111
+ }
112
+ }
113
+
114
+ const newUserA = await NoEmailCheckUser.factory();
115
+
116
+ newUserA.username = 'new-user-a';
117
+ newUserA.email = 'newuser@localhost';
118
+ newUserA.nameFirst = 'New';
119
+ newUserA.nameLast = 'User';
120
+ newUserA.name = 'New User';
121
+ newUserA.$password('password');
122
+
123
+ await newUserA.$saveSkipAC();
124
+
125
+ const newUserB = await NoEmailCheckUser.factory();
126
+
127
+ newUserB.username = 'new-user-b';
128
+ newUserB.email = 'newuser@localhost';
129
+ newUserB.nameFirst = 'New';
130
+ newUserB.nameLast = 'User';
131
+ newUserB.name = 'New User';
132
+ newUserB.$password('password');
133
+
134
+ try {
135
+ await newUserB.$saveSkipAC();
136
+
137
+ throw new Error("Shouldn't get here because of unique constraint.");
138
+ } catch (e: any) {
139
+ expect(e).toBeInstanceOf(EntityUniqueConstraintError);
140
+ }
141
+
142
+ await newUserA.$deleteSkipAC();
143
+
144
+ tilmeld.config.generatePrimary = true;
145
+ });
146
+
147
+ it('allows a domain user', async () => {
148
+ const domainAdmin = await User.factory();
149
+
150
+ domainAdmin.username = 'admin';
151
+ domainAdmin.email = 'admin@localhost';
152
+ domainAdmin.nameFirst = 'Admin';
153
+ domainAdmin.nameLast = 'User';
154
+ domainAdmin.name = 'Admin User';
155
+ domainAdmin.$password('password');
156
+ domainAdmin.$grant('tilmeld/domain/example.com/admin');
157
+
158
+ await domainAdmin.$saveSkipAC();
159
+
160
+ // Log in the domain admin.
161
+ await tilmeld.fillSession(domainAdmin);
162
+
163
+ const domainUser = await User.factory();
164
+
165
+ domainUser.username = 'new-user@example.com';
166
+ domainUser.email = 'newuser@localhost';
167
+ domainUser.nameFirst = 'New';
168
+ domainUser.nameLast = 'User';
169
+ domainUser.name = 'New User';
170
+ domainUser.$password('password');
171
+
172
+ await domainUser.$save();
173
+
174
+ tilmeld.clearSession();
175
+
176
+ for (const user of [domainAdmin, domainUser]) {
177
+ if (user.guid != null) {
178
+ await user.$deleteSkipAC();
179
+ }
180
+ }
181
+ });
182
+
183
+ it('disallows a domain user with no admin permission', async () => {
184
+ const domainAdmin = await User.factory();
185
+
186
+ domainAdmin.username = 'admin';
187
+ domainAdmin.email = 'admin@localhost';
188
+ domainAdmin.nameFirst = 'Admin';
189
+ domainAdmin.nameLast = 'User';
190
+ domainAdmin.name = 'Admin User';
191
+ domainAdmin.$password('password');
192
+ domainAdmin.$grant('tilmeld/domain/example.com/admin');
193
+
194
+ await domainAdmin.$saveSkipAC();
195
+
196
+ // Log in the domain admin.
197
+ await tilmeld.fillSession(domainAdmin);
198
+
199
+ const domainUser = await User.factory();
200
+
201
+ // The wrong domain name.
202
+ domainUser.username = 'new-user@example.net';
203
+ domainUser.email = 'newuser@localhost';
204
+ domainUser.nameFirst = 'New';
205
+ domainUser.nameLast = 'User';
206
+ domainUser.name = 'New User';
207
+ domainUser.$password('password');
208
+
209
+ try {
210
+ await domainUser.$save();
211
+
212
+ throw new Error("Shouldn't get here because of access control.");
213
+ } catch (e: any) {
214
+ expect(e).toBeInstanceOf(AccessControlError);
215
+ }
216
+
217
+ tilmeld.clearSession();
218
+
219
+ for (const user of [domainAdmin, domainUser]) {
220
+ if (user.guid != null) {
221
+ await user.$deleteSkipAC();
222
+ }
223
+ }
224
+ });
225
+ });
226
+ }