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

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 (57) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/README.md +14 -1
  3. package/dist/AbleObject.d.ts +15 -0
  4. package/dist/AbleObject.js +16 -1
  5. package/dist/AbleObject.js.map +1 -1
  6. package/dist/Group.d.ts +119 -8
  7. package/dist/Group.js +230 -97
  8. package/dist/Group.js.map +1 -1
  9. package/dist/Group.test.js +3 -2
  10. package/dist/Group.test.js.map +1 -1
  11. package/dist/Tilmeld.d.ts +237 -9
  12. package/dist/Tilmeld.js +559 -195
  13. package/dist/Tilmeld.js.map +1 -1
  14. package/dist/User.d.ts +333 -14
  15. package/dist/User.js +797 -204
  16. package/dist/User.js.map +1 -1
  17. package/dist/User.test.js +3 -2
  18. package/dist/User.test.js.map +1 -1
  19. package/dist/conf/d.d.ts +250 -3
  20. package/dist/conf/defaults.js +146 -98
  21. package/dist/conf/defaults.js.map +1 -1
  22. package/dist/enforceTilmeld.d.ts +3 -0
  23. package/dist/enforceTilmeld.js +22 -0
  24. package/dist/enforceTilmeld.js.map +1 -0
  25. package/dist/errors/AccessControlError.d.ts +2 -1
  26. package/dist/errors/AccessControlError.js +3 -2
  27. package/dist/errors/AccessControlError.js.map +1 -1
  28. package/dist/errors/BadDataError.d.ts +2 -1
  29. package/dist/errors/BadDataError.js +3 -2
  30. package/dist/errors/BadDataError.js.map +1 -1
  31. package/dist/errors/BadEmailError.d.ts +2 -1
  32. package/dist/errors/BadEmailError.js +3 -2
  33. package/dist/errors/BadEmailError.js.map +1 -1
  34. package/dist/errors/BadUsernameError.d.ts +2 -1
  35. package/dist/errors/BadUsernameError.js +3 -2
  36. package/dist/errors/BadUsernameError.js.map +1 -1
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/package.json +30 -26
  41. package/src/AbleObject.ts +2 -2
  42. package/src/Group.test.ts +3 -3
  43. package/src/Group.ts +194 -143
  44. package/src/Tilmeld.ts +384 -249
  45. package/src/User.test.ts +3 -3
  46. package/src/User.ts +876 -266
  47. package/src/conf/d.ts +26 -13
  48. package/src/conf/defaults.ts +151 -104
  49. package/src/enforceTilmeld.ts +20 -0
  50. package/src/errors/AccessControlError.ts +4 -2
  51. package/src/errors/BadDataError.ts +4 -2
  52. package/src/errors/BadEmailError.ts +4 -2
  53. package/src/errors/BadUsernameError.ts +4 -2
  54. package/src/index.ts +2 -0
  55. package/src/tilmeldJoiProps.ts +9 -9
  56. package/tsconfig.json +1 -1
  57. package/typedoc.json +4 -0
package/dist/User.js CHANGED
@@ -3,18 +3,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const guid_1 = require("@nymphjs/guid");
6
7
  const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
7
8
  const enc_base64_1 = __importDefault(require("crypto-js/enc-base64"));
8
9
  const sha256_1 = __importDefault(require("crypto-js/sha256"));
9
10
  const md5_1 = __importDefault(require("crypto-js/md5"));
10
11
  const lodash_1 = require("lodash");
11
- const guid_1 = require("@nymphjs/guid");
12
+ const otpauth_1 = require("otpauth");
13
+ const qrcode_1 = require("qrcode");
14
+ const enforceTilmeld_1 = require("./enforceTilmeld");
12
15
  const AbleObject_1 = __importDefault(require("./AbleObject"));
