@nymphjs/tilmeld 1.0.0-beta.3 → 1.0.0-beta.31
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/CHANGELOG.md +150 -0
- package/README.md +13 -0
- package/dist/Group.d.ts +0 -2
- package/dist/Group.js +57 -28
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.d.ts +5 -2
- package/dist/Tilmeld.js +164 -63
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +56 -5
- package/dist/User.js +331 -66
- package/dist/User.js.map +1 -1
- package/dist/conf/d.d.ts +3 -1
- package/dist/conf/defaults.js +15 -4
- package/dist/conf/defaults.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +22 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +22 -19
- package/src/Group.ts +80 -32
- package/src/Tilmeld.ts +241 -78
- package/src/User.ts +508 -74
- package/src/conf/d.ts +16 -7
- package/src/conf/defaults.ts +17 -4
- package/src/enforceTilmeld.ts +20 -0
- package/src/index.ts +2 -0
package/src/User.ts
CHANGED
|
@@ -5,15 +5,17 @@ import {
|
|
|
5
5
|
Selector,
|
|
6
6
|
SerializedEntityData,
|
|
7
7
|
} from '@nymphjs/nymph';
|
|
8
|
+
import { humanSecret, nanoid } from '@nymphjs/guid';
|
|
8
9
|
import { EmailOptions } from 'email-templates';
|
|
9
10
|
import strtotime from 'locutus/php/datetime/strtotime';
|
|
10
11
|
import Base64 from 'crypto-js/enc-base64';
|
|
11
12
|
import sha256 from 'crypto-js/sha256';
|
|
12
13
|
import md5 from 'crypto-js/md5';
|
|
13
14
|
import { difference } from 'lodash';
|
|
14
|
-
import {
|
|
15
|
+
import { TOTP, Secret } from 'otpauth';
|
|
16
|
+
import { toDataURL } from 'qrcode';
|
|
15
17
|
|
|
16
|
-
import
|
|
18
|
+
import { enforceTilmeld } from './enforceTilmeld';
|
|
17
19
|
import AbleObject from './AbleObject';
|
|
18
20
|
import Group, { GroupData } from './Group';
|
|
19
21
|
import {
|
|
@@ -24,12 +26,22 @@ import {
|
|
|
24
26
|
} from './errors';
|
|
25
27
|
|
|
26
28
|
export type EventType =
|
|
29
|
+
| 'checkUsername'
|
|
27
30
|
| 'beforeRegister'
|
|
28
31
|
| 'afterRegister'
|
|
29
32
|
| 'beforeLogin'
|
|
30
33
|
| 'afterLogin'
|
|
31
34
|
| 'beforeLogout'
|
|
32
35
|
| 'afterLogout';
|
|
36
|
+
/**
|
|
37
|
+
* This is run when the user has entered an otherwise valid username into the
|
|
38
|
+
* signup form. It should return a result, which when false will stop the
|
|
39
|
+
* process and return the included message, disallowing the username.
|
|
40
|
+
*/
|
|
41
|
+
export type TilmeldCheckUsernameCallback = (
|
|
42
|
+
user: User & UserData,
|
|
43
|
+
data: { username: string }
|
|
44
|
+
) => Promise<{ result: boolean; message?: string }>;
|
|
33
45
|
/**
|
|
34
46
|
* Theses are run before the user data checks, so the only checks before are
|
|
35
47
|
* whether registration is allowed and whether the user is already registered.
|
|
@@ -135,7 +147,8 @@ export type UserData = {
|
|
|
135
147
|
*/
|
|
136
148
|
newEmailSecret?: string;
|
|
137
149
|
/**
|
|
138
|
-
*
|
|
150
|
+
* If the user has changed their email address, this is the new one, awaiting
|
|
151
|
+
* verification.
|
|
139
152
|
*/
|
|
140
153
|
newEmailAddress?: string;
|
|
141
154
|
/**
|
|
@@ -167,6 +180,15 @@ export type UserData = {
|
|
|
167
180
|
* database.
|
|
168
181
|
*/
|
|
169
182
|
passwordTemp?: string;
|
|
183
|
+
/**
|
|
184
|
+
* If set, this timestamp is the cutoff point for JWT issue dates. Any token
|
|
185
|
+
* issued before this date will not authenticate the user.
|
|
186
|
+
*/
|
|
187
|
+
revokeTokenDate?: number;
|
|
188
|
+
/**
|
|
189
|
+
* Two factor auth secret.
|
|
190
|
+
*/
|
|
191
|
+
totpSecret?: string;
|
|
170
192
|
};
|
|
171
193
|
|
|
172
194
|
/**
|
|
@@ -182,9 +204,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
182
204
|
/**
|
|
183
205
|
* The instance of Tilmeld to use for queries.
|
|
184
206
|
*/
|
|
185
|
-
static tilmeld: Tilmeld;
|
|
186
207
|
static ETYPE = 'tilmeld_user';
|
|
187
208
|
static class = 'User';
|
|
209
|
+
private static checkUsernameCallbacks: TilmeldCheckUsernameCallback[] = [];
|
|
188
210
|
private static beforeRegisterCallbacks: TilmeldBeforeRegisterCallback[] = [];
|
|
189
211
|
private static afterRegisterCallbacks: TilmeldAfterRegisterCallback[] = [];
|
|
190
212
|
private static beforeLoginCallbacks: TilmeldBeforeLoginCallback[] = [];
|
|
@@ -200,23 +222,33 @@ export default class User extends AbleObject<UserData> {
|
|
|
200
222
|
'$register',
|
|
201
223
|
];
|
|
202
224
|
private static DEFAULT_PRIVATE_DATA = [
|
|
225
|
+
// The things that are commented are there to show what is _not_ private.
|
|
226
|
+
'abilities',
|
|
227
|
+
// 'username',
|
|
228
|
+
// 'nameFirst',
|
|
229
|
+
// 'nameMiddle',
|
|
230
|
+
// 'nameLast',
|
|
231
|
+
// 'name',
|
|
203
232
|
'email',
|
|
204
|
-
'
|
|
233
|
+
// 'avatar',
|
|
205
234
|
'phone',
|
|
206
235
|
'group',
|
|
207
236
|
'groups',
|
|
208
|
-
'abilities',
|
|
209
237
|
'inheritAbilities',
|
|
210
|
-
'
|
|
211
|
-
'recoverSecretDate',
|
|
212
|
-
'password',
|
|
213
|
-
'salt',
|
|
238
|
+
'enabled',
|
|
214
239
|
'secret',
|
|
215
|
-
'
|
|
240
|
+
'emailChangeDate',
|
|
216
241
|
'newEmailSecret',
|
|
217
|
-
'
|
|
242
|
+
'newEmailAddress',
|
|
218
243
|
'cancelEmailSecret',
|
|
219
|
-
'
|
|
244
|
+
'cancelEmailAddress',
|
|
245
|
+
'recoverSecret',
|
|
246
|
+
'recoverSecretDate',
|
|
247
|
+
'salt',
|
|
248
|
+
'password',
|
|
249
|
+
'passwordTemp',
|
|
250
|
+
'revokeTokenDate',
|
|
251
|
+
'totpSecret',
|
|
220
252
|
];
|
|
221
253
|
private static DEFAULT_ALLOWLIST_DATA: string[] = [];
|
|
222
254
|
|
|
@@ -304,10 +336,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
304
336
|
public static current(
|
|
305
337
|
returnObjectIfNotExist?: boolean
|
|
306
338
|
): (User & UserData) | null {
|
|
307
|
-
|
|
339
|
+
const tilmeld = enforceTilmeld(this);
|
|
340
|
+
if (tilmeld.currentUser == null) {
|
|
308
341
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
309
342
|
}
|
|
310
|
-
return
|
|
343
|
+
return tilmeld.currentUser;
|
|
311
344
|
}
|
|
312
345
|
|
|
313
346
|
/**
|
|
@@ -320,7 +353,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
320
353
|
recoveryType: 'username' | 'password';
|
|
321
354
|
account: string;
|
|
322
355
|
}): Promise<{ result: boolean; message: string }> {
|
|
323
|
-
|
|
356
|
+
const tilmeld = enforceTilmeld(this);
|
|
357
|
+
if (!tilmeld.config.pwRecovery) {
|
|
324
358
|
return {
|
|
325
359
|
result: false,
|
|
326
360
|
message: 'Account recovery is not allowed.',
|
|
@@ -330,14 +364,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
330
364
|
let user: User & UserData;
|
|
331
365
|
const options: EmailOptions = {};
|
|
332
366
|
|
|
333
|
-
if (
|
|
334
|
-
!this.tilmeld.config.emailUsernames &&
|
|
335
|
-
data.recoveryType === 'username'
|
|
336
|
-
) {
|
|
367
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
337
368
|
// Create a username recovery email.
|
|
338
369
|
|
|
339
370
|
const getUser = await this.nymph.getEntity(
|
|
340
|
-
{ class:
|
|
371
|
+
{ class: tilmeld.User, skipAc: true },
|
|
341
372
|
{
|
|
342
373
|
type: '&',
|
|
343
374
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
@@ -364,7 +395,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
364
395
|
} else if (data.recoveryType === 'password') {
|
|
365
396
|
// Create a password recovery email.
|
|
366
397
|
|
|
367
|
-
const getUser = await
|
|
398
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
368
399
|
|
|
369
400
|
if (getUser.guid == null) {
|
|
370
401
|
return {
|
|
@@ -391,14 +422,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
391
422
|
};
|
|
392
423
|
options.locals = {
|
|
393
424
|
recoverCode: getUser.recoverSecret,
|
|
394
|
-
timeLimit:
|
|
425
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
395
426
|
};
|
|
396
427
|
} else {
|
|
397
428
|
return { result: false, message: 'Invalid recovery type.' };
|
|
398
429
|
}
|
|
399
430
|
|
|
400
431
|
// Send the email.
|
|
401
|
-
if (await
|
|
432
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
402
433
|
return {
|
|
403
434
|
result: true,
|
|
404
435
|
message:
|
|
@@ -421,21 +452,22 @@ export default class User extends AbleObject<UserData> {
|
|
|
421
452
|
secret: string;
|
|
422
453
|
password: string;
|
|
423
454
|
}): Promise<{ result: boolean; message: string }> {
|
|
424
|
-
|
|
455
|
+
const tilmeld = enforceTilmeld(this);
|
|
456
|
+
if (!tilmeld.config.pwRecovery) {
|
|
425
457
|
return {
|
|
426
458
|
result: false,
|
|
427
459
|
message: 'Account recovery is not allowed.',
|
|
428
460
|
};
|
|
429
461
|
}
|
|
430
462
|
|
|
431
|
-
const user = await
|
|
463
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
432
464
|
|
|
433
465
|
if (
|
|
434
466
|
user.guid == null ||
|
|
435
467
|
user.recoverSecret == null ||
|
|
436
468
|
data.secret !== user.recoverSecret ||
|
|
437
469
|
strtotime(
|
|
438
|
-
'+' +
|
|
470
|
+
'+' + tilmeld.config.pwRecoveryTimeLimit,
|
|
439
471
|
Math.floor((user.recoverSecretDate ?? 0) / 1000)
|
|
440
472
|
) *
|
|
441
473
|
1000 <
|
|
@@ -466,27 +498,30 @@ export default class User extends AbleObject<UserData> {
|
|
|
466
498
|
}
|
|
467
499
|
|
|
468
500
|
public static getClientConfig() {
|
|
501
|
+
const tilmeld = enforceTilmeld(this);
|
|
469
502
|
return {
|
|
470
|
-
regFields:
|
|
471
|
-
userFields:
|
|
472
|
-
emailUsernames:
|
|
473
|
-
allowRegistration:
|
|
474
|
-
allowUsernameChange:
|
|
475
|
-
pwRecovery:
|
|
476
|
-
verifyEmail:
|
|
477
|
-
unverifiedAccess:
|
|
503
|
+
regFields: tilmeld.config.regFields,
|
|
504
|
+
userFields: tilmeld.config.userFields,
|
|
505
|
+
emailUsernames: tilmeld.config.emailUsernames,
|
|
506
|
+
allowRegistration: tilmeld.config.allowRegistration,
|
|
507
|
+
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
508
|
+
pwRecovery: tilmeld.config.pwRecovery,
|
|
509
|
+
verifyEmail: tilmeld.config.verifyEmail,
|
|
510
|
+
unverifiedAccess: tilmeld.config.unverifiedAccess,
|
|
478
511
|
};
|
|
479
512
|
}
|
|
480
513
|
|
|
481
514
|
public static async loginUser(data: {
|
|
482
515
|
username: string;
|
|
483
516
|
password: string;
|
|
517
|
+
code?: string;
|
|
484
518
|
additionalData?: { [k: string]: any };
|
|
485
519
|
}) {
|
|
520
|
+
const tilmeld = enforceTilmeld(this);
|
|
486
521
|
if (!('username' in data) || !data.username.length) {
|
|
487
522
|
return { result: false, message: 'Incorrect login/password.' };
|
|
488
523
|
}
|
|
489
|
-
const user = await
|
|
524
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
490
525
|
const result: { result: boolean; message: string; user?: User & UserData } =
|
|
491
526
|
await user.$login(data);
|
|
492
527
|
if (result.result) {
|
|
@@ -499,9 +534,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
499
534
|
public async $login(data: {
|
|
500
535
|
username: string;
|
|
501
536
|
password: string;
|
|
537
|
+
code?: string;
|
|
502
538
|
additionalData?: { [k: string]: any };
|
|
503
539
|
}) {
|
|
504
|
-
const tilmeld = this
|
|
540
|
+
const tilmeld = enforceTilmeld(this);
|
|
505
541
|
if (this.guid == null) {
|
|
506
542
|
return { result: false, message: 'Incorrect login/password.' };
|
|
507
543
|
}
|
|
@@ -515,6 +551,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
515
551
|
return { result: false, message: 'Incorrect login/password.' };
|
|
516
552
|
}
|
|
517
553
|
|
|
554
|
+
if (this.$data.totpSecret != null) {
|
|
555
|
+
if (
|
|
556
|
+
data.code == null ||
|
|
557
|
+
typeof data.code !== 'string' ||
|
|
558
|
+
data.code.length !== 6
|
|
559
|
+
) {
|
|
560
|
+
return {
|
|
561
|
+
result: false,
|
|
562
|
+
needTOTP: true,
|
|
563
|
+
message: 'You need to provide a 2FA code.',
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!this.$checkTOTPCode(data.code)) {
|
|
568
|
+
return { result: false, message: 'Incorrect 2FA code.' };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
518
572
|
try {
|
|
519
573
|
for (let callback of (this.constructor as typeof User)
|
|
520
574
|
.beforeLoginCallbacks) {
|
|
@@ -545,12 +599,84 @@ export default class User extends AbleObject<UserData> {
|
|
|
545
599
|
return { result: true, message: 'You are logged in.' };
|
|
546
600
|
}
|
|
547
601
|
|
|
602
|
+
public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
|
|
603
|
+
const tilmeld = enforceTilmeld(this);
|
|
604
|
+
if (this.guid == null) {
|
|
605
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let user = tilmeld.User.current();
|
|
609
|
+
|
|
610
|
+
if (!user || user.guid == null) {
|
|
611
|
+
return { result: false, message: "You're not logged in." };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
615
|
+
|
|
616
|
+
if (isCurrentUser) {
|
|
617
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const toAbilities = this.$data.abilities || [];
|
|
621
|
+
const fromAbilities = user.abilities || [];
|
|
622
|
+
|
|
623
|
+
if (
|
|
624
|
+
fromAbilities.indexOf('tilmeld/switch') === -1 &&
|
|
625
|
+
fromAbilities.indexOf('system/admin') === -1
|
|
626
|
+
) {
|
|
627
|
+
return {
|
|
628
|
+
result: false,
|
|
629
|
+
message: "You don't have permission to switch users.",
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (
|
|
634
|
+
toAbilities.indexOf('system/admin') !== -1 ||
|
|
635
|
+
toAbilities.indexOf('tilmeld/admin') !== -1
|
|
636
|
+
) {
|
|
637
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
for (let callback of (this.constructor as typeof User)
|
|
642
|
+
.beforeLoginCallbacks) {
|
|
643
|
+
if (callback) {
|
|
644
|
+
await callback(this, {
|
|
645
|
+
username: this.$data.username || '',
|
|
646
|
+
password: '',
|
|
647
|
+
...(data || {}),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
} catch (e: any) {
|
|
652
|
+
return {
|
|
653
|
+
result: false,
|
|
654
|
+
message: e.message,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Authentication was successful, attempt to login.
|
|
659
|
+
if (!tilmeld.loginSwitch(this, true)) {
|
|
660
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
for (let callback of (this.constructor as typeof User)
|
|
664
|
+
.afterLoginCallbacks) {
|
|
665
|
+
if (callback) {
|
|
666
|
+
await callback(this);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Login was successful.
|
|
671
|
+
return { result: true, message: 'You are logged in.' };
|
|
672
|
+
}
|
|
673
|
+
|
|
548
674
|
/**
|
|
549
675
|
* Log a user out of the system.
|
|
550
676
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
551
677
|
*/
|
|
552
678
|
public async $logout() {
|
|
553
|
-
const tilmeld = this
|
|
679
|
+
const tilmeld = enforceTilmeld(this);
|
|
554
680
|
|
|
555
681
|
try {
|
|
556
682
|
for (let callback of (this.constructor as typeof User)
|
|
@@ -629,17 +755,39 @@ export default class User extends AbleObject<UserData> {
|
|
|
629
755
|
}
|
|
630
756
|
|
|
631
757
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
632
|
-
const tilmeld = this
|
|
758
|
+
const tilmeld = enforceTilmeld(this);
|
|
633
759
|
this.$referenceWake();
|
|
634
760
|
|
|
635
761
|
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
762
|
+
'abilities' in input.data &&
|
|
763
|
+
input.data.abilities?.includes('system/admin') !==
|
|
764
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
639
765
|
!tilmeld.gatekeeper('system/admin')
|
|
640
766
|
) {
|
|
641
767
|
throw new BadDataError(
|
|
642
|
-
"You don't have the authority to
|
|
768
|
+
"You don't have the authority to grant or revoke system/admin."
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (
|
|
773
|
+
'abilities' in input.data &&
|
|
774
|
+
input.data.abilities?.includes('tilmeld/admin') !==
|
|
775
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
776
|
+
!tilmeld.gatekeeper('system/admin')
|
|
777
|
+
) {
|
|
778
|
+
throw new BadDataError(
|
|
779
|
+
"You don't have the authority to grant or revoke tilmeld/admin."
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (
|
|
784
|
+
'abilities' in input.data &&
|
|
785
|
+
input.data.abilities?.includes('tilmeld/switch') !==
|
|
786
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
787
|
+
!tilmeld.gatekeeper('system/admin')
|
|
788
|
+
) {
|
|
789
|
+
throw new BadDataError(
|
|
790
|
+
"You don't have the authority to grant or revoke tilmeld/switch."
|
|
643
791
|
);
|
|
644
792
|
}
|
|
645
793
|
|
|
@@ -648,17 +796,39 @@ export default class User extends AbleObject<UserData> {
|
|
|
648
796
|
}
|
|
649
797
|
|
|
650
798
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
651
|
-
const tilmeld = this
|
|
799
|
+
const tilmeld = enforceTilmeld(this);
|
|
652
800
|
this.$referenceWake();
|
|
653
801
|
|
|
654
802
|
if (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
803
|
+
'abilities' in patch.set &&
|
|
804
|
+
patch.set.abilities?.includes('system/admin') !==
|
|
805
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
806
|
+
!tilmeld.gatekeeper('system/admin')
|
|
807
|
+
) {
|
|
808
|
+
throw new BadDataError(
|
|
809
|
+
"You don't have the authority to grant or revoke system/admin."
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (
|
|
814
|
+
'abilities' in patch.set &&
|
|
815
|
+
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
816
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
658
817
|
!tilmeld.gatekeeper('system/admin')
|
|
659
818
|
) {
|
|
660
819
|
throw new BadDataError(
|
|
661
|
-
"You don't have the authority to
|
|
820
|
+
"You don't have the authority to grant or revoke tilmeld/admin."
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (
|
|
825
|
+
'abilities' in patch.set &&
|
|
826
|
+
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
827
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
828
|
+
!tilmeld.gatekeeper('system/admin')
|
|
829
|
+
) {
|
|
830
|
+
throw new BadDataError(
|
|
831
|
+
"You don't have the authority to grant or revoke tilmeld/switch."
|
|
662
832
|
);
|
|
663
833
|
}
|
|
664
834
|
|
|
@@ -677,7 +847,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
677
847
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
678
848
|
*/
|
|
679
849
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
680
|
-
const tilmeld = this
|
|
850
|
+
const tilmeld = enforceTilmeld(this);
|
|
681
851
|
let user = givenUser ?? tilmeld.User.current();
|
|
682
852
|
|
|
683
853
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
@@ -701,8 +871,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
701
871
|
// Users can check to see what abilities they have.
|
|
702
872
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
703
873
|
this.$clientEnabledMethods.push('$changePassword');
|
|
874
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
704
875
|
this.$clientEnabledMethods.push('$logout');
|
|
705
876
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
877
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
878
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
879
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
880
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
706
881
|
}
|
|
707
882
|
|
|
708
883
|
if (
|
|
@@ -711,7 +886,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
711
886
|
abilities.indexOf('system/admin') !== -1)
|
|
712
887
|
) {
|
|
713
888
|
// Users who can edit other users can see most of their data.
|
|
714
|
-
this.$privateData = ['password', 'salt'];
|
|
889
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
715
890
|
this.$allowlistData = undefined;
|
|
716
891
|
} else if (isCurrentUser || isNewUser) {
|
|
717
892
|
// Users can see their own data, and edit some of it.
|
|
@@ -732,17 +907,45 @@ export default class User extends AbleObject<UserData> {
|
|
|
732
907
|
this.$allowlistData.push('phone');
|
|
733
908
|
}
|
|
734
909
|
this.$privateData = [
|
|
910
|
+
// The things that are commented are there to show what is _not_ private.
|
|
911
|
+
// 'abilities',
|
|
912
|
+
// 'username',
|
|
913
|
+
// 'nameFirst',
|
|
914
|
+
// 'nameMiddle',
|
|
915
|
+
// 'nameLast',
|
|
916
|
+
// 'name',
|
|
917
|
+
// 'email',
|
|
918
|
+
// 'avatar',
|
|
919
|
+
// 'phone',
|
|
920
|
+
// 'group',
|
|
921
|
+
// 'groups',
|
|
922
|
+
// 'inheritAbilities',
|
|
923
|
+
'enabled',
|
|
735
924
|
'secret',
|
|
925
|
+
'emailChangeDate',
|
|
736
926
|
'newEmailSecret',
|
|
737
|
-
'
|
|
927
|
+
// 'newEmailAddress',
|
|
738
928
|
'cancelEmailSecret',
|
|
739
|
-
'
|
|
929
|
+
'cancelEmailAddress',
|
|
740
930
|
'recoverSecret',
|
|
741
931
|
'recoverSecretDate',
|
|
742
|
-
'password',
|
|
743
932
|
'salt',
|
|
933
|
+
'password',
|
|
934
|
+
'passwordTemp',
|
|
935
|
+
// 'revokeTokenDate',
|
|
936
|
+
'totpSecret',
|
|
744
937
|
];
|
|
745
938
|
}
|
|
939
|
+
|
|
940
|
+
if (
|
|
941
|
+
user != null &&
|
|
942
|
+
!isCurrentUser &&
|
|
943
|
+
(abilities.indexOf('tilmeld/switch') !== -1 ||
|
|
944
|
+
abilities.indexOf('system/admin') !== -1)
|
|
945
|
+
) {
|
|
946
|
+
// Users with this ability can switch to other users.
|
|
947
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
948
|
+
}
|
|
746
949
|
}
|
|
747
950
|
|
|
748
951
|
/**
|
|
@@ -760,7 +963,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
760
963
|
* @returns True or false.
|
|
761
964
|
*/
|
|
762
965
|
public $gatekeeper(ability?: string): boolean {
|
|
763
|
-
const tilmeld = this
|
|
966
|
+
const tilmeld = enforceTilmeld(this);
|
|
764
967
|
if (ability == null) {
|
|
765
968
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
766
969
|
}
|
|
@@ -787,7 +990,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
787
990
|
}
|
|
788
991
|
return (
|
|
789
992
|
(ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
790
|
-
!!this.$
|
|
993
|
+
!!this.$data.abilities?.includes('system/admin')
|
|
791
994
|
);
|
|
792
995
|
}
|
|
793
996
|
|
|
@@ -804,7 +1007,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
804
1007
|
* @returns True on success, false on failure.
|
|
805
1008
|
*/
|
|
806
1009
|
public async $sendEmailVerification() {
|
|
807
|
-
const tilmeld = this
|
|
1010
|
+
const tilmeld = enforceTilmeld(this);
|
|
808
1011
|
if (this.guid == null) {
|
|
809
1012
|
return false;
|
|
810
1013
|
}
|
|
@@ -917,7 +1120,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
917
1120
|
* @returns True if the passwords match, otherwise false.
|
|
918
1121
|
*/
|
|
919
1122
|
public $checkPassword(password: string) {
|
|
920
|
-
const tilmeld = this
|
|
1123
|
+
const tilmeld = enforceTilmeld(this);
|
|
921
1124
|
switch (tilmeld.config.pwMethod) {
|
|
922
1125
|
case 'plain':
|
|
923
1126
|
return this.$data.password === password;
|
|
@@ -1006,6 +1209,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1006
1209
|
public async $changePassword(data: {
|
|
1007
1210
|
newPassword: string;
|
|
1008
1211
|
currentPassword: string;
|
|
1212
|
+
revokeCurrentTokens?: boolean;
|
|
1009
1213
|
}): Promise<{ result: boolean; message: string }> {
|
|
1010
1214
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1011
1215
|
return { result: false, message: 'Please specify a password.' };
|
|
@@ -1014,7 +1218,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
1014
1218
|
return { result: false, message: 'Incorrect password.' };
|
|
1015
1219
|
}
|
|
1016
1220
|
this.$data.passwordTemp = data.newPassword;
|
|
1221
|
+
if (data.revokeCurrentTokens) {
|
|
1222
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1223
|
+
}
|
|
1017
1224
|
if (await this.$save()) {
|
|
1225
|
+
if (data.revokeCurrentTokens) {
|
|
1226
|
+
const tilmeld = enforceTilmeld(this);
|
|
1227
|
+
tilmeld.login(this, true);
|
|
1228
|
+
}
|
|
1018
1229
|
return { result: true, message: 'Your password has been changed.' };
|
|
1019
1230
|
} else {
|
|
1020
1231
|
return { result: false, message: "Couldn't save new password." };
|
|
@@ -1028,7 +1239,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1028
1239
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1029
1240
|
*/
|
|
1030
1241
|
public $password(password: string) {
|
|
1031
|
-
const tilmeld = this
|
|
1242
|
+
const tilmeld = enforceTilmeld(this);
|
|
1032
1243
|
switch (tilmeld.config.pwMethod) {
|
|
1033
1244
|
case 'plain':
|
|
1034
1245
|
delete this.$data.salt;
|
|
@@ -1049,13 +1260,216 @@ export default class User extends AbleObject<UserData> {
|
|
|
1049
1260
|
return this.$data.password;
|
|
1050
1261
|
}
|
|
1051
1262
|
|
|
1263
|
+
/**
|
|
1264
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
1265
|
+
*
|
|
1266
|
+
* @param data The input data from the client.
|
|
1267
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1268
|
+
*/
|
|
1269
|
+
public async $revokeCurrentTokens(data: {
|
|
1270
|
+
password: string;
|
|
1271
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1272
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1273
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1277
|
+
|
|
1278
|
+
if (await this.$save()) {
|
|
1279
|
+
const tilmeld = enforceTilmeld(this);
|
|
1280
|
+
tilmeld.login(this, true);
|
|
1281
|
+
return {
|
|
1282
|
+
result: true,
|
|
1283
|
+
message: 'You have logged out of all other sessions.',
|
|
1284
|
+
};
|
|
1285
|
+
} else {
|
|
1286
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
public async $hasTOTPSecret() {
|
|
1291
|
+
return this.$data.totpSecret != null;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Check the given code against the user's TOTP secret.
|
|
1296
|
+
*
|
|
1297
|
+
* @param code The code in question.
|
|
1298
|
+
* @returns True if the code is valid, otherwise false.
|
|
1299
|
+
*/
|
|
1300
|
+
public $checkTOTPCode(code: string) {
|
|
1301
|
+
if (this.$data.totpSecret == null) {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const tilmeld = enforceTilmeld(this);
|
|
1306
|
+
|
|
1307
|
+
const totp = new TOTP({
|
|
1308
|
+
issuer: tilmeld.config.appName,
|
|
1309
|
+
label: this.$data.username,
|
|
1310
|
+
algorithm: 'SHA1',
|
|
1311
|
+
digits: 6,
|
|
1312
|
+
period: 30,
|
|
1313
|
+
secret: this.$data.totpSecret,
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1321
|
+
*
|
|
1322
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1323
|
+
*/
|
|
1324
|
+
public async $getNewTOTPSecret() {
|
|
1325
|
+
const tilmeld = enforceTilmeld(this);
|
|
1326
|
+
|
|
1327
|
+
if (this.$data.totpSecret != null) {
|
|
1328
|
+
throw new BadDataError('You already have a 2FA secret.');
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const secret = new Secret();
|
|
1332
|
+
|
|
1333
|
+
const totp = new TOTP({
|
|
1334
|
+
issuer: tilmeld.config.appName,
|
|
1335
|
+
label: this.$data.username,
|
|
1336
|
+
algorithm: 'SHA1',
|
|
1337
|
+
digits: 6,
|
|
1338
|
+
period: 30,
|
|
1339
|
+
secret,
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
const uri = totp.toString();
|
|
1343
|
+
|
|
1344
|
+
const qrcode = await toDataURL(uri);
|
|
1345
|
+
|
|
1346
|
+
return {
|
|
1347
|
+
uri,
|
|
1348
|
+
qrcode,
|
|
1349
|
+
secret: secret.base32,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1355
|
+
*
|
|
1356
|
+
* @param data The input data from the client.
|
|
1357
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1358
|
+
*/
|
|
1359
|
+
public async $saveTOTPSecret(data: {
|
|
1360
|
+
password: string;
|
|
1361
|
+
secret: string;
|
|
1362
|
+
code: string;
|
|
1363
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1364
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1365
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
if (this.$data.totpSecret != null) {
|
|
1369
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (
|
|
1373
|
+
data.secret == null ||
|
|
1374
|
+
typeof data.secret !== 'string' ||
|
|
1375
|
+
data.secret === ''
|
|
1376
|
+
) {
|
|
1377
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (
|
|
1381
|
+
data.code == null ||
|
|
1382
|
+
typeof data.code !== 'string' ||
|
|
1383
|
+
data.code.length !== 6
|
|
1384
|
+
) {
|
|
1385
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
const tilmeld = enforceTilmeld(this);
|
|
1389
|
+
|
|
1390
|
+
const totp = new TOTP({
|
|
1391
|
+
issuer: tilmeld.config.appName,
|
|
1392
|
+
label: this.$data.username,
|
|
1393
|
+
algorithm: 'SHA1',
|
|
1394
|
+
digits: 6,
|
|
1395
|
+
period: 30,
|
|
1396
|
+
secret: data.secret,
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1400
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
this.$data.totpSecret = data.secret;
|
|
1404
|
+
|
|
1405
|
+
if (await this.$save()) {
|
|
1406
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1407
|
+
} else {
|
|
1408
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1414
|
+
* account.
|
|
1415
|
+
*
|
|
1416
|
+
* @param data The input data from the client.
|
|
1417
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1418
|
+
*/
|
|
1419
|
+
public async $removeTOTPSecret(data: {
|
|
1420
|
+
password: string;
|
|
1421
|
+
code: string;
|
|
1422
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1423
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1424
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (this.$data.totpSecret == null) {
|
|
1428
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (
|
|
1432
|
+
data.code == null ||
|
|
1433
|
+
typeof data.code !== 'string' ||
|
|
1434
|
+
data.code.length !== 6
|
|
1435
|
+
) {
|
|
1436
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const tilmeld = enforceTilmeld(this);
|
|
1440
|
+
|
|
1441
|
+
const totp = new TOTP({
|
|
1442
|
+
issuer: tilmeld.config.appName,
|
|
1443
|
+
label: this.$data.username,
|
|
1444
|
+
algorithm: 'SHA1',
|
|
1445
|
+
digits: 6,
|
|
1446
|
+
period: 30,
|
|
1447
|
+
secret: this.$data.totpSecret,
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1451
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
delete this.$data.totpSecret;
|
|
1455
|
+
|
|
1456
|
+
if (await this.$save()) {
|
|
1457
|
+
return {
|
|
1458
|
+
result: true,
|
|
1459
|
+
message: 'Your two factor secret has been removed.',
|
|
1460
|
+
};
|
|
1461
|
+
} else {
|
|
1462
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1052
1466
|
/**
|
|
1053
1467
|
* Check that a username is valid.
|
|
1054
1468
|
*
|
|
1055
1469
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1056
1470
|
*/
|
|
1057
1471
|
public async $checkUsername(): Promise<{ result: boolean; message: string }> {
|
|
1058
|
-
const tilmeld = this
|
|
1472
|
+
const tilmeld = enforceTilmeld(this);
|
|
1059
1473
|
if (!tilmeld.config.emailUsernames) {
|
|
1060
1474
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1061
1475
|
return { result: false, message: 'Please specify a username.' };
|
|
@@ -1109,6 +1523,18 @@ export default class User extends AbleObject<UserData> {
|
|
|
1109
1523
|
return { result: false, message: 'That username is taken.' };
|
|
1110
1524
|
}
|
|
1111
1525
|
|
|
1526
|
+
for (let callback of (this.constructor as typeof User)
|
|
1527
|
+
.checkUsernameCallbacks) {
|
|
1528
|
+
if (callback) {
|
|
1529
|
+
const result = await callback(this, {
|
|
1530
|
+
username: this.$data.username,
|
|
1531
|
+
});
|
|
1532
|
+
if (!result.result) {
|
|
1533
|
+
return { message: 'Username is not available.', ...result };
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1112
1538
|
return {
|
|
1113
1539
|
result: true,
|
|
1114
1540
|
message:
|
|
@@ -1142,7 +1568,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1142
1568
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1143
1569
|
*/
|
|
1144
1570
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
1145
|
-
const tilmeld = this
|
|
1571
|
+
const tilmeld = enforceTilmeld(this);
|
|
1146
1572
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
1147
1573
|
if (tilmeld.config.verifyEmail) {
|
|
1148
1574
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -1187,7 +1613,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1187
1613
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1188
1614
|
*/
|
|
1189
1615
|
public async $checkPhone(): Promise<{ result: boolean; message: string }> {
|
|
1190
|
-
const tilmeld = this
|
|
1616
|
+
const tilmeld = enforceTilmeld(this);
|
|
1191
1617
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
1192
1618
|
return { result: false, message: 'Please specify a phone number.' };
|
|
1193
1619
|
}
|
|
@@ -1226,7 +1652,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1226
1652
|
password: string;
|
|
1227
1653
|
additionalData?: { [k: string]: any };
|
|
1228
1654
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1229
|
-
let tilmeld = this
|
|
1655
|
+
let tilmeld = enforceTilmeld(this);
|
|
1230
1656
|
if (!tilmeld.config.allowRegistration) {
|
|
1231
1657
|
return {
|
|
1232
1658
|
result: false,
|
|
@@ -1288,7 +1714,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1288
1714
|
const nymph = this.$nymph;
|
|
1289
1715
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1290
1716
|
this.$nymph = tnymph;
|
|
1291
|
-
tilmeld = this
|
|
1717
|
+
tilmeld = enforceTilmeld(this);
|
|
1292
1718
|
|
|
1293
1719
|
try {
|
|
1294
1720
|
// Add primary group.
|
|
@@ -1493,7 +1919,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1493
1919
|
}
|
|
1494
1920
|
|
|
1495
1921
|
public async $save() {
|
|
1496
|
-
let tilmeld = this
|
|
1922
|
+
let tilmeld = enforceTilmeld(this);
|
|
1497
1923
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1498
1924
|
return false;
|
|
1499
1925
|
}
|
|
@@ -1501,7 +1927,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1501
1927
|
if (
|
|
1502
1928
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1503
1929
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1504
|
-
this.$
|
|
1930
|
+
this.$data.abilities?.includes('system/admin')
|
|
1505
1931
|
) {
|
|
1506
1932
|
throw new BadDataError(
|
|
1507
1933
|
"You don't have the authority to modify system admins."
|
|
@@ -1691,7 +2117,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1691
2117
|
const nymph = this.$nymph;
|
|
1692
2118
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1693
2119
|
this.$nymph = tnymph;
|
|
1694
|
-
tilmeld = this
|
|
2120
|
+
tilmeld = enforceTilmeld(this);
|
|
1695
2121
|
|
|
1696
2122
|
if (
|
|
1697
2123
|
this.$data.group != null &&
|
|
@@ -1742,9 +2168,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1742
2168
|
this.$gatekeeperCache = undefined;
|
|
1743
2169
|
await tnymph.commit(transaction);
|
|
1744
2170
|
|
|
1745
|
-
if ((nymph
|
|
2171
|
+
if (enforceTilmeld(nymph).User.current(true).$is(this)) {
|
|
1746
2172
|
// Update the user in the session cache.
|
|
1747
|
-
(nymph
|
|
2173
|
+
enforceTilmeld(nymph).fillSession(this);
|
|
1748
2174
|
}
|
|
1749
2175
|
} else {
|
|
1750
2176
|
await tnymph.rollback(transaction);
|
|
@@ -1770,13 +2196,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1770
2196
|
}
|
|
1771
2197
|
|
|
1772
2198
|
public async $delete() {
|
|
1773
|
-
const tilmeld = this
|
|
2199
|
+
const tilmeld = enforceTilmeld(this);
|
|
1774
2200
|
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1775
2201
|
throw new BadDataError("You don't have the authority to delete users.");
|
|
1776
2202
|
}
|
|
1777
2203
|
if (
|
|
1778
2204
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1779
|
-
this.$
|
|
2205
|
+
this.$data.abilities?.includes('system/admin')
|
|
1780
2206
|
) {
|
|
1781
2207
|
throw new BadDataError(
|
|
1782
2208
|
"You don't have the authority to delete system admins."
|
|
@@ -1790,7 +2216,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1790
2216
|
|
|
1791
2217
|
public static on<T extends EventType>(
|
|
1792
2218
|
event: T,
|
|
1793
|
-
callback: T extends '
|
|
2219
|
+
callback: T extends 'checkUsername'
|
|
2220
|
+
? TilmeldCheckUsernameCallback
|
|
2221
|
+
: T extends 'beforeRegister'
|
|
1794
2222
|
? TilmeldBeforeRegisterCallback
|
|
1795
2223
|
: T extends 'afterRegister'
|
|
1796
2224
|
? TilmeldAfterRegisterCallback
|
|
@@ -1804,7 +2232,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1804
2232
|
? TilmeldAfterLogoutCallback
|
|
1805
2233
|
: never
|
|
1806
2234
|
) {
|
|
1807
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2235
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2236
|
+
? 'checkUsernameCallbacks'
|
|
2237
|
+
: T extends 'beforeRegister'
|
|
1808
2238
|
? 'beforeRegisterCallbacks'
|
|
1809
2239
|
: T extends 'afterRegister'
|
|
1810
2240
|
? 'afterRegisterCallbacks'
|
|
@@ -1827,7 +2257,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1827
2257
|
|
|
1828
2258
|
public static off<T extends EventType>(
|
|
1829
2259
|
event: T,
|
|
1830
|
-
callback: T extends '
|
|
2260
|
+
callback: T extends 'checkUsername'
|
|
2261
|
+
? TilmeldCheckUsernameCallback
|
|
2262
|
+
: T extends 'beforeRegister'
|
|
1831
2263
|
? TilmeldBeforeRegisterCallback
|
|
1832
2264
|
: T extends 'afterRegister'
|
|
1833
2265
|
? TilmeldAfterRegisterCallback
|
|
@@ -1841,7 +2273,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1841
2273
|
? TilmeldAfterLogoutCallback
|
|
1842
2274
|
: never
|
|
1843
2275
|
) {
|
|
1844
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2276
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2277
|
+
? 'checkUsernameCallbacks'
|
|
2278
|
+
: T extends 'beforeRegister'
|
|
1845
2279
|
? 'beforeRegisterCallbacks'
|
|
1846
2280
|
: T extends 'afterRegister'
|
|
1847
2281
|
? 'afterRegisterCallbacks'
|