@nymphjs/tilmeld 1.0.0-beta.5 → 1.0.0-beta.50

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/dist/User.js CHANGED
@@ -3,12 +3,15 @@ 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");
14
17
  class User extends AbleObject_1.default {
@@ -30,32 +33,32 @@ class User extends AbleObject_1.default {
30
33
  }
31
34
  return entity;
32
35
  }
33
- static factorySync(guid) {
34
- return super.factorySync(guid);
36
+ static factorySync() {
37
+ return super.factorySync();
35
38
  }
36
- constructor(guid) {
37
- super(guid);
39
+ constructor() {
40
+ super();
38
41
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
39
42
  this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
40
43
  this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
41
44
  this.$allowlistTags = [];
42
45
  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
- }
46
+ this.$data.enabled = true;
47
+ this.$data.abilities = [];
48
+ this.$data.groups = [];
49
+ this.$data.inheritAbilities = true;
50
+ this.$updateDataProtection();
50
51
  }
51
52
  static current(returnObjectIfNotExist) {
52
- if (this.tilmeld.currentUser == null) {
53
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
54
+ if (tilmeld.currentUser == null) {
53
55
  return returnObjectIfNotExist ? this.factorySync() : null;
54
56
  }
55
- return this.tilmeld.currentUser;
57
+ return tilmeld.currentUser;
56
58
  }
57
59
  static async sendRecovery(data) {
58
- if (!this.tilmeld.config.pwRecovery) {
60
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
61
+ if (!tilmeld.config.pwRecovery) {
59
62
  return {
60
63
  result: false,
61
64
  message: 'Account recovery is not allowed.',
@@ -63,9 +66,8 @@ class User extends AbleObject_1.default {
63
66
  }
64
67
  let user;
65
68
  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 }, {
69
+ if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
70
+ const getUser = await this.nymph.getEntity({ class: tilmeld.User, skipAc: true }, {
69
71
  type: '&',
70
72
  ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
71
73
  });
@@ -86,7 +88,7 @@ class User extends AbleObject_1.default {
86
88
  options.locals = {};
87
89
  }
88
90
  else if (data.recoveryType === 'password') {
89
- const getUser = await this.tilmeld.User.factoryUsername(data.account);
91
+ const getUser = await tilmeld.User.factoryUsername(data.account);
90
92
  if (getUser.guid == null) {
91
93
  return {
92
94
  result: false,
@@ -108,13 +110,13 @@ class User extends AbleObject_1.default {
108
110
  };
109
111
  options.locals = {
110
112
  recoverCode: getUser.recoverSecret,
111
- timeLimit: this.tilmeld.config.pwRecoveryTimeLimit,
113
+ timeLimit: tilmeld.config.pwRecoveryTimeLimit,
112
114
  };
113
115
  }
114
116
  else {
115
117
  return { result: false, message: 'Invalid recovery type.' };
116
118
  }
117
- if (await this.tilmeld.config.sendEmail(this.tilmeld, options, user)) {
119
+ if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
118
120
  return {
119
121
  result: true,
120
122
  message: "We've sent an email to your registered address. Please " +
@@ -126,17 +128,18 @@ class User extends AbleObject_1.default {
126
128
  }
127
129
  }
128
130
  static async recover(data) {
129
- if (!this.tilmeld.config.pwRecovery) {
131
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
132
+ if (!tilmeld.config.pwRecovery) {
130
133
  return {
131
134
  result: false,
132
135
  message: 'Account recovery is not allowed.',
133
136
  };
134
137
  }
135
- const user = await this.tilmeld.User.factoryUsername(data.username);
138
+ const user = await tilmeld.User.factoryUsername(data.username);
136
139
  if (user.guid == null ||
137
140
  user.recoverSecret == null ||
138
141
  data.secret !== user.recoverSecret ||
139
- (0, strtotime_1.default)('+' + this.tilmeld.config.pwRecoveryTimeLimit, Math.floor((user.recoverSecretDate ?? 0) / 1000)) *
142
+ (0, strtotime_1.default)('+' + tilmeld.config.pwRecoveryTimeLimit, Math.floor((user.recoverSecretDate ?? 0) / 1000)) *
140
143
  1000 <
141
144
  Date.now()) {
142
145
  return {
@@ -161,22 +164,24 @@ class User extends AbleObject_1.default {
161
164
  }
162
165
  }
163
166
  static getClientConfig() {
167
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
164
168
  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,
169
+ regFields: tilmeld.config.regFields,
170
+ userFields: tilmeld.config.userFields,
171
+ emailUsernames: tilmeld.config.emailUsernames,
172
+ allowRegistration: tilmeld.config.allowRegistration,
173
+ allowUsernameChange: tilmeld.config.allowUsernameChange,
174
+ pwRecovery: tilmeld.config.pwRecovery,
175
+ verifyEmail: tilmeld.config.verifyEmail,
176
+ unverifiedAccess: tilmeld.config.unverifiedAccess,
173
177
  };
174
178
  }
175
179
  static async loginUser(data) {
180
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
176
181
  if (!('username' in data) || !data.username.length) {
177
182
  return { result: false, message: 'Incorrect login/password.' };
178
183
  }
179
- const user = await this.tilmeld.User.factoryUsername(data.username);
184
+ const user = await tilmeld.User.factoryUsername(data.username);
180
185
  const result = await user.$login(data);
181
186
  if (result.result) {
182
187
  user.$updateDataProtection();
@@ -185,19 +190,33 @@ class User extends AbleObject_1.default {
185
190
  return result;
186
191
  }
187
192
  async $login(data) {
188
- const tilmeld = this.$nymph.tilmeld;
193
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
189
194
  if (this.guid == null) {
190
195
  return { result: false, message: 'Incorrect login/password.' };
191
196
  }
192
197
  if (!this.$data.enabled) {
193
198
  return { result: false, message: 'This user is disabled.' };
194
199
  }
195
- if (this.$gatekeeper()) {
200
+ if (await this.$gatekeeper()) {
196
201
  return { result: true, message: 'You are already logged in.' };
197
202
  }
198
203
  if (!this.$checkPassword(data.password)) {
199
204
  return { result: false, message: 'Incorrect login/password.' };
200
205
  }
206
+ if (this.$data.totpSecret != null) {
207
+ if (data.code == null ||
208
+ typeof data.code !== 'string' ||
209
+ data.code.length !== 6) {
210
+ return {
211
+ result: false,
212
+ needTOTP: true,
213
+ message: 'You need to provide a 2FA code.',
214
+ };
215
+ }
216
+ if (!this.$checkTOTPCode(data.code)) {
217
+ return { result: false, message: 'Incorrect 2FA code.' };
218
+ }
219
+ }
201
220
  try {
202
221
  for (let callback of this.constructor
203
222
  .beforeLoginCallbacks) {
@@ -212,7 +231,62 @@ class User extends AbleObject_1.default {
212
231
  message: e.message,
213
232
  };
214
233
  }
215
- if (!tilmeld.login(this, true)) {
234
+ if (!(await tilmeld.login(this, true))) {
235
+ return { result: false, message: 'Incorrect login/password.' };
236
+ }
237
+ for (let callback of this.constructor
238
+ .afterLoginCallbacks) {
239
+ if (callback) {
240
+ await callback(this);
241
+ }
242
+ }
243
+ return { result: true, message: 'You are logged in.' };
244
+ }
245
+ async $switchUser(data) {
246
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
247
+ if (this.guid == null) {
248
+ return { result: false, message: 'Incorrect login/password.' };
249
+ }
250
+ let user = tilmeld.User.current();
251
+ if (!user || user.guid == null) {
252
+ return { result: false, message: "You're not logged in." };
253
+ }
254
+ const isCurrentUser = user != null && user.$is(this);
255
+ if (isCurrentUser) {
256
+ return { result: false, message: "You can't switch to yourself." };
257
+ }
258
+ const toAbilities = this.$data.abilities || [];
259
+ const fromAbilities = user.abilities || [];
260
+ if (fromAbilities.indexOf('tilmeld/switch') === -1 &&
261
+ fromAbilities.indexOf('system/admin') === -1) {
262
+ return {
263
+ result: false,
264
+ message: "You don't have permission to switch users.",
265
+ };
266
+ }
267
+ if (toAbilities.indexOf('system/admin') !== -1 ||
268
+ toAbilities.indexOf('tilmeld/admin') !== -1) {
269
+ return { result: false, message: "You can't switch to an admin." };
270
+ }
271
+ try {
272
+ for (let callback of this.constructor
273
+ .beforeLoginCallbacks) {
274
+ if (callback) {
275
+ await callback(this, {
276
+ username: this.$data.username || '',
277
+ password: '',
278
+ ...(data || {}),
279
+ });
280
+ }
281
+ }
282
+ }
283
+ catch (e) {
284
+ return {
285
+ result: false,
286
+ message: e.message,
287
+ };
288
+ }
289
+ if (!(await tilmeld.loginSwitch(this, true))) {
216
290
  return { result: false, message: 'Incorrect login/password.' };
217
291
  }
218
292
  for (let callback of this.constructor
@@ -224,7 +298,7 @@ class User extends AbleObject_1.default {
224
298
  return { result: true, message: 'You are logged in.' };
225
299
  }
226
300
  async $logout() {
227
- const tilmeld = this.$nymph.tilmeld;
301
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
228
302
  try {
229
303
  for (let callback of this.constructor
230
304
  .beforeLogoutCallbacks) {
@@ -239,7 +313,7 @@ class User extends AbleObject_1.default {
239
313
  message: e.message,
240
314
  };
241
315
  }
242
- tilmeld.logout();
316
+ await tilmeld.logout();
243
317
  for (let callback of this.constructor
244
318
  .afterLogoutCallbacks) {
245
319
  if (callback) {
@@ -259,52 +333,84 @@ class User extends AbleObject_1.default {
259
333
  (0, md5_1.default)(this.$data.email.trim().toLowerCase()).toString() +
260
334
  '?d=identicon&s=40');
261
335
  }
336
+ $getGid() {
337
+ if (this.$data.group) {
338
+ return this.$data.group.guid;
339
+ }
340
+ return null;
341
+ }
342
+ $getGids() {
343
+ if (this.$data.groups) {
344
+ return this.$data.groups
345
+ .map((group) => group.guid)
346
+ .filter((guid) => guid != null);
347
+ }
348
+ return null;
349
+ }
262
350
  async $getDescendantGroups() {
263
351
  if (this.$descendantGroups == null) {
264
352
  this.$descendantGroups = [];
265
353
  if (this.$data.group != null) {
354
+ await this.$data.group.$wake();
266
355
  this.$descendantGroups = await this.$data.group.$getDescendants();
267
356
  }
357
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
268
358
  for (let curGroup of this.$data.groups ?? []) {
269
359
  this.$descendantGroups = this.$descendantGroups?.concat(await curGroup.$getDescendants());
270
360
  }
271
361
  }
272
362
  return this.$descendantGroups;
273
363
  }
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
364
  $jsonAcceptData(input, allowConflict = false) {
287
- const tilmeld = this.$nymph.tilmeld;
288
- this.$referenceWake();
365
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
366
+ this.$check();
367
+ if ('abilities' in input.data &&
368
+ input.data.abilities?.includes('system/admin') !==
369
+ this.$data.abilities?.includes('system/admin') &&
370
+ (!tilmeld.currentUser ||
371
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
372
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
373
+ }
374
+ if ('abilities' in input.data &&
375
+ input.data.abilities?.includes('tilmeld/admin') !==
376
+ this.$data.abilities?.includes('tilmeld/admin') &&
377
+ (!tilmeld.currentUser ||
378
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
379
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
380
+ }
289
381
  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.");
382
+ input.data.abilities?.includes('tilmeld/switch') !==
383
+ this.$data.abilities?.includes('tilmeld/switch') &&
384
+ (!tilmeld.currentUser ||
385
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
386
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
295
387
  }
296
388
  this.$originalEmail = this.$data.email;
297
389
  super.$jsonAcceptData(input, allowConflict);
298
390
  }
299
391
  $jsonAcceptPatch(patch, allowConflict = false) {
300
- const tilmeld = this.$nymph.tilmeld;
301
- this.$referenceWake();
392
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
393
+ this.$check();
394
+ if ('abilities' in patch.set &&
395
+ patch.set.abilities?.includes('system/admin') !==
396
+ this.$data.abilities?.includes('system/admin') &&
397
+ (!tilmeld.currentUser ||
398
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
399
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
400
+ }
401
+ if ('abilities' in patch.set &&
402
+ patch.set.abilities?.includes('tilmeld/admin') !==
403
+ this.$data.abilities?.includes('tilmeld/admin') &&
404
+ (!tilmeld.currentUser ||
405
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
406
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
407
+ }
302
408
  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.");
409
+ patch.set.abilities?.includes('tilmeld/switch') !==
410
+ this.$data.abilities?.includes('tilmeld/switch') &&
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 tilmeld/switch.");
308
414
  }
309
415
  this.$originalEmail = this.$data.email;
310
416
  super.$jsonAcceptPatch(patch, allowConflict);
@@ -314,7 +420,7 @@ class User extends AbleObject_1.default {
314
420
  this.$updateDataProtection();
315
421
  }
316
422
  $updateDataProtection(givenUser) {
317
- const tilmeld = this.$nymph.tilmeld;
423
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
318
424
  let user = givenUser ?? tilmeld.User.current();
319
425
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
320
426
  this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
@@ -330,13 +436,20 @@ class User extends AbleObject_1.default {
330
436
  if (isCurrentUser) {
331
437
  this.$clientEnabledMethods.push('$gatekeeper');
332
438
  this.$clientEnabledMethods.push('$changePassword');
439
+ this.$clientEnabledMethods.push('$revokeCurrentTokens');
333
440
  this.$clientEnabledMethods.push('$logout');
334
441
  this.$clientEnabledMethods.push('$sendEmailVerification');
442
+ this.$clientEnabledMethods.push('$hasTOTPSecret');
443
+ this.$clientEnabledMethods.push('$getNewTOTPSecret');
444
+ this.$clientEnabledMethods.push('$saveTOTPSecret');
445
+ this.$clientEnabledMethods.push('$removeTOTPSecret');
335
446
  }
336
447
  if (user != null &&
337
448
  (abilities.indexOf('tilmeld/admin') !== -1 ||
338
449
  abilities.indexOf('system/admin') !== -1)) {
339
- this.$privateData = ['password', 'salt'];
450
+ this.$privateData = ['password', 'salt', 'totpSecret'];
451
+ this.$clientEnabledMethods.push('$hasTOTPSecret');
452
+ this.$clientEnabledMethods.push('$removeTOTPSecret');
340
453
  this.$allowlistData = undefined;
341
454
  }
342
455
  else if (isCurrentUser || isNewUser) {
@@ -357,41 +470,66 @@ class User extends AbleObject_1.default {
357
470
  this.$allowlistData.push('phone');
358
471
  }
359
472
  this.$privateData = [
473
+ 'enabled',
360
474
  'secret',
475
+ 'emailChangeDate',
361
476
  'newEmailSecret',
362
- 'cancelEmailAddress',
363
477
  'cancelEmailSecret',
364
- 'emailChangeDate',
478
+ 'cancelEmailAddress',
365
479
  'recoverSecret',
366
480
  'recoverSecretDate',
367
- 'password',
368
481
  'salt',
482
+ 'password',
483
+ 'passwordTemp',
484
+ 'totpSecret',
369
485
  ];
370
486
  }
487
+ if (user != null &&
488
+ !isCurrentUser &&
489
+ (abilities.indexOf('tilmeld/switch') !== -1 ||
490
+ abilities.indexOf('system/admin') !== -1)) {
491
+ this.$clientEnabledMethods.push('$switchUser');
492
+ }
493
+ }
494
+ $updateGroupDataProtection() {
495
+ if (this.$data.group != null && !this.$data.group.$asleep()) {
496
+ this.$data.group.$updateDataProtection();
497
+ }
498
+ for (let group of this.$data.groups ?? []) {
499
+ if (!group.$asleep()) {
500
+ group.$updateDataProtection();
501
+ }
502
+ }
371
503
  }
372
- $gatekeeper(ability) {
373
- const tilmeld = this.$nymph.tilmeld;
504
+ async $gatekeeper(ability) {
505
+ await this.$wake();
506
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
374
507
  if (ability == null) {
375
508
  return tilmeld.User.current()?.$is(this) ?? false;
376
509
  }
377
510
  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 ?? []);
511
+ this.$gatekeeperCache = await this.$getGatekeeperCache();
512
+ }
513
+ return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
514
+ !!this.$data.abilities?.includes('system/admin'));
515
+ }
516
+ async $getGatekeeperCache() {
517
+ await this.$wake();
518
+ let abilities = this.$data.abilities ?? [];
519
+ if (this.$data.inheritAbilities) {
520
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
521
+ for (let curGroup of this.$data.groups ?? []) {
522
+ if (curGroup.enabled) {
523
+ abilities = abilities.concat(curGroup.abilities ?? []);
389
524
  }
390
525
  }
391
- this.$gatekeeperCache = Object.fromEntries(abilities.map((ability) => [ability, true]));
526
+ await this.$data.group?.$wake();
527
+ const group = this.$data.group;
528
+ if (group != null && group.cdate != null && group.enabled) {
529
+ abilities = abilities.concat(group.abilities ?? []);
530
+ }
392
531
  }
393
- return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
394
- !!this.$gatekeeperCache['system/admin']);
532
+ return Object.fromEntries(abilities.map((ability) => [ability, true]));
395
533
  }
396
534
  $clearCache() {
397
535
  const ret = super.$clearCache();
@@ -400,7 +538,7 @@ class User extends AbleObject_1.default {
400
538
  return ret;
401
539
  }
402
540
  async $sendEmailVerification() {
403
- const tilmeld = this.$nymph.tilmeld;
541
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
404
542
  if (this.guid == null) {
405
543
  return false;
406
544
  }
@@ -463,18 +601,21 @@ class User extends AbleObject_1.default {
463
601
  }
464
602
  return success;
465
603
  }
466
- $addGroup(group) {
467
- if (this.$data.groups == null) {
468
- this.$data.groups = [];
604
+ async $addGroup(group) {
605
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
606
+ let groups = this.$data.groups;
607
+ if (groups == null) {
608
+ groups = [];
469
609
  }
470
- if (!group.$inArray(this.$data.groups)) {
471
- this.$data.groups.push(group);
472
- return this.$data.groups;
610
+ if (!group.$inArray(groups)) {
611
+ groups.push(group);
612
+ this.$data.groups = groups;
613
+ return groups;
473
614
  }
474
615
  return true;
475
616
  }
476
617
  $checkPassword(password) {
477
- const tilmeld = this.$nymph.tilmeld;
618
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
478
619
  switch (tilmeld.config.pwMethod) {
479
620
  case 'plain':
480
621
  return this.$data.password === password;
@@ -486,10 +627,12 @@ class User extends AbleObject_1.default {
486
627
  (0, sha256_1.default)(password + this.$data.salt).toString(enc_base64_1.default));
487
628
  }
488
629
  }
489
- $delGroup(group) {
490
- if (this.$data.groups != null && group.$inArray(this.$data.groups)) {
630
+ async $delGroup(group) {
631
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
632
+ let groups = this.$data.groups || [];
633
+ if (this.$data.groups != null && group.$inArray(groups)) {
491
634
  const newGroups = [];
492
- for (let curGroup of this.$data.groups) {
635
+ for (let curGroup of groups) {
493
636
  if (!group.$is(curGroup)) {
494
637
  newGroups.push(curGroup);
495
638
  }
@@ -499,7 +642,7 @@ class User extends AbleObject_1.default {
499
642
  }
500
643
  return true;
501
644
  }
502
- $inGroup(group) {
645
+ async $inGroup(group) {
503
646
  if (typeof group === 'string') {
504
647
  if (this.$data.group?.guid === group) {
505
648
  return true;
@@ -513,13 +656,16 @@ class User extends AbleObject_1.default {
513
656
  }
514
657
  return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
515
658
  }
516
- $isDescendant(group) {
517
- if (this.$data.group?.cdate != null &&
518
- this.$data.group.$isDescendant(group)) {
659
+ async $isDescendant(group) {
660
+ await this.$data.group?.$wake();
661
+ if (this.$data.group != null &&
662
+ this.$data.group?.cdate != null &&
663
+ (await this.$data.group?.$isDescendant(group))) {
519
664
  return true;
520
665
  }
666
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
521
667
  for (let curGroup of this.$data.groups ?? []) {
522
- if (curGroup.$isDescendant(group)) {
668
+ if (await curGroup.$isDescendant(group)) {
523
669
  return true;
524
670
  }
525
671
  }
@@ -533,7 +679,14 @@ class User extends AbleObject_1.default {
533
679
  return { result: false, message: 'Incorrect password.' };
534
680
  }
535
681
  this.$data.passwordTemp = data.newPassword;
682
+ if (data.revokeCurrentTokens) {
683
+ this.$data.revokeTokenDate = Date.now();
684
+ }
536
685
  if (await this.$save()) {
686
+ if (data.revokeCurrentTokens) {
687
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
688
+ await tilmeld.login(this, true);
689
+ }
537
690
  return { result: true, message: 'Your password has been changed.' };
538
691
  }
539
692
  else {
@@ -541,7 +694,7 @@ class User extends AbleObject_1.default {
541
694
  }
542
695
  }
543
696
  $password(password) {
544
- const tilmeld = this.$nymph.tilmeld;
697
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
545
698
  switch (tilmeld.config.pwMethod) {
546
699
  case 'plain':
547
700
  delete this.$data.salt;
@@ -559,14 +712,158 @@ class User extends AbleObject_1.default {
559
712
  }
560
713
  return this.$data.password;
561
714
  }
715
+ async $revokeCurrentTokens(data) {
716
+ if (!this.$checkPassword(data.password ?? '')) {
717
+ return { result: false, message: 'Incorrect password.' };
718
+ }
719
+ this.$data.revokeTokenDate = Date.now();
720
+ if (await this.$save()) {
721
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
722
+ await tilmeld.login(this, true);
723
+ return {
724
+ result: true,
725
+ message: 'You have logged out of all other sessions.',
726
+ };
727
+ }
728
+ else {
729
+ return { result: false, message: "Couldn't save revocation date." };
730
+ }
731
+ }
732
+ async $hasTOTPSecret() {
733
+ return this.$data.totpSecret != null;
734
+ }
735
+ $checkTOTPCode(code) {
736
+ if (this.$data.totpSecret == null) {
737
+ return false;
738
+ }
739
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
740
+ const totp = new otpauth_1.TOTP({
741
+ issuer: tilmeld.config.appName,
742
+ label: this.$data.username,
743
+ algorithm: 'SHA1',
744
+ digits: 6,
745
+ period: 30,
746
+ secret: this.$data.totpSecret,
747
+ });
748
+ return totp.validate({ token: code, window: 2 }) != null;
749
+ }
750
+ async $getNewTOTPSecret() {
751
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
752
+ if (this.$data.totpSecret != null) {
753
+ throw new errors_1.BadDataError('You already have a 2FA secret.');
754
+ }
755
+ const secret = new otpauth_1.Secret();
756
+ const totp = new otpauth_1.TOTP({
757
+ issuer: tilmeld.config.appName,
758
+ label: this.$data.username,
759
+ algorithm: 'SHA1',
760
+ digits: 6,
761
+ period: 30,
762
+ secret,
763
+ });
764
+ const uri = totp.toString();
765
+ const qrcode = await (0, qrcode_1.toDataURL)(uri);
766
+ return {
767
+ uri,
768
+ qrcode,
769
+ secret: secret.base32,
770
+ };
771
+ }
772
+ async $saveTOTPSecret(data) {
773
+ if (!this.$checkPassword(data.password ?? '')) {
774
+ return { result: false, message: 'Incorrect password.' };
775
+ }
776
+ if (this.$data.totpSecret != null) {
777
+ return { result: false, message: 'You already have a 2FA secret.' };
778
+ }
779
+ if (data.secret == null ||
780
+ typeof data.secret !== 'string' ||
781
+ data.secret === '') {
782
+ return { result: false, message: '2FA secret is invalid.' };
783
+ }
784
+ if (data.code == null ||
785
+ typeof data.code !== 'string' ||
786
+ data.code.length !== 6) {
787
+ return { result: false, message: '2FA code is invalid.' };
788
+ }
789
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
790
+ const totp = new otpauth_1.TOTP({
791
+ issuer: tilmeld.config.appName,
792
+ label: this.$data.username,
793
+ algorithm: 'SHA1',
794
+ digits: 6,
795
+ period: 30,
796
+ secret: data.secret,
797
+ });
798
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
799
+ return { result: false, message: '2FA code is incorrect.' };
800
+ }
801
+ this.$data.totpSecret = data.secret;
802
+ if (await this.$save()) {
803
+ return { result: true, message: 'Your two factor secret is now set.' };
804
+ }
805
+ else {
806
+ return { result: false, message: "Couldn't save two factor secret." };
807
+ }
808
+ }
809
+ async $removeTOTPSecret(data) {
810
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
811
+ if (data) {
812
+ if (!this.$checkPassword(data.password ?? '')) {
813
+ return { result: false, message: 'Incorrect password.' };
814
+ }
815
+ if (data.code == null ||
816
+ typeof data.code !== 'string' ||
817
+ data.code.length !== 6) {
818
+ return { result: false, message: '2FA code is invalid.' };
819
+ }
820
+ if (this.$data.totpSecret == null) {
821
+ return { result: false, message: "You don't have a 2FA secret." };
822
+ }
823
+ const totp = new otpauth_1.TOTP({
824
+ issuer: tilmeld.config.appName,
825
+ label: this.$data.username,
826
+ algorithm: 'SHA1',
827
+ digits: 6,
828
+ period: 30,
829
+ secret: this.$data.totpSecret,
830
+ });
831
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
832
+ return { result: false, message: '2FA code is incorrect.' };
833
+ }
834
+ }
835
+ else if (!tilmeld.gatekeeper('tilmeld/admin')) {
836
+ return {
837
+ result: false,
838
+ message: 'You must provide your password and 2FA code.',
839
+ };
840
+ }
841
+ delete this.$data.totpSecret;
842
+ if (await this.$save()) {
843
+ return {
844
+ result: true,
845
+ message: 'Two factor secret has been removed.',
846
+ };
847
+ }
848
+ else {
849
+ return { result: false, message: "Couldn't remove two factor secret." };
850
+ }
851
+ }
562
852
  async $checkUsername() {
563
- const tilmeld = this.$nymph.tilmeld;
853
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
564
854
  if (!tilmeld.config.emailUsernames) {
565
855
  if (this.$data.username == null || !this.$data.username.length) {
566
856
  return { result: false, message: 'Please specify a username.' };
567
857
  }
568
- if (tilmeld.config.maxUsernameLength > 0 &&
569
- this.$data.username.length > tilmeld.config.maxUsernameLength) {
858
+ if (this.$data.username.length < tilmeld.config.minUsernameLength) {
859
+ return {
860
+ result: false,
861
+ message: 'Usernames must be at least ' +
862
+ tilmeld.config.minUsernameLength +
863
+ ' characters.',
864
+ };
865
+ }
866
+ if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
570
867
  return {
571
868
  result: false,
572
869
  message: 'Usernames must not exceed ' +
@@ -600,6 +897,17 @@ class User extends AbleObject_1.default {
600
897
  if (test != null) {
601
898
  return { result: false, message: 'That username is taken.' };
602
899
  }
900
+ for (let callback of this.constructor
901
+ .checkUsernameCallbacks) {
902
+ if (callback) {
903
+ const result = await callback(this, {
904
+ username: this.$data.username,
905
+ });
906
+ if (!result.result) {
907
+ return { message: 'Username is not available.', ...result };
908
+ }
909
+ }
910
+ }
603
911
  return {
604
912
  result: true,
605
913
  message: this.guid != null ? 'Username is valid.' : 'Username is available!',
@@ -610,8 +918,15 @@ class User extends AbleObject_1.default {
610
918
  if (this.$data.username == null || !this.$data.username.length) {
611
919
  return { result: false, message: 'Please specify an email.' };
612
920
  }
613
- if (tilmeld.config.maxUsernameLength > 0 &&
614
- this.$data.username.length > tilmeld.config.maxUsernameLength) {
921
+ if (this.$data.username.length < tilmeld.config.minUsernameLength) {
922
+ return {
923
+ result: false,
924
+ message: 'Emails must be at least ' +
925
+ tilmeld.config.minUsernameLength +
926
+ ' characters.',
927
+ };
928
+ }
929
+ if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
615
930
  return {
616
931
  result: false,
617
932
  message: 'Emails must not exceed ' +
@@ -623,7 +938,7 @@ class User extends AbleObject_1.default {
623
938
  }
624
939
  }
625
940
  async $checkEmail() {
626
- const tilmeld = this.$nymph.tilmeld;
941
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
627
942
  if (this.$data.email == null || !this.$data.email.length) {
628
943
  if (tilmeld.config.verifyEmail) {
629
944
  return { result: false, message: 'Please specify an email.' };
@@ -658,7 +973,7 @@ class User extends AbleObject_1.default {
658
973
  };
659
974
  }
660
975
  async $checkPhone() {
661
- const tilmeld = this.$nymph.tilmeld;
976
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
662
977
  if (this.$data.phone == null || !this.$data.phone.length) {
663
978
  return { result: false, message: 'Please specify a phone number.' };
664
979
  }
@@ -686,7 +1001,7 @@ class User extends AbleObject_1.default {
686
1001
  };
687
1002
  }
688
1003
  async $register(data) {
689
- let tilmeld = this.$nymph.tilmeld;
1004
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
690
1005
  if (!tilmeld.config.allowRegistration) {
691
1006
  return {
692
1007
  result: false,
@@ -701,21 +1016,6 @@ class User extends AbleObject_1.default {
701
1016
  message: 'This is already a registered user.',
702
1017
  };
703
1018
  }
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
1019
  if (!('password' in data) || !data.password.length) {
720
1020
  return {
721
1021
  result: false,
@@ -744,8 +1044,27 @@ class User extends AbleObject_1.default {
744
1044
  const nymph = this.$nymph;
745
1045
  const tnymph = await this.$nymph.startTransaction(transaction);
746
1046
  this.$nymph = tnymph;
747
- tilmeld = this.$nymph.tilmeld;
1047
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1048
+ let message = '';
1049
+ let loggedin = false;
748
1050
  try {
1051
+ try {
1052
+ for (let callback of this.constructor
1053
+ .beforeRegisterCallbacks) {
1054
+ if (callback) {
1055
+ await callback(this, data);
1056
+ }
1057
+ }
1058
+ }
1059
+ catch (e) {
1060
+ await tnymph.rollback(transaction);
1061
+ this.$nymph = nymph;
1062
+ return {
1063
+ result: false,
1064
+ loggedin: false,
1065
+ message: e.message,
1066
+ };
1067
+ }
749
1068
  let generatedPrimaryGroup = null;
750
1069
  if (tilmeld.config.generatePrimary) {
751
1070
  generatedPrimaryGroup = await tilmeld.Group.factory();
@@ -831,8 +1150,6 @@ class User extends AbleObject_1.default {
831
1150
  },
832
1151
  }, this);
833
1152
  }
834
- let message = '';
835
- let loggedin = false;
836
1153
  if (generatedPrimaryGroup != null) {
837
1154
  generatedPrimaryGroup.user = this;
838
1155
  if (!(await generatedPrimaryGroup.$saveSkipAC())) {
@@ -855,7 +1172,7 @@ class User extends AbleObject_1.default {
855
1172
  else if (tilmeld.config.verifyEmail &&
856
1173
  tilmeld.config.unverifiedAccess &&
857
1174
  !madeAdmin) {
858
- if (!tilmeld.login(this, true)) {
1175
+ if (!(await tilmeld.login(this, true))) {
859
1176
  throw new Error('An error occurred trying to log you in.');
860
1177
  }
861
1178
  message +=
@@ -865,7 +1182,7 @@ class User extends AbleObject_1.default {
865
1182
  loggedin = true;
866
1183
  }
867
1184
  else {
868
- if (!tilmeld.login(this, true)) {
1185
+ if (!(await tilmeld.login(this, true))) {
869
1186
  throw new Error('An error occurred trying to log you in.');
870
1187
  }
871
1188
  message += "You're now registered and logged in!";
@@ -880,19 +1197,6 @@ class User extends AbleObject_1.default {
880
1197
  });
881
1198
  }
882
1199
  }
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
1200
  }
897
1201
  else {
898
1202
  await tnymph.rollback(transaction);
@@ -909,15 +1213,28 @@ class User extends AbleObject_1.default {
909
1213
  this.$nymph = nymph;
910
1214
  throw e;
911
1215
  }
1216
+ try {
1217
+ await tnymph.commit(transaction);
1218
+ this.$nymph = nymph;
1219
+ await this.$nymph.tilmeld?.fillSession(this);
1220
+ }
1221
+ catch (e) {
1222
+ throw e;
1223
+ }
1224
+ return {
1225
+ result: true,
1226
+ loggedin,
1227
+ message,
1228
+ };
912
1229
  }
913
1230
  async $save() {
914
- let tilmeld = this.$nymph.tilmeld;
1231
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
915
1232
  if (this.$data.username == null || !this.$data.username.trim().length) {
916
1233
  return false;
917
1234
  }
918
1235
  if (tilmeld.gatekeeper('tilmeld/admin') &&
919
1236
  !tilmeld.gatekeeper('system/admin') &&
920
- this.$gatekeeper('system/admin')) {
1237
+ this.$data.abilities?.includes('system/admin')) {
921
1238
  throw new errors_1.BadDataError("You don't have the authority to modify system admins.");
922
1239
  }
923
1240
  let sendVerification = false;
@@ -1064,17 +1381,18 @@ class User extends AbleObject_1.default {
1064
1381
  const nymph = this.$nymph;
1065
1382
  const tnymph = await this.$nymph.startTransaction(transaction);
1066
1383
  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)) {
1384
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1385
+ const group = this.$data.group;
1386
+ await group?.$wake();
1387
+ if (group != null && group.user != null && this.$is(group.user)) {
1071
1388
  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();
1389
+ group.groupname = this.$data.username;
1390
+ group.avatar = this.$data.avatar;
1391
+ group.email = this.$data.email;
1392
+ group.name = this.$data.name;
1393
+ group.phone = this.$data.phone;
1394
+ await group.$saveSkipAC();
1395
+ this.$data.group = group;
1078
1396
  }
1079
1397
  catch (e) {
1080
1398
  await tnymph.rollback(transaction);
@@ -1108,8 +1426,9 @@ class User extends AbleObject_1.default {
1108
1426
  this.$descendantGroups = undefined;
1109
1427
  this.$gatekeeperCache = undefined;
1110
1428
  await tnymph.commit(transaction);
1111
- if (nymph.tilmeld.User.current(true).$is(this)) {
1112
- nymph.tilmeld.fillSession(this);
1429
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(nymph);
1430
+ if (tilmeld.User.current(true).$is(this)) {
1431
+ await tilmeld.fillSession(this);
1113
1432
  }
1114
1433
  }
1115
1434
  else {
@@ -1130,12 +1449,12 @@ class User extends AbleObject_1.default {
1130
1449
  return false;
1131
1450
  }
1132
1451
  async $delete() {
1133
- const tilmeld = this.$nymph.tilmeld;
1452
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1134
1453
  if (!tilmeld.gatekeeper('tilmeld/admin')) {
1135
1454
  throw new errors_1.BadDataError("You don't have the authority to delete users.");
1136
1455
  }
1137
1456
  if (!tilmeld.gatekeeper('system/admin') &&
1138
- this.$gatekeeper('system/admin')) {
1457
+ this.$data.abilities?.includes('system/admin')) {
1139
1458
  throw new errors_1.BadDataError("You don't have the authority to delete system admins.");
1140
1459
  }
1141
1460
  if (tilmeld.User.current(true).$is(this)) {
@@ -1163,9 +1482,9 @@ class User extends AbleObject_1.default {
1163
1482
  return true;
1164
1483
  }
1165
1484
  }
1166
- exports.default = User;
1167
1485
  User.ETYPE = 'tilmeld_user';
1168
1486
  User.class = 'User';
1487
+ User.checkUsernameCallbacks = [];
1169
1488
  User.beforeRegisterCallbacks = [];
1170
1489
  User.afterRegisterCallbacks = [];
1171
1490
  User.beforeLoginCallbacks = [];
@@ -1180,23 +1499,26 @@ User.DEFAULT_CLIENT_ENABLED_METHODS = [
1180
1499
  '$register',
1181
1500
  ];
1182
1501
  User.DEFAULT_PRIVATE_DATA = [
1502
+ 'abilities',
1183
1503
  'email',
1184
- 'originalEmail',
1185
1504
  'phone',
1186
1505
  'group',
1187
1506
  'groups',
1188
- 'abilities',
1189
1507
  'inheritAbilities',
1190
- 'recoverSecret',
1191
- 'recoverSecretDate',
1192
- 'password',
1193
- 'salt',
1508
+ 'enabled',
1194
1509
  'secret',
1195
- 'newEmailAddress',
1510
+ 'emailChangeDate',
1196
1511
  'newEmailSecret',
1197
- 'cancelEmailAddress',
1512
+ 'newEmailAddress',
1198
1513
  'cancelEmailSecret',
1199
- 'emailChangeDate',
1514
+ 'cancelEmailAddress',
1515
+ 'recoverSecret',
1516
+ 'recoverSecretDate',
1517
+ 'salt',
1518
+ 'password',
1519
+ 'passwordTemp',
1520
+ 'revokeTokenDate',
1521
+ 'totpSecret',
1200
1522
  ];
1201
1523
  User.DEFAULT_ALLOWLIST_DATA = [];
1202
1524
  User.clientEnabledStaticMethods = [
@@ -1207,4 +1529,5 @@ User.clientEnabledStaticMethods = [
1207
1529
  'getClientConfig',
1208
1530
  ];
1209
1531
  User.searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
1532
+ exports.default = User;
1210
1533
  //# sourceMappingURL=User.js.map