13
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
+ */
14
26
  class User extends AbleObject_1.default {
15
- static async factory(guid) {
16
- return (await super.factory(guid));
17
- }
18
27
  static async factoryUsername(username) {
19
28
  const entity = new this();
20
29
  if (username != null) {
@@ -30,32 +39,39 @@ class User extends AbleObject_1.default {
30
39
  }
31
40
  return entity;
32
41
  }
33
- static factorySync(guid) {
34
- return super.factorySync(guid);
35
- }
36
- constructor(guid) {
37
- super(guid);
42
+ constructor() {
43
+ super();
38
44
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
39
45
  this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
40
46
  this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
41
47
  this.$allowlistTags = [];
48
+ /**
49
+ * This is explicitly used only during the registration proccess.
50
+ */
42
51
  this.$skipAcWhenSaving = false;
43
- if (this.guid == null) {
44
- this.$data.enabled = true;
45
- this.$data.abilities = [];
46
- this.$data.groups = [];
47
- this.$data.inheritAbilities = true;
48
- this.$updateDataProtection();
49
- }
52
+ // Defaults.
53
+ this.$data.enabled = true;
54
+ this.$data.abilities = [];
55
+ this.$data.groups = [];
56
+ this.$data.inheritAbilities = true;
57
+ this.$updateDataProtection();
50
58
  }
51
59
  static current(returnObjectIfNotExist) {
52
- if (this.tilmeld.currentUser == null) {
60
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
61
+ if (tilmeld.currentUser == null) {
53
62
  return returnObjectIfNotExist ? this.factorySync() : null;
54
63
  }
55
- return this.tilmeld.currentUser;
64
+ return tilmeld.currentUser;
56
65
  }
66
+ /**
67
+ * Send an account recovery link.
68
+ *
69
+ * @param data The input data from the client.
70
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
71
+ */
57
72
  static async sendRecovery(data) {
58
- if (!this.tilmeld.config.pwRecovery) {
73
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
74
+ if (!tilmeld.config.pwRecovery) {
59
75
  return {
60
76
  result: false,
61
77
  message: 'Account recovery is not allowed.',
@@ -63,9 +79,9 @@ class User extends AbleObject_1.default {
63
79
  }
64
80
  let user;
65
81
  const options = {};
66
- if (!this.tilmeld.config.emailUsernames &&
67
- data.recoveryType === 'username') {
68
- const getUser = await this.nymph.getEntity({ class: this.tilmeld.User, skipAc: true }, {
82
+ if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
83
+ // Create a username recovery email.
84
+ const getUser = await this.nymph.getEntity({ class: tilmeld.User, skipAc: true }, {
69
85
  type: '&',
70
86
  ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
71
87
  });
@@ -75,6 +91,7 @@ class User extends AbleObject_1.default {
75
91
  message: 'Requested account is not accessible.',
76
92
  };
77
93
  }
94
+ // Send the recovery email.
78
95
  user = getUser;
79
96
  options.template = 'RecoverUsername';
80
97
  options.message = {
@@ -86,18 +103,21 @@ class User extends AbleObject_1.default {
86
103
  options.locals = {};
87
104
  }
88
105
  else if (data.recoveryType === 'password') {
89
- const getUser = await this.tilmeld.User.factoryUsername(data.account);
106
+ // Create a password recovery email.
107
+ const getUser = await tilmeld.User.factoryUsername(data.account);
90
108
  if (getUser.guid == null) {
91
109
  return {
92
110
  result: false,
93
111
  message: 'Requested account is not accessible.',
94
112
  };
95
113
  }
114
+ // Create a unique secret.
96
115
  getUser.recoverSecret = (0, guid_1.humanSecret)();
97
116
  getUser.recoverSecretDate = Date.now();
98
117
  if (!(await getUser.$saveSkipAC())) {
99
118
  return { result: false, message: "Couldn't save recovery secret." };
100
119
  }
120
+ // Send the recovery email.
101
121
  user = getUser;
102
122
  options.template = 'RecoverPassword';
103
123
  options.message = {
@@ -108,13 +128,15 @@ class User extends AbleObject_1.default {
108
128
  };
109
129
  options.locals = {
110
130
  recoverCode: getUser.recoverSecret,
111
- timeLimit: this.tilmeld.config.pwRecoveryTimeLimit,
131
+ timeLimit: tilmeld.config.pwRecoveryTimeLimit,
112
132
  };
113
133
  }
114
134
  else {
115
135
  return { result: false, message: 'Invalid recovery type.' };
116
136
  }
117
- if (await this.tilmeld.config.sendEmail(this.tilmeld, options, user)) {
137
+ this.nymph.config.debugLog('tilmeld', `User account recovery (${options.template}) request for "${user.username}".`);
138
+ // Send the email.
139
+ if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
118
140
  return {
119
141
  result: true,
120
142
  message: "We've sent an email to your registered address. Please " +
@@ -125,18 +147,25 @@ class User extends AbleObject_1.default {
125
147
  return { result: false, message: "Couldn't send recovery email." };
126
148
  }
127
149
  }
150
+ /**
151
+ * Recover account details.
152
+ *
153
+ * @param data The input data from the client.
154
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
155
+ */
128
156
  static async recover(data) {
129
- if (!this.tilmeld.config.pwRecovery) {
157
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
158
+ if (!tilmeld.config.pwRecovery) {
130
159
  return {
131
160
  result: false,
132
161
  message: 'Account recovery is not allowed.',
133
162
  };
134
163
  }
135
- const user = await this.tilmeld.User.factoryUsername(data.username);
164
+ const user = await tilmeld.User.factoryUsername(data.username);
136
165
  if (user.guid == null ||
137
166
  user.recoverSecret == null ||
138
167
  data.secret !== user.recoverSecret ||
139
- (0, strtotime_1.default)('+' + this.tilmeld.config.pwRecoveryTimeLimit, Math.floor((user.recoverSecretDate ?? 0) / 1000)) *
168
+ (0, strtotime_1.default)('+' + tilmeld.config.pwRecoveryTimeLimit, Math.floor((user.recoverSecretDate ?? 0) / 1000)) *
140
169
  1000 <
141
170
  Date.now()) {
142
171
  return {
@@ -147,6 +176,7 @@ class User extends AbleObject_1.default {
147
176
  if (!('password' in data) || !data.password.length) {
148
177
  return { result: false, message: 'Password cannot be empty.' };
149
178
  }
179
+ this.nymph.config.debugLog('tilmeld', `User account recovery for "${user.username}".`);
150
180
  user.$password(data.password);
151
181
  delete user.recoverSecret;
152
182
  delete user.recoverSecretDate;
@@ -161,22 +191,24 @@ class User extends AbleObject_1.default {
161
191
  }
162
192
  }
163
193
  static getClientConfig() {
194
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
164
195
  return {
165
- regFields: this.tilmeld.config.regFields,
166
- userFields: this.tilmeld.config.userFields,
167
- emailUsernames: this.tilmeld.config.emailUsernames,
168
- allowRegistration: this.tilmeld.config.allowRegistration,
169
- allowUsernameChange: this.tilmeld.config.allowUsernameChange,
170
- pwRecovery: this.tilmeld.config.pwRecovery,
171
- verifyEmail: this.tilmeld.config.verifyEmail,
172
- unverifiedAccess: this.tilmeld.config.unverifiedAccess,
196
+ regFields: tilmeld.config.regFields,
197
+ userFields: tilmeld.config.userFields,
198
+ emailUsernames: tilmeld.config.emailUsernames,
199
+ allowRegistration: tilmeld.config.allowRegistration,
200
+ allowUsernameChange: tilmeld.config.allowUsernameChange,
201
+ pwRecovery: tilmeld.config.pwRecovery,
202
+ verifyEmail: tilmeld.config.verifyEmail,
203
+ unverifiedAccess: tilmeld.config.unverifiedAccess,
173
204
  };
174
205
  }
175
206
  static async loginUser(data) {
207
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
176
208
  if (!('username' in data) || !data.username.length) {
177
209
  return { result: false, message: 'Incorrect login/password.' };
178
210
  }
179
- const user = await this.tilmeld.User.factoryUsername(data.username);
211
+ const user = await tilmeld.User.factoryUsername(data.username);
180
212
  const result = await user.$login(data);
181
213
  if (result.result) {
182
214
  user.$updateDataProtection();
@@ -185,19 +217,33 @@ class User extends AbleObject_1.default {
185
217
  return result;
186
218
  }
187
219
  async $login(data) {
188
- const tilmeld = this.$nymph.tilmeld;
220
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
189
221
  if (this.guid == null) {
190
222
  return { result: false, message: 'Incorrect login/password.' };
191
223
  }
192
224
  if (!this.$data.enabled) {
193
225
  return { result: false, message: 'This user is disabled.' };
194
226
  }
195
- if (this.$gatekeeper()) {
227
+ if (await this.$gatekeeper()) {
196
228
  return { result: true, message: 'You are already logged in.' };
197
229
  }
198
230
  if (!this.$checkPassword(data.password)) {
199
231
  return { result: false, message: 'Incorrect login/password.' };
200
232
  }
233
+ if (this.$data.totpSecret != null) {
234
+ if (data.code == null ||
235
+ typeof data.code !== 'string' ||
236
+ data.code.length !== 6) {
237
+ return {
238
+ result: false,
239
+ needTOTP: true,
240
+ message: 'You need to provide a 2FA code.',
241
+ };
242
+ }
243
+ if (!this.$checkTOTPCode(data.code)) {
244
+ return { result: false, message: 'Incorrect 2FA code.' };
245
+ }
246
+ }
201
247
  try {
202
248
  for (let callback of this.constructor
203
249
  .beforeLoginCallbacks) {
@@ -212,7 +258,67 @@ class User extends AbleObject_1.default {
212
258
  message: e.message,
213
259
  };
214
260
  }
215
- if (!tilmeld.login(this, true)) {
261
+ this.$nymph.config.debugLog('tilmeld', `User login for "${this.$data.username}".`);
262
+ // Authentication was successful, attempt to login.
263
+ if (!(await tilmeld.login(this, true))) {
264
+ return { result: false, message: 'Incorrect login/password.' };
265
+ }
266
+ for (let callback of this.constructor
267
+ .afterLoginCallbacks) {
268
+ if (callback) {
269
+ await callback(this);
270
+ }
271
+ }
272
+ // Login was successful.
273
+ return { result: true, message: 'You are logged in.' };
274
+ }
275
+ async $switchUser(data) {
276
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
277
+ if (this.guid == null) {
278
+ return { result: false, message: 'Incorrect login/password.' };
279
+ }
280
+ let user = tilmeld.User.current();
281
+ if (!user || user.guid == null) {
282
+ return { result: false, message: "You're not logged in." };
283
+ }
284
+ const isCurrentUser = user != null && user.$is(this);
285
+ if (isCurrentUser) {
286
+ return { result: false, message: "You can't switch to yourself." };
287
+ }
288
+ const toAbilities = this.$data.abilities || [];
289
+ const fromAbilities = user.abilities || [];
290
+ if (!fromAbilities.includes('tilmeld/switch') &&
291
+ !fromAbilities.includes('system/admin')) {
292
+ return {
293
+ result: false,
294
+ message: "You don't have permission to switch users.",
295
+ };
296
+ }
297
+ if (toAbilities.includes('system/admin') ||
298
+ toAbilities.includes('tilmeld/admin')) {
299
+ return { result: false, message: "You can't switch to an admin." };
300
+ }
301
+ try {
302
+ for (let callback of this.constructor
303
+ .beforeLoginCallbacks) {
304
+ if (callback) {
305
+ await callback(this, {
306
+ username: this.$data.username || '',
307
+ password: '',
308
+ ...(data || {}),
309
+ });
310
+ }
311
+ }
312
+ }
313
+ catch (e) {
314
+ return {
315
+ result: false,
316
+ message: e.message,
317
+ };
318
+ }
319
+ this.$nymph.config.debugLog('tilmeld', `User login switch for "${user.username}" to "${this.$data.username}".`);
320
+ // Authentication was successful, attempt to login.
321
+ if (!(await tilmeld.loginSwitch(this, true))) {
216
322
  return { result: false, message: 'Incorrect login/password.' };
217
323
  }
218
324
  for (let callback of this.constructor
@@ -221,10 +327,15 @@ class User extends AbleObject_1.default {
221
327
  await callback(this);
222
328
  }
223
329
  }
330
+ // Login was successful.
224
331
  return { result: true, message: 'You are logged in.' };
225
332
  }
333
+ /**
334
+ * Log a user out of the system.
335
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
336
+ */
226
337
  async $logout() {
227
- const tilmeld = this.$nymph.tilmeld;
338
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
228
339
  try {
229
340
  for (let callback of this.constructor
230
341
  .beforeLogoutCallbacks) {
@@ -239,7 +350,8 @@ class User extends AbleObject_1.default {
239
350
  message: e.message,
240
351
  };
241
352
  }
242
- tilmeld.logout();
353
+ this.$nymph.config.debugLog('tilmeld', `User logout for "${this.$data.username}".`);
354
+ await tilmeld.logout();
243
355
  for (let callback of this.constructor
244
356
  .afterLogoutCallbacks) {
245
357
  if (callback) {
@@ -259,52 +371,87 @@ class User extends AbleObject_1.default {
259
371
  (0, md5_1.default)(this.$data.email.trim().toLowerCase()).toString() +
260
372
  '?d=identicon&s=40');
261
373
  }
374
+ $getGid() {
375
+ if (this.$data.group) {
376
+ return this.$data.group.guid;
377
+ }
378
+ return null;
379
+ }
380
+ $getGids() {
381
+ if (this.$data.groups) {
382
+ return this.$data.groups
383
+ .map((group) => group.guid)
384
+ .filter((guid) => guid != null);
385
+ }
386
+ return null;
387
+ }
388
+ /**
389
+ * Get the user's group descendants.
390
+ */
262
391
  async $getDescendantGroups() {
263
392
  if (this.$descendantGroups == null) {
264
393
  this.$descendantGroups = [];
265
394
  if (this.$data.group != null) {
395
+ await this.$data.group.$wake();
266
396
  this.$descendantGroups = await this.$data.group.$getDescendants();
267
397
  }
398
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
268
399
  for (let curGroup of this.$data.groups ?? []) {
269
400
  this.$descendantGroups = this.$descendantGroups?.concat(await curGroup.$getDescendants());
270
401
  }
271
402
  }
272
403
  return this.$descendantGroups;
273
404
  }
274
- $getDescendantGroupsSync() {
275
- if (this.$descendantGroups == null) {
276
- this.$descendantGroups = [];
277
- if (this.$data.group != null) {
278
- this.$descendantGroups = this.$data.group.$getDescendantsSync();
279
- }
280
- for (let curGroup of this.$data.groups ?? []) {
281
- this.$descendantGroups = this.$descendantGroups?.concat(curGroup.$getDescendantsSync());
282
- }
283
- }
284
- return this.$descendantGroups;
285
- }
286
405
  $jsonAcceptData(input, allowConflict = false) {
287
- const tilmeld = this.$nymph.tilmeld;
288
- this.$referenceWake();
406
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
407
+ this.$check();
408
+ if ('abilities' in input.data &&
409
+ input.data.abilities?.includes('system/admin') !==
410
+ this.$data.abilities?.includes('system/admin') &&
411
+ (!tilmeld.currentUser ||
412
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
413
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
414
+ }
289
415
  if ('abilities' in input.data &&
290
- input.data.abilities.indexOf('system/admin') !== -1 &&
291
- this.$data.abilities?.indexOf('system/admin') === -1 &&
292
- tilmeld.gatekeeper('tilmeld/admin') &&
293
- !tilmeld.gatekeeper('system/admin')) {
294
- throw new errors_1.BadDataError("You don't have the authority to make this user a system admin.");
416
+ input.data.abilities?.includes('tilmeld/admin') !==
417
+ this.$data.abilities?.includes('tilmeld/admin') &&
418
+ (!tilmeld.currentUser ||
419
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
420
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
421
+ }
422
+ if ('abilities' in input.data &&
423
+ input.data.abilities?.includes('tilmeld/switch') !==
424
+ this.$data.abilities?.includes('tilmeld/switch') &&
425
+ (!tilmeld.currentUser ||
426
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
427
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
295
428
  }
296
429
  this.$originalEmail = this.$data.email;
297
430
  super.$jsonAcceptData(input, allowConflict);
298
431
  }
299
432
  $jsonAcceptPatch(patch, allowConflict = false) {
300
- const tilmeld = this.$nymph.tilmeld;
301
- this.$referenceWake();
433
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
434
+ this.$check();
435
+ if ('abilities' in patch.set &&
436
+ patch.set.abilities?.includes('system/admin') !==
437
+ this.$data.abilities?.includes('system/admin') &&
438
+ (!tilmeld.currentUser ||
439
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
440
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
441
+ }
302
442
  if ('abilities' in patch.set &&
303
- patch.set.abilities.indexOf('system/admin') !== -1 &&
304
- this.$data.abilities?.indexOf('system/admin') === -1 &&
305
- tilmeld.gatekeeper('tilmeld/admin') &&
306
- !tilmeld.gatekeeper('system/admin')) {
307
- throw new errors_1.BadDataError("You don't have the authority to make this user a system admin.");
443
+ patch.set.abilities?.includes('tilmeld/admin') !==
444
+ this.$data.abilities?.includes('tilmeld/admin') &&
445
+ (!tilmeld.currentUser ||
446
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
447
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
448
+ }
449
+ if ('abilities' in patch.set &&
450
+ patch.set.abilities?.includes('tilmeld/switch') !==
451
+ this.$data.abilities?.includes('tilmeld/switch') &&
452
+ (!tilmeld.currentUser ||
453
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
454
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
308
455
  }
309
456
  this.$originalEmail = this.$data.email;
310
457
  super.$jsonAcceptPatch(patch, allowConflict);
@@ -313,8 +460,13 @@ class User extends AbleObject_1.default {
313
460
  super.$putData(data, sdata);
314
461
  this.$updateDataProtection();
315
462
  }
463
+ /**
464
+ * Update the data protection arrays for a user.
465
+ *
466
+ * @param givenUser User to update protection for. If undefined, will use the currently logged in user.
467
+ */
316
468
  $updateDataProtection(givenUser) {
317
- const tilmeld = this.$nymph.tilmeld;
469
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
318
470
  let user = givenUser ?? tilmeld.User.current();
319
471
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
320
472
  this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
@@ -323,75 +475,146 @@ class User extends AbleObject_1.default {
323
475
  this.$privateData.push('username');
324
476
  }
325
477
  const isCurrentUser = user != null && user.$is(this);
478
+ // While loading, accessing abilities on the current user will infinitely
479
+ // loop, but if this is the current user, we can use its abilities.
326
480
  const abilities = (isCurrentUser || user == null
327
481
  ? this.$data.abilities
328
482
  : user?.abilities) ?? [];
329
483
  const isNewUser = this.guid == null;
330
484
  if (isCurrentUser) {
485
+ // Users can check to see what abilities they have.
331
486
  this.$clientEnabledMethods.push('$gatekeeper');
332
487
  this.$clientEnabledMethods.push('$changePassword');
488
+ this.$clientEnabledMethods.push('$revokeCurrentTokens');
333
489
  this.$clientEnabledMethods.push('$logout');
334
490
  this.$clientEnabledMethods.push('$sendEmailVerification');
491
+ this.$clientEnabledMethods.push('$hasTOTPSecret');
492
+ this.$clientEnabledMethods.push('$getNewTOTPSecret');
493
+ this.$clientEnabledMethods.push('$saveTOTPSecret');
494
+ this.$clientEnabledMethods.push('$removeTOTPSecret');
335
495
  }
336
496
  if (user != null &&
337
- (abilities.indexOf('tilmeld/admin') !== -1 ||
338
- abilities.indexOf('system/admin') !== -1)) {
339
- this.$privateData = ['password', 'salt'];
497
+ (abilities.includes('tilmeld/admin') ||
498
+ abilities.includes('system/admin'))) {
499
+ // Users who can edit other users can see most of their data.
500
+ this.$privateData = ['password', 'salt', 'totpSecret'];
501
+ this.$clientEnabledMethods.push('$hasTOTPSecret');
502
+ this.$clientEnabledMethods.push('$removeTOTPSecret');
340
503
  this.$allowlistData = undefined;
341
504
  }
342
505
  else if (isCurrentUser || isNewUser) {
506
+ // Users can see their own data, and edit some of it.
343
507
  if (tilmeld.config.allowUsernameChange || isNewUser) {
344
508
  this.$allowlistData.push('username');
345
509
  }
346
510
  this.$allowlistData.push('avatar');
347
- if (tilmeld.config.userFields.indexOf('name') !== -1) {
511
+ if (tilmeld.config.userFields.includes('name')) {
348
512
  this.$allowlistData.push('nameFirst');
349
513
  this.$allowlistData.push('nameMiddle');
350
514
  this.$allowlistData.push('nameLast');
351
515
  this.$allowlistData.push('name');
352
516
  }
353
- if (tilmeld.config.userFields.indexOf('email') !== -1) {
517
+ if (tilmeld.config.userFields.includes('email')) {
354
518
  this.$allowlistData.push('email');
355
519
  }
356
- if (tilmeld.config.userFields.indexOf('phone') !== -1) {
520
+ if (tilmeld.config.userFields.includes('phone')) {
357
521
  this.$allowlistData.push('phone');
358
522
  }
359
523
  this.$privateData = [
524
+ // The things that are commented are there to show what is _not_ private.
525
+ // 'abilities',
526
+ // 'username',
527
+ // 'nameFirst',
528
+ // 'nameMiddle',
529
+ // 'nameLast',
530
+ // 'name',
531
+ // 'email',
532
+ // 'avatar',
533
+ // 'phone',
534
+ // 'group',
535
+ // 'groups',
536
+ // 'inheritAbilities',
537
+ 'enabled',
360
538
  'secret',
539
+ 'emailChangeDate',
361
540
  'newEmailSecret',
362
- 'cancelEmailAddress',
541
+ // 'newEmailAddress',
363
542
  'cancelEmailSecret',
364
- 'emailChangeDate',
543
+ 'cancelEmailAddress',
365
544
  'recoverSecret',
366
545
  'recoverSecretDate',
367
- 'password',
368
546
  'salt',
547
+ 'password',
548
+ 'passwordTemp',
549
+ // 'revokeTokenDate',
550
+ 'totpSecret',
369
551
  ];
370
552
  }
553
+ if (user != null &&
554
+ !isCurrentUser &&
555
+ (abilities.includes('tilmeld/switch') ||
556
+ abilities.includes('system/admin'))) {
557
+ // Users with this ability can switch to other users.
558
+ this.$clientEnabledMethods.push('$switchUser');
559
+ }
371
560
  }
372
- $gatekeeper(ability) {
373
- const tilmeld = this.$nymph.tilmeld;
561
+ $updateGroupDataProtection() {
562
+ if (this.$data.group != null && !this.$data.group.$asleep()) {
563
+ this.$data.group.$updateDataProtection();
564
+ }
565
+ for (let group of this.$data.groups ?? []) {
566
+ if (!group.$asleep()) {
567
+ group.$updateDataProtection();
568
+ }
569
+ }
570
+ }
571
+ /**
572
+ * Check to see if a user has an ability.
573
+ *
574
+ * This function will check both user and group abilities, if the user is
575
+ * marked to inherit the abilities of its group.
576
+ *
577
+ * If `ability` is undefined, it will check to see if the user is currently
578
+ * logged in.
579
+ *
580
+ * If the user has the "system/admin" ability, this function will return true.
581
+ *
582
+ * @param ability The ability.
583
+ * @returns True or false.
584
+ */
585
+ async $gatekeeper(ability) {
586
+ await this.$wake();
587
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
374
588
  if (ability == null) {
375
589
  return tilmeld.User.current()?.$is(this) ?? false;
376
590
  }
591
+ // Check the cache to see if we've already checked this user.
377
592
  if (this.$gatekeeperCache == null) {
378
- let abilities = this.$data.abilities ?? [];
379
- if (this.$data.inheritAbilities) {
380
- for (let curGroup of this.$data.groups ?? []) {
381
- if (curGroup.enabled) {
382
- abilities = abilities.concat(curGroup.abilities ?? []);
383
- }
384
- }
385
- if (this.$data.group != null &&
386
- this.$data.group.cdate != null &&
387
- this.$data.group.enabled) {
388
- abilities = abilities.concat(this.$data.group.abilities ?? []);
593
+ this.$gatekeeperCache = await this.$getGatekeeperCache();
594
+ }
595
+ return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
596
+ !!this.$data.abilities?.includes('system/admin'));
597
+ }
598
+ /**
599
+ * Build a gatekeeper cache object.
600
+ */
601
+ async $getGatekeeperCache() {
602
+ await this.$wake();
603
+ let abilities = this.$data.abilities ?? [];
604
+ if (this.$data.inheritAbilities) {
605
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
606
+ for (let curGroup of this.$data.groups ?? []) {
607
+ if (curGroup.enabled) {
608
+ abilities = abilities.concat(curGroup.abilities ?? []);
389
609
  }
390
610
  }
391
- this.$gatekeeperCache = Object.fromEntries(abilities.map((ability) => [ability, true]));
611
+ await this.$data.group?.$wake();
612
+ const group = this.$data.group;
613
+ if (group != null && group.cdate != null && group.enabled) {
614
+ abilities = abilities.concat(group.abilities ?? []);
615
+ }
392
616
  }
393
- return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
394
- !!this.$gatekeeperCache['system/admin']);
617
+ return Object.fromEntries(abilities.map((ability) => [ability, true]));
395
618
  }
396
619
  $clearCache() {
397
620
  const ret = super.$clearCache();
@@ -399,8 +622,13 @@ class User extends AbleObject_1.default {
399
622
  this.$gatekeeperCache = undefined;
400
623
  return ret;
401
624
  }
625
+ /**
626
+ * Send the user email verification/change/cancellation links.
627
+ *
628
+ * @returns True on success, false on failure.
629
+ */
402
630
  async $sendEmailVerification() {
403
- const tilmeld = this.$nymph.tilmeld;
631
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
404
632
  if (this.guid == null) {
405
633
  return false;
406
634
  }
@@ -463,18 +691,33 @@ class User extends AbleObject_1.default {
463
691
  }
464
692
  return success;
465
693
  }
466
- $addGroup(group) {
467
- if (this.$data.groups == null) {
468
- this.$data.groups = [];
469
- }
470
- if (!group.$inArray(this.$data.groups)) {
471
- this.$data.groups.push(group);
472
- return this.$data.groups;
694
+ /**
695
+ * Add the user to a (secondary) group.
696
+ *
697
+ * @param group The group.
698
+ * @returns True if the user is already in the group. The resulting array of groups if the user was not.
699
+ */
700
+ async $addGroup(group) {
701
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
702
+ let groups = this.$data.groups;
703
+ if (groups == null) {
704
+ groups = [];
705
+ }
706
+ if (!group.$inArray(groups)) {
707
+ groups.push(group);
708
+ this.$data.groups = groups;
709
+ return groups;
473
710
  }
474
711
  return true;
475
712
  }
713
+ /**
714
+ * Check the given password against the user's.
715
+ *
716
+ * @param password The password in question.
717
+ * @returns True if the passwords match, otherwise false.
718
+ */
476
719
  $checkPassword(password) {
477
- const tilmeld = this.$nymph.tilmeld;
720
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
478
721
  switch (tilmeld.config.pwMethod) {
479
722
  case 'plain':
480
723
  return this.$data.password === password;
@@ -486,10 +729,18 @@ class User extends AbleObject_1.default {
486
729
  (0, sha256_1.default)(password + this.$data.salt).toString(enc_base64_1.default));
487
730
  }
488
731
  }
489
- $delGroup(group) {
490
- if (this.$data.groups != null && group.$inArray(this.$data.groups)) {
732
+ /**
733
+ * Remove the user from a (secondary) group.
734
+ *
735
+ * @param group The group.
736
+ * @returns True if the user wasn't in the group. The resulting array of groups if the user was.
737
+ */
738
+ async $delGroup(group) {
739
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
740
+ let groups = this.$data.groups || [];
741
+ if (this.$data.groups != null && group.$inArray(groups)) {
491
742
  const newGroups = [];
492
- for (let curGroup of this.$data.groups) {
743
+ for (let curGroup of groups) {
493
744
  if (!group.$is(curGroup)) {
494
745
  newGroups.push(curGroup);
495
746
  }
@@ -499,7 +750,13 @@ class User extends AbleObject_1.default {
499
750
  }
500
751
  return true;
501
752
  }
502
- $inGroup(group) {
753
+ /**
754
+ * Check whether the user is in a (primary or secondary) group.
755
+ *
756
+ * @param group The group, or the group's GUID.
757
+ * @returns True or false.
758
+ */
759
+ async $inGroup(group) {
503
760
  if (typeof group === 'string') {
504
761
  if (this.$data.group?.guid === group) {
505
762
  return true;
@@ -513,35 +770,66 @@ class User extends AbleObject_1.default {
513
770
  }
514
771
  return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
515
772
  }
516
- $isDescendant(group) {
517
- if (this.$data.group?.cdate != null &&
518
- this.$data.group.$isDescendant(group)) {
773
+ /**
774
+ * Check whether the user is a descendant of a group.
775
+ *
776
+ * @param group The group, or the group's GUID.
777
+ * @returns True or false.
778
+ */
779
+ async $isDescendant(group) {
780
+ // Check to see if the user is in a descendant group of the given group.
781
+ await this.$data.group?.$wake();
782
+ if (this.$data.group != null &&
783
+ this.$data.group?.cdate != null &&
784
+ (await this.$data.group?.$isDescendant(group))) {
519
785
  return true;
520
786
  }
787
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
521
788
  for (let curGroup of this.$data.groups ?? []) {
522
- if (curGroup.$isDescendant(group)) {
789
+ if (await curGroup.$isDescendant(group)) {
523
790
  return true;
524
791
  }
525
792
  }
526
793
  return false;
527
794
  }
795
+ /**
796
+ * A frontend accessible method to change the user's password.
797
+ *
798
+ * @param data The input data from the client.
799
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
800
+ */
528
801
  async $changePassword(data) {
802
+ this.$nymph.config.debugLog('tilmeld', `Password change request for "${this.$data.username}".`);
529
803
  if (!('newPassword' in data) || !data.newPassword.length) {
530
804
  return { result: false, message: 'Please specify a password.' };
531
805
  }
532
806
  if (!this.$checkPassword(data.currentPassword ?? '')) {
807
+ this.$nymph.config.debugLog('tilmeld', `Incorrect password for change request for "${this.$data.username}".`);
533
808
  return { result: false, message: 'Incorrect password.' };
534
809
  }
535
810
  this.$data.passwordTemp = data.newPassword;
811
+ if (data.revokeCurrentTokens) {
812
+ this.$data.revokeTokenDate = Date.now();
813
+ }
536
814
  if (await this.$save()) {
815
+ if (data.revokeCurrentTokens) {
816
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
817
+ await tilmeld.login(this, true);
818
+ }
537
819
  return { result: true, message: 'Your password has been changed.' };
538
820
  }
539
821
  else {
540
822
  return { result: false, message: "Couldn't save new password." };
541
823
  }
542
824
  }
825
+ /**
826
+ * Change the user's password.
827
+ *
828
+ * @param password The new password.
829
+ * @returns The resulting password or hash which is stored in the entity.
830
+ */
543
831
  $password(password) {
544
- const tilmeld = this.$nymph.tilmeld;
832
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
545
833
  switch (tilmeld.config.pwMethod) {
546
834
  case 'plain':
547
835
  delete this.$data.salt;
@@ -559,14 +847,194 @@ class User extends AbleObject_1.default {
559
847
  }
560
848
  return this.$data.password;
561
849
  }
850
+ /**
851
+ * A frontend accessible method to revoke all currently issued tokens.
852
+ *
853
+ * @param data The input data from the client.
854
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
855
+ */
856
+ async $revokeCurrentTokens(data) {
857
+ if (!this.$checkPassword(data.password ?? '')) {
858
+ return { result: false, message: 'Incorrect password.' };
859
+ }
860
+ this.$data.revokeTokenDate = Date.now();
861
+ if (await this.$save()) {
862
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
863
+ await tilmeld.login(this, true);
864
+ return {
865
+ result: true,
866
+ message: 'You have logged out of all other sessions.',
867
+ };
868
+ }
869
+ else {
870
+ return { result: false, message: "Couldn't save revocation date." };
871
+ }
872
+ }
873
+ async $hasTOTPSecret() {
874
+ return this.$data.totpSecret != null;
875
+ }
876
+ /**
877
+ * Check the given code against the user's TOTP secret.
878
+ *
879
+ * @param code The code in question.
880
+ * @returns True if the code is valid, otherwise false.
881
+ */
882
+ $checkTOTPCode(code) {
883
+ if (this.$data.totpSecret == null) {
884
+ return false;
885
+ }
886
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
887
+ const totp = new otpauth_1.TOTP({
888
+ issuer: tilmeld.config.appName,
889
+ label: this.$data.username,
890
+ algorithm: 'SHA1',
891
+ digits: 6,
892
+ period: 30,
893
+ secret: this.$data.totpSecret,
894
+ });
895
+ return totp.validate({ token: code, window: 2 }) != null;
896
+ }
897
+ /**
898
+ * A frontend accessible method to generate a new TOTP secret.
899
+ *
900
+ * @returns An object with 'uri', 'qrcode', and 'secret'.
901
+ */
902
+ async $getNewTOTPSecret() {
903
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
904
+ if (this.$data.totpSecret != null) {
905
+ throw new errors_1.BadDataError('You already have a 2FA secret.');
906
+ }
907
+ const secret = new otpauth_1.Secret();
908
+ const totp = new otpauth_1.TOTP({
909
+ issuer: tilmeld.config.appName,
910
+ label: this.$data.username,
911
+ algorithm: 'SHA1',
912
+ digits: 6,
913
+ period: 30,
914
+ secret,
915
+ });
916
+ const uri = totp.toString();
917
+ const qrcode = await (0, qrcode_1.toDataURL)(uri);
918
+ return {
919
+ uri,
920
+ qrcode,
921
+ secret: secret.base32,
922
+ };
923
+ }
924
+ /**
925
+ * A frontend accessible method to save a TOTP secret into the user's account.
926
+ *
927
+ * @param data The input data from the client.
928
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
929
+ */
930
+ async $saveTOTPSecret(data) {
931
+ if (!this.$checkPassword(data.password ?? '')) {
932
+ return { result: false, message: 'Incorrect password.' };
933
+ }
934
+ if (this.$data.totpSecret != null) {
935
+ return { result: false, message: 'You already have a 2FA secret.' };
936
+ }
937
+ if (data.secret == null ||
938
+ typeof data.secret !== 'string' ||
939
+ data.secret === '') {
940
+ return { result: false, message: '2FA secret is invalid.' };
941
+ }
942
+ if (data.code == null ||
943
+ typeof data.code !== 'string' ||
944
+ data.code.length !== 6) {
945
+ return { result: false, message: '2FA code is invalid.' };
946
+ }
947
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
948
+ const totp = new otpauth_1.TOTP({
949
+ issuer: tilmeld.config.appName,
950
+ label: this.$data.username,
951
+ algorithm: 'SHA1',
952
+ digits: 6,
953
+ period: 30,
954
+ secret: data.secret,
955
+ });
956
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
957
+ return { result: false, message: '2FA code is incorrect.' };
958
+ }
959
+ this.$data.totpSecret = data.secret;
960
+ if (await this.$save()) {
961
+ return { result: true, message: 'Your two factor secret is now set.' };
962
+ }
963
+ else {
964
+ return { result: false, message: "Couldn't save two factor secret." };
965
+ }
966
+ }
967
+ /**
968
+ * A frontend accessible method to remove the TOTP secret from the user's
969
+ * account.
970
+ *
971
+ * @param data The input data from the client.
972
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
973
+ */
974
+ async $removeTOTPSecret(data) {
975
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
976
+ if (data) {
977
+ if (!this.$checkPassword(data.password ?? '')) {
978
+ return { result: false, message: 'Incorrect password.' };
979
+ }
980
+ if (data.code == null ||
981
+ typeof data.code !== 'string' ||
982
+ data.code.length !== 6) {
983
+ return { result: false, message: '2FA code is invalid.' };
984
+ }
985
+ if (this.$data.totpSecret == null) {
986
+ return { result: false, message: "You don't have a 2FA secret." };
987
+ }
988
+ const totp = new otpauth_1.TOTP({
989
+ issuer: tilmeld.config.appName,
990
+ label: this.$data.username,
991
+ algorithm: 'SHA1',
992
+ digits: 6,
993
+ period: 30,
994
+ secret: this.$data.totpSecret,
995
+ });
996
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
997
+ return { result: false, message: '2FA code is incorrect.' };
998
+ }
999
+ }
1000
+ else if (!tilmeld.gatekeeper('tilmeld/admin')) {
1001
+ return {
1002
+ result: false,
1003
+ message: 'You must provide your password and 2FA code.',
1004
+ };
1005
+ }
1006
+ delete this.$data.totpSecret;
1007
+ if (await this.$save()) {
1008
+ return {
1009
+ result: true,
1010
+ message: 'Two factor secret has been removed.',
1011
+ };
1012
+ }
1013
+ else {
1014
+ return { result: false, message: "Couldn't remove two factor secret." };
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Check that a username is valid.
1019
+ *
1020
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
1021
+ */
562
1022
  async $checkUsername() {
563
- const tilmeld = this.$nymph.tilmeld;
1023
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1024
+ this.$nymph.config.debugLog('tilmeld', `Username check "${this.$data.username}".`);
564
1025
  if (!tilmeld.config.emailUsernames) {
565
1026
  if (this.$data.username == null || !this.$data.username.length) {
566
1027
  return { result: false, message: 'Please specify a username.' };
567
1028
  }
568
- if (tilmeld.config.maxUsernameLength > 0 &&
569
- this.$data.username.length > tilmeld.config.maxUsernameLength) {
1029
+ if (this.$data.username.length < tilmeld.config.minUsernameLength) {
1030
+ return {
1031
+ result: false,
1032
+ message: 'Usernames must be at least ' +
1033
+ tilmeld.config.minUsernameLength +
1034
+ ' characters.',
1035
+ };
1036
+ }
1037
+ if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
570
1038
  return {
571
1039
  result: false,
572
1040
  message: 'Usernames must not exceed ' +
@@ -600,6 +1068,17 @@ class User extends AbleObject_1.default {
600
1068
  if (test != null) {
601
1069
  return { result: false, message: 'That username is taken.' };
602
1070
  }
1071
+ for (let callback of this.constructor
1072
+ .checkUsernameCallbacks) {
1073
+ if (callback) {
1074
+ const result = await callback(this, {
1075
+ username: this.$data.username,
1076
+ });
1077
+ if (!result.result) {
1078
+ return { message: 'Username is not available.', ...result };
1079
+ }
1080
+ }
1081
+ }
603
1082
  return {
604
1083
  result: true,
605
1084
  message: this.guid != null ? 'Username is valid.' : 'Username is available!',
@@ -610,8 +1089,15 @@ class User extends AbleObject_1.default {
610
1089
  if (this.$data.username == null || !this.$data.username.length) {
611
1090
  return { result: false, message: 'Please specify an email.' };
612
1091
  }
613
- if (tilmeld.config.maxUsernameLength > 0 &&
614
- this.$data.username.length > tilmeld.config.maxUsernameLength) {
1092
+ if (this.$data.username.length < tilmeld.config.minUsernameLength) {
1093
+ return {
1094
+ result: false,
1095
+ message: 'Emails must be at least ' +
1096
+ tilmeld.config.minUsernameLength +
1097
+ ' characters.',
1098
+ };
1099
+ }
1100
+ if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
615
1101
  return {
616
1102
  result: false,
617
1103
  message: 'Emails must not exceed ' +
@@ -622,8 +1108,14 @@ class User extends AbleObject_1.default {
622
1108
  return await this.$checkEmail();
623
1109
  }
624
1110
  }
1111
+ /**
1112
+ * Check that an email is unique.
1113
+ *
1114
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
1115
+ */
625
1116
  async $checkEmail() {
626
- const tilmeld = this.$nymph.tilmeld;
1117
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1118
+ this.$nymph.config.debugLog('tilmeld', `Email check "${this.$data.email}".`);
627
1119
  if (this.$data.email == null || !this.$data.email.length) {
628
1120
  if (tilmeld.config.verifyEmail) {
629
1121
  return { result: false, message: 'Please specify an email.' };
@@ -657,8 +1149,14 @@ class User extends AbleObject_1.default {
657
1149
  message: this.guid != null ? 'Email is valid.' : 'Email address is valid!',
658
1150
  };
659
1151
  }
1152
+ /**
1153
+ * Check that a phone number is unique.
1154
+ *
1155
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
1156
+ */
660
1157
  async $checkPhone() {
661
- const tilmeld = this.$nymph.tilmeld;
1158
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1159
+ this.$nymph.config.debugLog('tilmeld', `Phone check "${this.$data.phone}".`);
662
1160
  if (this.$data.phone == null || !this.$data.phone.length) {
663
1161
  return { result: false, message: 'Please specify a phone number.' };
664
1162
  }
@@ -686,7 +1184,7 @@ class User extends AbleObject_1.default {
686
1184
  };
687
1185
  }
688
1186
  async $register(data) {
689
- let tilmeld = this.$nymph.tilmeld;
1187
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
690
1188
  if (!tilmeld.config.allowRegistration) {
691
1189
  return {
692
1190
  result: false,
@@ -701,21 +1199,6 @@ class User extends AbleObject_1.default {
701
1199
  message: 'This is already a registered user.',
702
1200
  };
703
1201
  }
704
- try {
705
- for (let callback of this.constructor
706
- .beforeRegisterCallbacks) {
707
- if (callback) {
708
- await callback(this, data);
709
- }
710
- }
711
- }
712
- catch (e) {
713
- return {
714
- result: false,
715
- loggedin: false,
716
- message: e.message,
717
- };
718
- }
719
1202
  if (!('password' in data) || !data.password.length) {
720
1203
  return {
721
1204
  result: false,
@@ -728,7 +1211,7 @@ class User extends AbleObject_1.default {
728
1211
  return { ...unCheck, loggedin: false };
729
1212
  }
730
1213
  this.$password(data.password);
731
- if (tilmeld.config.regFields.indexOf('name') !== -1) {
1214
+ if (tilmeld.config.regFields.includes('name')) {
732
1215
  this.$data.name =
733
1216
  this.$data.nameFirst +
734
1217
  (this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
@@ -740,19 +1223,45 @@ class User extends AbleObject_1.default {
740
1223
  if (tilmeld.config.emailUsernames) {
741
1224
  this.$data.email = this.$data.username;
742
1225
  }
1226
+ // Start transaction.
743
1227
  const transaction = 'tilmeld-register-' + (0, guid_1.nanoid)();
744
1228
  const nymph = this.$nymph;
745
1229
  const tnymph = await this.$nymph.startTransaction(transaction);
746
1230
  this.$nymph = tnymph;
747
- tilmeld = this.$nymph.tilmeld;
1231
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1232
+ let message = '';
1233
+ let loggedin = false;
748
1234
  try {
1235
+ try {
1236
+ for (let callback of this.constructor
1237
+ .beforeRegisterCallbacks) {
1238
+ if (callback) {
1239
+ await callback(this, data);
1240
+ }
1241
+ }
1242
+ }
1243
+ catch (e) {
1244
+ await tnymph.rollback(transaction);
1245
+ this.$nymph = nymph;
1246
+ return {
1247
+ result: false,
1248
+ loggedin: false,
1249
+ message: e.message,
1250
+ };
1251
+ }
1252
+ // Add primary group.
749
1253
  let generatedPrimaryGroup = null;
750
1254
  if (tilmeld.config.generatePrimary) {
1255
+ // Generate a new primary group for the user.
751
1256
  generatedPrimaryGroup = await tilmeld.Group.factory();
752
1257
  generatedPrimaryGroup.groupname = this.$data.username;
753
1258
  generatedPrimaryGroup.avatar = this.$data.avatar;
754
- generatedPrimaryGroup.name = this.$data.name;
755
- generatedPrimaryGroup.email = this.$data.email;
1259
+ if (tilmeld.config.userFields.includes('name')) {
1260
+ generatedPrimaryGroup.name = this.$data.name;
1261
+ }
1262
+ if (tilmeld.config.userFields.includes('email')) {
1263
+ generatedPrimaryGroup.email = this.$data.email;
1264
+ }
756
1265
  const parent = await tnymph.getEntity({ class: tilmeld.Group }, {
757
1266
  type: '&',
758
1267
  equal: ['defaultPrimary', true],
@@ -772,6 +1281,7 @@ class User extends AbleObject_1.default {
772
1281
  this.$data.group = generatedPrimaryGroup;
773
1282
  }
774
1283
  else {
1284
+ // Add the default primary.
775
1285
  const group = await tnymph.getEntity({ class: tilmeld.Group }, {
776
1286
  type: '&',
777
1287
  equal: ['defaultPrimary', true],
@@ -780,19 +1290,26 @@ class User extends AbleObject_1.default {
780
1290
  this.$data.group = group;
781
1291
  }
782
1292
  }
783
- if (tilmeld.config.verifyEmail && tilmeld.config.unverifiedAccess) {
1293
+ // Add secondary groups.
1294
+ if (tilmeld.config.userFields.includes('email') &&
1295
+ tilmeld.config.verifyEmail &&
1296
+ tilmeld.config.unverifiedAccess) {
1297
+ // Add the default secondaries for unverified users.
784
1298
  this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
785
1299
  type: '&',
786
1300
  equal: ['unverifiedSecondary', true],
787
1301
  });
788
1302
  }
789
1303
  else {
1304
+ // Add the default secondaries.
790
1305
  this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
791
1306
  type: '&',
792
1307
  equal: ['defaultSecondary', true],
793
1308
  });
794
1309
  }
795
- if (tilmeld.config.verifyEmail) {
1310
+ if (tilmeld.config.userFields.includes('email') &&
1311
+ tilmeld.config.verifyEmail) {
1312
+ // The user will be enabled after verifying their e-mail address.
796
1313
  if (!tilmeld.config.unverifiedAccess) {
797
1314
  this.$data.enabled = false;
798
1315
  }
@@ -800,6 +1317,8 @@ class User extends AbleObject_1.default {
800
1317
  else {
801
1318
  this.$data.enabled = true;
802
1319
  }
1320
+ // If create_admin is true and there are no other users, grant
1321
+ // "system/admin".
803
1322
  let madeAdmin = false;
804
1323
  if (tilmeld.config.createAdmin) {
805
1324
  const otherUsers = await tnymph.getEntities({
@@ -808,13 +1327,17 @@ class User extends AbleObject_1.default {
808
1327
  limit: 1,
809
1328
  return: 'guid',
810
1329
  });
1330
+ // Make sure it's not just null, cause that means an error.
811
1331
  if (!otherUsers.length) {
812
1332
  this.$grant('system/admin');
813
1333
  this.$data.enabled = true;
814
1334
  madeAdmin = true;
815
1335
  }
816
1336
  }
1337
+ this.$nymph.config.debugLog('tilmeld', `Registering new user "${this.$data.username}".`);
817
1338
  if (await this.$saveSkipAC()) {
1339
+ this.$nymph.config.debugLog('tilmeld', `New user registered "${this.$data.username}".`);
1340
+ // Send the new user registered email.
818
1341
  if (tilmeld.config.userRegisteredRecipient != null) {
819
1342
  await tilmeld.config.sendEmail(tilmeld, {
820
1343
  template: 'UserRegistered',
@@ -831,8 +1354,7 @@ class User extends AbleObject_1.default {
831
1354
  },
832
1355
  }, this);
833
1356
  }
834
- let message = '';
835
- let loggedin = false;
1357
+ // Save the primary group.
836
1358
  if (generatedPrimaryGroup != null) {
837
1359
  generatedPrimaryGroup.user = this;
838
1360
  if (!(await generatedPrimaryGroup.$saveSkipAC())) {
@@ -845,17 +1367,20 @@ class User extends AbleObject_1.default {
845
1367
  };
846
1368
  }
847
1369
  }
1370
+ // Finish up.
848
1371
  if (tilmeld.config.verifyEmail &&
849
1372
  !tilmeld.config.unverifiedAccess &&
850
- !madeAdmin) {
1373
+ !madeAdmin &&
1374
+ tilmeld.config.userFields.includes('email')) {
851
1375
  message +=
852
1376
  `Almost there. An email has been sent to ${this.$data.email} ` +
853
1377
  'with a verification link for you to finish registration.';
854
1378
  }
855
1379
  else if (tilmeld.config.verifyEmail &&
856
1380
  tilmeld.config.unverifiedAccess &&
857
- !madeAdmin) {
858
- if (!tilmeld.login(this, true)) {
1381
+ !madeAdmin &&
1382
+ tilmeld.config.userFields.includes('email')) {
1383
+ if (!(await tilmeld.login(this, true))) {
859
1384
  throw new Error('An error occurred trying to log you in.');
860
1385
  }
861
1386
  message +=
@@ -865,7 +1390,7 @@ class User extends AbleObject_1.default {
865
1390
  loggedin = true;
866
1391
  }
867
1392
  else {
868
- if (!tilmeld.login(this, true)) {
1393
+ if (!(await tilmeld.login(this, true))) {
869
1394
  throw new Error('An error occurred trying to log you in.');
870
1395
  }
871
1396
  message += "You're now registered and logged in!";
@@ -880,21 +1405,9 @@ class User extends AbleObject_1.default {
880
1405
  });
881
1406
  }
882
1407
  }
883
- try {
884
- await tnymph.commit(transaction);
885
- this.$nymph = nymph;
886
- this.$nymph.tilmeld?.fillSession(this);
887
- }
888
- catch (e) {
889
- throw e;
890
- }
891
- return {
892
- result: true,
893
- loggedin,
894
- message,
895
- };
896
1408
  }
897
1409
  else {
1410
+ this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}".`);
898
1411
  await tnymph.rollback(transaction);
899
1412
  this.$nymph = nymph;
900
1413
  return {
@@ -905,22 +1418,37 @@ class User extends AbleObject_1.default {
905
1418
  }
906
1419
  }
907
1420
  catch (e) {
1421
+ this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}": ${e}`);
908
1422
  await tnymph.rollback(transaction);
909
1423
  this.$nymph = nymph;
910
1424
  throw e;
911
1425
  }
1426
+ try {
1427
+ await tnymph.commit(transaction);
1428
+ this.$nymph = nymph;
1429
+ await this.$nymph.tilmeld?.fillSession(this);
1430
+ }
1431
+ catch (e) {
1432
+ throw e;
1433
+ }
1434
+ return {
1435
+ result: true,
1436
+ loggedin,
1437
+ message,
1438
+ };
912
1439
  }
913
1440
  async $save() {
914
- let tilmeld = this.$nymph.tilmeld;
1441
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
915
1442
  if (this.$data.username == null || !this.$data.username.trim().length) {
916
1443
  return false;
917
1444
  }
918
1445
  if (tilmeld.gatekeeper('tilmeld/admin') &&
919
1446
  !tilmeld.gatekeeper('system/admin') &&
920
- this.$gatekeeper('system/admin')) {
1447
+ this.$data.abilities?.includes('system/admin')) {
921
1448
  throw new errors_1.BadDataError("You don't have the authority to modify system admins.");
922
1449
  }
923
1450
  let sendVerification = false;
1451
+ // Formatting.
924
1452
  this.$data.username = this.$data.username.trim();
925
1453
  if (tilmeld.config.emailUsernames) {
926
1454
  this.$data.email = this.$data.username;
@@ -934,49 +1462,63 @@ class User extends AbleObject_1.default {
934
1462
  (this.$data.nameMiddle ? ' ' + this.$data.nameMiddle : '') +
935
1463
  (this.$data.nameLast ? ' ' + this.$data.nameLast : '');
936
1464
  this.$data.enabled = !!this.$data.enabled;
937
- if (this.$data.nameFirst === '') {
1465
+ // Clear empty values.
1466
+ if (this.$data.nameFirst === '' ||
1467
+ !tilmeld.config.userFields.includes('name')) {
938
1468
  delete this.$data.nameFirst;
939
1469
  }
940
- if (this.$data.nameMiddle === '') {
1470
+ if (this.$data.nameMiddle === '' ||
1471
+ !tilmeld.config.userFields.includes('name')) {
941
1472
  delete this.$data.nameMiddle;
942
1473
  }
943
- if (this.$data.nameLast === '') {
1474
+ if (this.$data.nameLast === '' ||
1475
+ !tilmeld.config.userFields.includes('name')) {
944
1476
  delete this.$data.nameLast;
945
1477
  }
946
- if (this.$data.name === '') {
1478
+ if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
947
1479
  delete this.$data.name;
948
1480
  }
949
- if (this.$data.email === '') {
1481
+ if (this.$data.email === '' ||
1482
+ !tilmeld.config.userFields.includes('email')) {
950
1483
  delete this.$data.email;
951
1484
  }
952
1485
  if (this.$data.avatar === '') {
953
1486
  delete this.$data.avatar;
954
1487
  }
955
- if (this.$data.phone === '') {
1488
+ if (this.$data.phone === '' ||
1489
+ !tilmeld.config.userFields.includes('phone')) {
956
1490
  delete this.$data.phone;
957
1491
  }
958
- if (this.$data.secret === '') {
1492
+ if (this.$data.secret === '' ||
1493
+ !tilmeld.config.userFields.includes('email')) {
959
1494
  delete this.$data.secret;
960
1495
  }
961
- if (this.$data.emailChangeDate === 0) {
1496
+ if (this.$data.emailChangeDate === 0 ||
1497
+ !tilmeld.config.userFields.includes('email')) {
962
1498
  delete this.$data.emailChangeDate;
963
1499
  }
964
- if (this.$data.newEmailSecret === '') {
1500
+ if (this.$data.newEmailSecret === '' ||
1501
+ !tilmeld.config.userFields.includes('email')) {
965
1502
  delete this.$data.newEmailSecret;
966
1503
  }
967
- if (this.$data.newEmailAddress === '') {
1504
+ if (this.$data.newEmailAddress === '' ||
1505
+ !tilmeld.config.userFields.includes('email')) {
968
1506
  delete this.$data.newEmailAddress;
969
1507
  }
970
- if (this.$data.cancelEmailSecret === '') {
1508
+ if (this.$data.cancelEmailSecret === '' ||
1509
+ !tilmeld.config.userFields.includes('email')) {
971
1510
  delete this.$data.cancelEmailSecret;
972
1511
  }
973
- if (this.$data.cancelEmailAddress === '') {
1512
+ if (this.$data.cancelEmailAddress === '' ||
1513
+ !tilmeld.config.userFields.includes('email')) {
974
1514
  delete this.$data.cancelEmailAddress;
975
1515
  }
976
- if (this.$data.recoverSecret === '') {
1516
+ if (this.$data.recoverSecret === '' ||
1517
+ !tilmeld.config.userFields.includes('email')) {
977
1518
  delete this.$data.recoverSecret;
978
1519
  }
979
- if (this.$data.recoverSecretDate === 0) {
1520
+ if (this.$data.recoverSecretDate === 0 ||
1521
+ !tilmeld.config.userFields.includes('email')) {
980
1522
  delete this.$data.recoverSecretDate;
981
1523
  }
982
1524
  if (this.$data.password === '') {
@@ -988,26 +1530,36 @@ class User extends AbleObject_1.default {
988
1530
  if (this.$data.inheritAbilities === false) {
989
1531
  delete this.$data.inheritAbilities;
990
1532
  }
1533
+ // Verification.
991
1534
  const unCheck = await this.$checkUsername();
992
1535
  if (!unCheck.result) {
993
1536
  throw new errors_1.BadUsernameError(unCheck.message);
994
1537
  }
995
- if (!tilmeld.config.emailUsernames) {
1538
+ if (!tilmeld.config.emailUsernames &&
1539
+ tilmeld.config.userFields.includes('email')) {
996
1540
  const emCheck = await this.$checkEmail();
997
1541
  if (!emCheck.result) {
998
1542
  throw new errors_1.BadEmailError(emCheck.message);
999
1543
  }
1000
1544
  }
1001
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
1545
+ // Email changes.
1546
+ if (!tilmeld.gatekeeper('tilmeld/admin') &&
1547
+ tilmeld.config.userFields.includes('email')) {
1548
+ // The user isn't an admin, so email address changes should contain some
1549
+ // security measures.
1002
1550
  if (tilmeld.config.verifyEmail) {
1551
+ // The user needs to verify this new email address.
1003
1552
  if (this.guid == null) {
1004
- if ((this.$data.abilities?.indexOf('system/admin') ?? -1) === -1) {
1553
+ // If this is the first user, they'll be made an admin, so they don't
1554
+ // need to verify.
1555
+ if (!this.$data.abilities?.includes('system/admin')) {
1005
1556
  this.$data.secret = (0, guid_1.nanoid)();
1006
1557
  sendVerification = true;
1007
1558
  }
1008
1559
  }
1009
1560
  else if (this.$originalEmail != null &&
1010
1561
  this.$originalEmail !== this.$data.email) {
1562
+ // The user already has an old email address.
1011
1563
  if (tilmeld.config.emailRateLimit !== '' &&
1012
1564
  this.$data.emailChangeDate != null &&
1013
1565
  this.$data.emailChangeDate >=
@@ -1019,13 +1571,18 @@ class User extends AbleObject_1.default {
1019
1571
  else {
1020
1572
  if (this.$data.newEmailSecret == null &&
1021
1573
  tilmeld.config.emailRateLimit !== '' &&
1574
+ // Make sure the user has at least the rate limit time to cancel
1575
+ // an email change.
1022
1576
  (this.$data.emailChangeDate == null ||
1023
1577
  this.$data.emailChangeDate <
1024
1578
  (0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
1579
+ // Save the old email in case the cancel change link is clicked.
1025
1580
  this.$data.cancelEmailAddress = this.$originalEmail;
1026
1581
  this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
1027
1582
  this.$data.emailChangeDate = Date.now();
1028
1583
  }
1584
+ // Save the new address and reset to the old one (until they verify
1585
+ // it).
1029
1586
  this.$data.newEmailAddress = this.$data.email;
1030
1587
  this.$data.email = this.$originalEmail;
1031
1588
  if (tilmeld.config.emailUsernames) {
@@ -1039,9 +1596,13 @@ class User extends AbleObject_1.default {
1039
1596
  else if (this.guid != null &&
1040
1597
  this.$originalEmail != null &&
1041
1598
  this.$originalEmail !== this.$data.email &&
1599
+ // Make sure the user has at least the rate limit time to cancel an
1600
+ // email change.
1042
1601
  (this.$data.emailChangeDate == null ||
1043
1602
  this.$data.emailChangeDate <
1044
1603
  (0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
1604
+ // The user doesn't need to verify their new email address, but should
1605
+ // be able to cancel the email change from their old address.
1045
1606
  this.$data.cancelEmailAddress = this.$originalEmail;
1046
1607
  this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
1047
1608
  sendVerification = true;
@@ -1055,26 +1616,35 @@ class User extends AbleObject_1.default {
1055
1616
  throw new errors_1.BadDataError('A password is required.');
1056
1617
  }
1057
1618
  try {
1058
- tilmeld.config.validatorUser(this);
1619
+ tilmeld.config.validatorUser(tilmeld, this);
1059
1620
  }
1060
1621
  catch (e) {
1061
1622
  throw new errors_1.BadDataError(e?.message);
1062
1623
  }
1624
+ // Start transaction.
1063
1625
  const transaction = 'tilmeld-save-' + (0, guid_1.nanoid)();
1064
1626
  const nymph = this.$nymph;
1065
1627
  const tnymph = await this.$nymph.startTransaction(transaction);
1066
1628
  this.$nymph = tnymph;
1067
- tilmeld = this.$nymph.tilmeld;
1068
- if (this.$data.group != null &&
1069
- this.$data.group.user != null &&
1070
- this.$is(this.$data.group.user)) {
1629
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1630
+ const group = this.$data.group;
1631
+ await group?.$wake();
1632
+ if (group != null && group.user != null && this.$is(group.user)) {
1071
1633
  try {
1072
- this.$data.group.groupname = this.$data.username;
1073
- this.$data.group.avatar = this.$data.avatar;
1074
- this.$data.group.email = this.$data.email;
1075
- this.$data.group.name = this.$data.name;
1076
- this.$data.group.phone = this.$data.phone;
1077
- await this.$data.group.$saveSkipAC();
1634
+ // Update the user's generated primary group.
1635
+ group.groupname = this.$data.username;
1636
+ group.avatar = this.$data.avatar;
1637
+ if (tilmeld.config.userFields.includes('email')) {
1638
+ group.email = this.$data.email;
1639
+ }
1640
+ if (tilmeld.config.userFields.includes('name')) {
1641
+ group.name = this.$data.name;
1642
+ }
1643
+ if (tilmeld.config.userFields.includes('phone')) {
1644
+ group.phone = this.$data.phone;
1645
+ }
1646
+ await group.$saveSkipAC();
1647
+ this.$data.group = group;
1078
1648
  }
1079
1649
  catch (e) {
1080
1650
  await tnymph.rollback(transaction);
@@ -1096,6 +1666,7 @@ class User extends AbleObject_1.default {
1096
1666
  }
1097
1667
  if (ret) {
1098
1668
  if (sendVerification) {
1669
+ // The email has changed, so send a new verification email.
1099
1670
  if (!(await this.$sendEmailVerification())) {
1100
1671
  await tnymph.rollback(transaction);
1101
1672
  this.guid = preGuid;
@@ -1108,8 +1679,10 @@ class User extends AbleObject_1.default {
1108
1679
  this.$descendantGroups = undefined;
1109
1680
  this.$gatekeeperCache = undefined;
1110
1681
  await tnymph.commit(transaction);
1111
- if (nymph.tilmeld.User.current(true).$is(this)) {
1112
- nymph.tilmeld.fillSession(this);
1682
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(nymph);
1683
+ if (tilmeld.User.current(true).$is(this)) {
1684
+ // Update the user in the session cache.
1685
+ await tilmeld.fillSession(this);
1113
1686
  }
1114
1687
  }
1115
1688
  else {
@@ -1118,6 +1691,9 @@ class User extends AbleObject_1.default {
1118
1691
  this.$nymph = nymph;
1119
1692
  return ret;
1120
1693
  }
1694
+ /**
1695
+ * This should *never* be accessible on the client.
1696
+ */
1121
1697
  async $saveSkipAC() {
1122
1698
  this.$skipAcWhenSaving = true;
1123
1699
  return await this.$save();
@@ -1130,12 +1706,12 @@ class User extends AbleObject_1.default {
1130
1706
  return false;
1131
1707
  }
1132
1708
  async $delete() {
1133
- const tilmeld = this.$nymph.tilmeld;
1709
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1134
1710
  if (!tilmeld.gatekeeper('tilmeld/admin')) {
1135
1711
  throw new errors_1.BadDataError("You don't have the authority to delete users.");
1136
1712
  }
1137
1713
  if (!tilmeld.gatekeeper('system/admin') &&
1138
- this.$gatekeeper('system/admin')) {
1714
+ this.$data.abilities?.includes('system/admin')) {
1139
1715
  throw new errors_1.BadDataError("You don't have the authority to delete system admins.");
1140
1716
  }
1141
1717
  if (tilmeld.User.current(true).$is(this)) {
@@ -1148,6 +1724,7 @@ class User extends AbleObject_1.default {
1148
1724
  if (!(prop in this)) {
1149
1725
  throw new Error('Invalid event type.');
1150
1726
  }
1727
+ // @ts-ignore: The callback should always be the right type here.
1151
1728
  this[prop].push(callback);
1152
1729
  return () => this.off(event, callback);
1153
1730
  }
@@ -1156,16 +1733,21 @@ class User extends AbleObject_1.default {
1156
1733
  if (!(prop in this)) {
1157
1734
  return false;
1158
1735
  }
1736
+ // @ts-ignore: The callback should always be the right type here.
1159
1737
  const i = this[prop].indexOf(callback);
1160
1738
  if (i > -1) {
1739
+ // @ts-ignore: The callback should always be the right type here.
1161
1740
  this[prop].splice(i, 1);
1162
1741
  }
1163
1742
  return true;
1164
1743
  }
1165
1744
  }
1166
- exports.default = User;
1745
+ /**
1746
+ * The instance of Tilmeld to use for queries.
1747
+ */
1167
1748
  User.ETYPE = 'tilmeld_user';
1168
1749
  User.class = 'User';
1750
+ User.checkUsernameCallbacks = [];
1169
1751
  User.beforeRegisterCallbacks = [];
1170
1752
  User.afterRegisterCallbacks = [];
1171
1753
  User.beforeLoginCallbacks = [];
@@ -1180,23 +1762,33 @@ User.DEFAULT_CLIENT_ENABLED_METHODS = [
1180
1762
  '$register',
1181
1763
  ];
1182
1764
  User.DEFAULT_PRIVATE_DATA = [
1765
+ // The things that are commented are there to show what is _not_ private.
1766
+ 'abilities',
1767
+ // 'username',
1768
+ // 'nameFirst',
1769
+ // 'nameMiddle',
1770
+ // 'nameLast',
1771
+ // 'name',
1183
1772
  'email',
1184
- 'originalEmail',
1773
+ // 'avatar',
1185
1774
  'phone',
1186
1775
  'group',
1187
1776
  'groups',
1188
- 'abilities',
1189
1777
  'inheritAbilities',
1190
- 'recoverSecret',
1191
- 'recoverSecretDate',
1192
- 'password',
1193
- 'salt',
1778
+ 'enabled',
1194
1779
  'secret',
1195
- 'newEmailAddress',
1780
+ 'emailChangeDate',
1196
1781
  'newEmailSecret',
1197
- 'cancelEmailAddress',
1782
+ 'newEmailAddress',
1198
1783
  'cancelEmailSecret',
1199
- 'emailChangeDate',
1784
+ 'cancelEmailAddress',
1785
+ 'recoverSecret',
1786
+ 'recoverSecretDate',
1787
+ 'salt',
1788
+ 'password',
1789
+ 'passwordTemp',
1790
+ 'revokeTokenDate',
1791
+ 'totpSecret',
1200
1792
  ];
1201
1793
  User.DEFAULT_ALLOWLIST_DATA = [];
1202
1794
  User.clientEnabledStaticMethods = [
@@ -1207,4 +1799,5 @@ User.clientEnabledStaticMethods = [
1207
1799
  'getClientConfig',
1208
1800
  ];
1209
1801
  User.searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
1802
+ exports.default = User;
1210
1803
  //# sourceMappingURL=User.js.map