@nymphjs/tilmeld 1.0.0-beta.4 → 1.0.0-beta.41

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) {
@@ -223,8 +242,63 @@ class User extends AbleObject_1.default {
223
242
  }
224
243
  return { result: true, message: 'You are logged in.' };
225
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 (!tilmeld.loginSwitch(this, true)) {
290
+ return { result: false, message: 'Incorrect login/password.' };
291
+ }
292
+ for (let callback of this.constructor
293
+ .afterLoginCallbacks) {
294
+ if (callback) {
295
+ await callback(this);
296
+ }
297
+ }
298
+ return { result: true, message: 'You are logged in.' };
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,50 +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();
289
- if (input.data.abilities?.indexOf('system/admin') !== -1 &&
290
- this.$data.abilities?.indexOf('system/admin') === -1 &&
291
- tilmeld.gatekeeper('tilmeld/admin') &&
292
- !tilmeld.gatekeeper('system/admin')) {
293
- throw new errors_1.BadDataError("You don't have the authority to make this user a system admin.");
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
+ }
381
+ if ('abilities' in input.data &&
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.");
294
387
  }
295
388
  this.$originalEmail = this.$data.email;
296
389
  super.$jsonAcceptData(input, allowConflict);
297
390
  }
298
391
  $jsonAcceptPatch(patch, allowConflict = false) {
299
- const tilmeld = this.$nymph.tilmeld;
300
- this.$referenceWake();
301
- if (patch.set.abilities?.indexOf('system/admin') !== -1 &&
302
- this.$data.abilities?.indexOf('system/admin') === -1 &&
303
- tilmeld.gatekeeper('tilmeld/admin') &&
304
- !tilmeld.gatekeeper('system/admin')) {
305
- throw new errors_1.BadDataError("You don't have the authority to make this user a system admin.");
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
+ }
408
+ if ('abilities' in patch.set &&
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.");
306
414
  }
307
415
  this.$originalEmail = this.$data.email;
308
416
  super.$jsonAcceptPatch(patch, allowConflict);
@@ -312,7 +420,7 @@ class User extends AbleObject_1.default {
312
420
  this.$updateDataProtection();
313
421
  }
314
422
  $updateDataProtection(givenUser) {
315
- const tilmeld = this.$nymph.tilmeld;
423
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
316
424
  let user = givenUser ?? tilmeld.User.current();
317
425
  this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
318
426
  this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
@@ -328,13 +436,18 @@ class User extends AbleObject_1.default {
328
436
  if (isCurrentUser) {
329
437
  this.$clientEnabledMethods.push('$gatekeeper');
330
438
  this.$clientEnabledMethods.push('$changePassword');
439
+ this.$clientEnabledMethods.push('$revokeCurrentTokens');
331
440
  this.$clientEnabledMethods.push('$logout');
332
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');
333
446
  }
334
447
  if (user != null &&
335
448
  (abilities.indexOf('tilmeld/admin') !== -1 ||
336
449
  abilities.indexOf('system/admin') !== -1)) {
337
- this.$privateData = ['password', 'salt'];
450
+ this.$privateData = ['password', 'salt', 'totpSecret'];
338
451
  this.$allowlistData = undefined;
339
452
  }
340
453
  else if (isCurrentUser || isNewUser) {
@@ -355,41 +468,61 @@ class User extends AbleObject_1.default {
355
468
  this.$allowlistData.push('phone');
356
469
  }
357
470
  this.$privateData = [
471
+ 'enabled',
358
472
  'secret',
473
+ 'emailChangeDate',
359
474
  'newEmailSecret',
360
- 'cancelEmailAddress',
361
475
  'cancelEmailSecret',
362
- 'emailChangeDate',
476
+ 'cancelEmailAddress',
363
477
  'recoverSecret',
364
478
  'recoverSecretDate',
365
- 'password',
366
479
  'salt',
480
+ 'password',
481
+ 'passwordTemp',
482
+ 'totpSecret',
367
483
  ];
368
484
  }
485
+ if (user != null &&
486
+ !isCurrentUser &&
487
+ (abilities.indexOf('tilmeld/switch') !== -1 ||
488
+ abilities.indexOf('system/admin') !== -1)) {
489
+ this.$clientEnabledMethods.push('$switchUser');
490
+ }
491
+ }
492
+ $updateGroupDataProtection() {
493
+ if (this.$data.group != null && !this.$data.group.$asleep()) {
494
+ this.$data.group.$updateDataProtection();
495
+ }
496
+ for (let group of this.$data.groups ?? []) {
497
+ if (!group.$asleep()) {
498
+ group.$updateDataProtection();
499
+ }
500
+ }
369
501
  }
370
- $gatekeeper(ability) {
371
- const tilmeld = this.$nymph.tilmeld;
502
+ async $gatekeeper(ability) {
503
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
372
504
  if (ability == null) {
373
505
  return tilmeld.User.current()?.$is(this) ?? false;
374
506
  }
375
507
  if (this.$gatekeeperCache == null) {
376
508
  let abilities = this.$data.abilities ?? [];
377
509
  if (this.$data.inheritAbilities) {
510
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
378
511
  for (let curGroup of this.$data.groups ?? []) {
379
512
  if (curGroup.enabled) {
380
513
  abilities = abilities.concat(curGroup.abilities ?? []);
381
514
  }
382
515
  }
383
- if (this.$data.group != null &&
384
- this.$data.group.cdate != null &&
385
- this.$data.group.enabled) {
386
- abilities = abilities.concat(this.$data.group.abilities ?? []);
516
+ await this.$data.group?.$wake();
517
+ const group = this.$data.group;
518
+ if (group != null && group.cdate != null && group.enabled) {
519
+ abilities = abilities.concat(group.abilities ?? []);
387
520
  }
388
521
  }
389
522
  this.$gatekeeperCache = Object.fromEntries(abilities.map((ability) => [ability, true]));
390
523
  }
391
524
  return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
392
- !!this.$gatekeeperCache['system/admin']);
525
+ !!this.$data.abilities?.includes('system/admin'));
393
526
  }
394
527
  $clearCache() {
395
528
  const ret = super.$clearCache();
@@ -398,7 +531,7 @@ class User extends AbleObject_1.default {
398
531
  return ret;
399
532
  }
400
533
  async $sendEmailVerification() {
401
- const tilmeld = this.$nymph.tilmeld;
534
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
402
535
  if (this.guid == null) {
403
536
  return false;
404
537
  }
@@ -461,18 +594,21 @@ class User extends AbleObject_1.default {
461
594
  }
462
595
  return success;
463
596
  }
464
- $addGroup(group) {
465
- if (this.$data.groups == null) {
466
- this.$data.groups = [];
597
+ async $addGroup(group) {
598
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
599
+ let groups = this.$data.groups;
600
+ if (groups == null) {
601
+ groups = [];
467
602
  }
468
- if (!group.$inArray(this.$data.groups)) {
469
- this.$data.groups.push(group);
470
- return this.$data.groups;
603
+ if (!group.$inArray(groups)) {
604
+ groups.push(group);
605
+ this.$data.groups = groups;
606
+ return groups;
471
607
  }
472
608
  return true;
473
609
  }
474
610
  $checkPassword(password) {
475
- const tilmeld = this.$nymph.tilmeld;
611
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
476
612
  switch (tilmeld.config.pwMethod) {
477
613
  case 'plain':
478
614
  return this.$data.password === password;
@@ -484,10 +620,12 @@ class User extends AbleObject_1.default {
484
620
  (0, sha256_1.default)(password + this.$data.salt).toString(enc_base64_1.default));
485
621
  }
486
622
  }
487
- $delGroup(group) {
488
- if (this.$data.groups != null && group.$inArray(this.$data.groups)) {
623
+ async $delGroup(group) {
624
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
625
+ let groups = this.$data.groups || [];
626
+ if (this.$data.groups != null && group.$inArray(groups)) {
489
627
  const newGroups = [];
490
- for (let curGroup of this.$data.groups) {
628
+ for (let curGroup of groups) {
491
629
  if (!group.$is(curGroup)) {
492
630
  newGroups.push(curGroup);
493
631
  }
@@ -497,7 +635,7 @@ class User extends AbleObject_1.default {
497
635
  }
498
636
  return true;
499
637
  }
500
- $inGroup(group) {
638
+ async $inGroup(group) {
501
639
  if (typeof group === 'string') {
502
640
  if (this.$data.group?.guid === group) {
503
641
  return true;
@@ -511,13 +649,16 @@ class User extends AbleObject_1.default {
511
649
  }
512
650
  return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
513
651
  }
514
- $isDescendant(group) {
515
- if (this.$data.group?.cdate != null &&
516
- this.$data.group.$isDescendant(group)) {
652
+ async $isDescendant(group) {
653
+ await this.$data.group?.$wake();
654
+ if (this.$data.group != null &&
655
+ this.$data.group?.cdate != null &&
656
+ (await this.$data.group?.$isDescendant(group))) {
517
657
  return true;
518
658
  }
659
+ await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
519
660
  for (let curGroup of this.$data.groups ?? []) {
520
- if (curGroup.$isDescendant(group)) {
661
+ if (await curGroup.$isDescendant(group)) {
521
662
  return true;
522
663
  }
523
664
  }
@@ -531,7 +672,14 @@ class User extends AbleObject_1.default {
531
672
  return { result: false, message: 'Incorrect password.' };
532
673
  }
533
674
  this.$data.passwordTemp = data.newPassword;
675
+ if (data.revokeCurrentTokens) {
676
+ this.$data.revokeTokenDate = Date.now();
677
+ }
534
678
  if (await this.$save()) {
679
+ if (data.revokeCurrentTokens) {
680
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
681
+ tilmeld.login(this, true);
682
+ }
535
683
  return { result: true, message: 'Your password has been changed.' };
536
684
  }
537
685
  else {
@@ -539,7 +687,7 @@ class User extends AbleObject_1.default {
539
687
  }
540
688
  }
541
689
  $password(password) {
542
- const tilmeld = this.$nymph.tilmeld;
690
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
543
691
  switch (tilmeld.config.pwMethod) {
544
692
  case 'plain':
545
693
  delete this.$data.salt;
@@ -557,8 +705,137 @@ class User extends AbleObject_1.default {
557
705
  }
558
706
  return this.$data.password;
559
707
  }
708
+ async $revokeCurrentTokens(data) {
709
+ if (!this.$checkPassword(data.password ?? '')) {
710
+ return { result: false, message: 'Incorrect password.' };
711
+ }
712
+ this.$data.revokeTokenDate = Date.now();
713
+ if (await this.$save()) {
714
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
715
+ tilmeld.login(this, true);
716
+ return {
717
+ result: true,
718
+ message: 'You have logged out of all other sessions.',
719
+ };
720
+ }
721
+ else {
722
+ return { result: false, message: "Couldn't save revocation date." };
723
+ }
724
+ }
725
+ async $hasTOTPSecret() {
726
+ return this.$data.totpSecret != null;
727
+ }
728
+ $checkTOTPCode(code) {
729
+ if (this.$data.totpSecret == null) {
730
+ return false;
731
+ }
732
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
733
+ const totp = new otpauth_1.TOTP({
734
+ issuer: tilmeld.config.appName,
735
+ label: this.$data.username,
736
+ algorithm: 'SHA1',
737
+ digits: 6,
738
+ period: 30,
739
+ secret: this.$data.totpSecret,
740
+ });
741
+ return totp.validate({ token: code, window: 2 }) != null;
742
+ }
743
+ async $getNewTOTPSecret() {
744
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
745
+ if (this.$data.totpSecret != null) {
746
+ throw new errors_1.BadDataError('You already have a 2FA secret.');
747
+ }
748
+ const secret = new otpauth_1.Secret();
749
+ const totp = new otpauth_1.TOTP({
750
+ issuer: tilmeld.config.appName,
751
+ label: this.$data.username,
752
+ algorithm: 'SHA1',
753
+ digits: 6,
754
+ period: 30,
755
+ secret,
756
+ });
757
+ const uri = totp.toString();
758
+ const qrcode = await (0, qrcode_1.toDataURL)(uri);
759
+ return {
760
+ uri,
761
+ qrcode,
762
+ secret: secret.base32,
763
+ };
764
+ }
765
+ async $saveTOTPSecret(data) {
766
+ if (!this.$checkPassword(data.password ?? '')) {
767
+ return { result: false, message: 'Incorrect password.' };
768
+ }
769
+ if (this.$data.totpSecret != null) {
770
+ return { result: false, message: 'You already have a 2FA secret.' };
771
+ }
772
+ if (data.secret == null ||
773
+ typeof data.secret !== 'string' ||
774
+ data.secret === '') {
775
+ return { result: false, message: '2FA secret is invalid.' };
776
+ }
777
+ if (data.code == null ||
778
+ typeof data.code !== 'string' ||
779
+ data.code.length !== 6) {
780
+ return { result: false, message: '2FA code is invalid.' };
781
+ }
782
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
783
+ const totp = new otpauth_1.TOTP({
784
+ issuer: tilmeld.config.appName,
785
+ label: this.$data.username,
786
+ algorithm: 'SHA1',
787
+ digits: 6,
788
+ period: 30,
789
+ secret: data.secret,
790
+ });
791
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
792
+ return { result: false, message: '2FA code is incorrect.' };
793
+ }
794
+ this.$data.totpSecret = data.secret;
795
+ if (await this.$save()) {
796
+ return { result: true, message: 'Your two factor secret is now set.' };
797
+ }
798
+ else {
799
+ return { result: false, message: "Couldn't save two factor secret." };
800
+ }
801
+ }
802
+ async $removeTOTPSecret(data) {
803
+ if (!this.$checkPassword(data.password ?? '')) {
804
+ return { result: false, message: 'Incorrect password.' };
805
+ }
806
+ if (this.$data.totpSecret == null) {
807
+ return { result: false, message: "You don't have a 2FA secret." };
808
+ }
809
+ if (data.code == null ||
810
+ typeof data.code !== 'string' ||
811
+ data.code.length !== 6) {
812
+ return { result: false, message: '2FA code is invalid.' };
813
+ }
814
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
815
+ const totp = new otpauth_1.TOTP({
816
+ issuer: tilmeld.config.appName,
817
+ label: this.$data.username,
818
+ algorithm: 'SHA1',
819
+ digits: 6,
820
+ period: 30,
821
+ secret: this.$data.totpSecret,
822
+ });
823
+ if (totp.validate({ token: data.code, window: 2 }) == null) {
824
+ return { result: false, message: '2FA code is incorrect.' };
825
+ }
826
+ delete this.$data.totpSecret;
827
+ if (await this.$save()) {
828
+ return {
829
+ result: true,
830
+ message: 'Your two factor secret has been removed.',
831
+ };
832
+ }
833
+ else {
834
+ return { result: false, message: "Couldn't remove two factor secret." };
835
+ }
836
+ }
560
837
  async $checkUsername() {
561
- const tilmeld = this.$nymph.tilmeld;
838
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
562
839
  if (!tilmeld.config.emailUsernames) {
563
840
  if (this.$data.username == null || !this.$data.username.length) {
564
841
  return { result: false, message: 'Please specify a username.' };
@@ -598,6 +875,17 @@ class User extends AbleObject_1.default {
598
875
  if (test != null) {
599
876
  return { result: false, message: 'That username is taken.' };
600
877
  }
878
+ for (let callback of this.constructor
879
+ .checkUsernameCallbacks) {
880
+ if (callback) {
881
+ const result = await callback(this, {
882
+ username: this.$data.username,
883
+ });
884
+ if (!result.result) {
885
+ return { message: 'Username is not available.', ...result };
886
+ }
887
+ }
888
+ }
601
889
  return {
602
890
  result: true,
603
891
  message: this.guid != null ? 'Username is valid.' : 'Username is available!',
@@ -621,7 +909,7 @@ class User extends AbleObject_1.default {
621
909
  }
622
910
  }
623
911
  async $checkEmail() {
624
- const tilmeld = this.$nymph.tilmeld;
912
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
625
913
  if (this.$data.email == null || !this.$data.email.length) {
626
914
  if (tilmeld.config.verifyEmail) {
627
915
  return { result: false, message: 'Please specify an email.' };
@@ -656,7 +944,7 @@ class User extends AbleObject_1.default {
656
944
  };
657
945
  }
658
946
  async $checkPhone() {
659
- const tilmeld = this.$nymph.tilmeld;
947
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
660
948
  if (this.$data.phone == null || !this.$data.phone.length) {
661
949
  return { result: false, message: 'Please specify a phone number.' };
662
950
  }
@@ -684,7 +972,7 @@ class User extends AbleObject_1.default {
684
972
  };
685
973
  }
686
974
  async $register(data) {
687
- let tilmeld = this.$nymph.tilmeld;
975
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
688
976
  if (!tilmeld.config.allowRegistration) {
689
977
  return {
690
978
  result: false,
@@ -742,7 +1030,7 @@ class User extends AbleObject_1.default {
742
1030
  const nymph = this.$nymph;
743
1031
  const tnymph = await this.$nymph.startTransaction(transaction);
744
1032
  this.$nymph = tnymph;
745
- tilmeld = this.$nymph.tilmeld;
1033
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
746
1034
  try {
747
1035
  let generatedPrimaryGroup = null;
748
1036
  if (tilmeld.config.generatePrimary) {
@@ -909,13 +1197,13 @@ class User extends AbleObject_1.default {
909
1197
  }
910
1198
  }
911
1199
  async $save() {
912
- let tilmeld = this.$nymph.tilmeld;
1200
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
913
1201
  if (this.$data.username == null || !this.$data.username.trim().length) {
914
1202
  return false;
915
1203
  }
916
- if (tilmeld.gatekeeper('tilmeld/admin') &&
917
- !tilmeld.gatekeeper('system/admin') &&
918
- this.$gatekeeper('system/admin')) {
1204
+ if ((await tilmeld.gatekeeper('tilmeld/admin')) &&
1205
+ !(await tilmeld.gatekeeper('system/admin')) &&
1206
+ this.$data.abilities?.includes('system/admin')) {
919
1207
  throw new errors_1.BadDataError("You don't have the authority to modify system admins.");
920
1208
  }
921
1209
  let sendVerification = false;
@@ -996,7 +1284,7 @@ class User extends AbleObject_1.default {
996
1284
  throw new errors_1.BadEmailError(emCheck.message);
997
1285
  }
998
1286
  }
999
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
1287
+ if (!(await tilmeld.gatekeeper('tilmeld/admin'))) {
1000
1288
  if (tilmeld.config.verifyEmail) {
1001
1289
  if (this.guid == null) {
1002
1290
  if ((this.$data.abilities?.indexOf('system/admin') ?? -1) === -1) {
@@ -1062,17 +1350,18 @@ class User extends AbleObject_1.default {
1062
1350
  const nymph = this.$nymph;
1063
1351
  const tnymph = await this.$nymph.startTransaction(transaction);
1064
1352
  this.$nymph = tnymph;
1065
- tilmeld = this.$nymph.tilmeld;
1066
- if (this.$data.group != null &&
1067
- this.$data.group.user != null &&
1068
- this.$is(this.$data.group.user)) {
1353
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1354
+ const group = this.$data.group;
1355
+ await group?.$wake();
1356
+ if (group != null && group.user != null && this.$is(group.user)) {
1069
1357
  try {
1070
- this.$data.group.groupname = this.$data.username;
1071
- this.$data.group.avatar = this.$data.avatar;
1072
- this.$data.group.email = this.$data.email;
1073
- this.$data.group.name = this.$data.name;
1074
- this.$data.group.phone = this.$data.phone;
1075
- await this.$data.group.$saveSkipAC();
1358
+ group.groupname = this.$data.username;
1359
+ group.avatar = this.$data.avatar;
1360
+ group.email = this.$data.email;
1361
+ group.name = this.$data.name;
1362
+ group.phone = this.$data.phone;
1363
+ await group.$saveSkipAC();
1364
+ this.$data.group = group;
1076
1365
  }
1077
1366
  catch (e) {
1078
1367
  await tnymph.rollback(transaction);
@@ -1106,8 +1395,8 @@ class User extends AbleObject_1.default {
1106
1395
  this.$descendantGroups = undefined;
1107
1396
  this.$gatekeeperCache = undefined;
1108
1397
  await tnymph.commit(transaction);
1109
- if (nymph.tilmeld.User.current(true).$is(this)) {
1110
- nymph.tilmeld.fillSession(this);
1398
+ if ((0, enforceTilmeld_1.enforceTilmeld)(nymph).User.current(true).$is(this)) {
1399
+ (0, enforceTilmeld_1.enforceTilmeld)(nymph).fillSession(this);
1111
1400
  }
1112
1401
  }
1113
1402
  else {
@@ -1128,12 +1417,12 @@ class User extends AbleObject_1.default {
1128
1417
  return false;
1129
1418
  }
1130
1419
  async $delete() {
1131
- const tilmeld = this.$nymph.tilmeld;
1132
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
1420
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
1421
+ if (!(await tilmeld.gatekeeper('tilmeld/admin'))) {
1133
1422
  throw new errors_1.BadDataError("You don't have the authority to delete users.");
1134
1423
  }
1135
- if (!tilmeld.gatekeeper('system/admin') &&
1136
- this.$gatekeeper('system/admin')) {
1424
+ if (!(await tilmeld.gatekeeper('system/admin')) &&
1425
+ this.$data.abilities?.includes('system/admin')) {
1137
1426
  throw new errors_1.BadDataError("You don't have the authority to delete system admins.");
1138
1427
  }
1139
1428
  if (tilmeld.User.current(true).$is(this)) {
@@ -1161,9 +1450,9 @@ class User extends AbleObject_1.default {
1161
1450
  return true;
1162
1451
  }
1163
1452
  }
1164
- exports.default = User;
1165
1453
  User.ETYPE = 'tilmeld_user';
1166
1454
  User.class = 'User';
1455
+ User.checkUsernameCallbacks = [];
1167
1456
  User.beforeRegisterCallbacks = [];
1168
1457
  User.afterRegisterCallbacks = [];
1169
1458
  User.beforeLoginCallbacks = [];
@@ -1178,23 +1467,26 @@ User.DEFAULT_CLIENT_ENABLED_METHODS = [
1178
1467
  '$register',
1179
1468
  ];
1180
1469
  User.DEFAULT_PRIVATE_DATA = [
1470
+ 'abilities',
1181
1471
  'email',
1182
- 'originalEmail',
1183
1472
  'phone',
1184
1473
  'group',
1185
1474
  'groups',
1186
- 'abilities',
1187
1475
  'inheritAbilities',
1188
- 'recoverSecret',
1189
- 'recoverSecretDate',
1190
- 'password',
1191
- 'salt',
1476
+ 'enabled',
1192
1477
  'secret',
1193
- 'newEmailAddress',
1478
+ 'emailChangeDate',
1194
1479
  'newEmailSecret',
1195
- 'cancelEmailAddress',
1480
+ 'newEmailAddress',
1196
1481
  'cancelEmailSecret',
1197
- 'emailChangeDate',
1482
+ 'cancelEmailAddress',
1483
+ 'recoverSecret',
1484
+ 'recoverSecretDate',
1485
+ 'salt',
1486
+ 'password',
1487
+ 'passwordTemp',
1488
+ 'revokeTokenDate',
1489
+ 'totpSecret',
1198
1490
  ];
1199
1491
  User.DEFAULT_ALLOWLIST_DATA = [];
1200
1492
  User.clientEnabledStaticMethods = [
@@ -1205,4 +1497,5 @@ User.clientEnabledStaticMethods = [
1205
1497
  'getClientConfig',
1206
1498
  ];
1207
1499
  User.searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
1500
+ exports.default = User;
1208
1501
  //# sourceMappingURL=User.js.map