@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/CHANGELOG.md +238 -0
- package/README.md +14 -1
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +4 -7
- package/dist/Group.js +124 -86
- package/dist/Group.js.map +1 -1
- package/dist/Tilmeld.d.ts +12 -9
- package/dist/Tilmeld.js +240 -179
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +69 -13
- package/dist/User.js +492 -169
- package/dist/User.js.map +1 -1
- package/dist/conf/d.d.ts +4 -1
- package/dist/conf/defaults.js +22 -8
- 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 +29 -26
- package/src/AbleObject.ts +1 -1
- package/src/Group.test.ts +1 -1
- package/src/Group.ts +180 -130
- package/src/Tilmeld.ts +355 -244
- package/src/User.test.ts +1 -1
- package/src/User.ts +717 -230
- package/src/conf/d.ts +24 -11
- package/src/conf/defaults.ts +30 -14
- package/src/enforceTilmeld.ts +20 -0
- package/src/index.ts +2 -0
- package/src/tilmeldJoiProps.ts +9 -9
- package/typedoc.json +4 -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,23 +26,33 @@ 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.
|
|
36
48
|
*/
|
|
37
49
|
export type TilmeldBeforeRegisterCallback = (
|
|
38
50
|
user: User & UserData,
|
|
39
|
-
data: { password: string; additionalData?: { [k: string]: any } }
|
|
51
|
+
data: { password: string; additionalData?: { [k: string]: any } },
|
|
40
52
|
) => Promise<void>;
|
|
41
53
|
export type TilmeldAfterRegisterCallback = (
|
|
42
54
|
user: User & UserData,
|
|
43
|
-
result: { loggedin: boolean; message: string }
|
|
55
|
+
result: { loggedin: boolean; message: string },
|
|
44
56
|
) => Promise<void>;
|
|
45
57
|
/**
|
|
46
58
|
* These are run after the authentication checks, but before the login action.
|
|
@@ -51,20 +63,20 @@ export type TilmeldBeforeLoginCallback = (
|
|
|
51
63
|
username: string;
|
|
52
64
|
password: string;
|
|
53
65
|
additionalData?: { [k: string]: any };
|
|
54
|
-
}
|
|
66
|
+
},
|
|
55
67
|
) => Promise<void>;
|
|
56
68
|
/**
|
|
57
69
|
* This is run before the transaction is committed, and you can perform
|
|
58
70
|
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
59
71
|
*/
|
|
60
72
|
export type TilmeldAfterLoginCallback = (
|
|
61
|
-
user: User & UserData
|
|
73
|
+
user: User & UserData,
|
|
62
74
|
) => Promise<void>;
|
|
63
75
|
export type TilmeldBeforeLogoutCallback = (
|
|
64
|
-
user: User & UserData
|
|
76
|
+
user: User & UserData,
|
|
65
77
|
) => Promise<void>;
|
|
66
78
|
export type TilmeldAfterLogoutCallback = (
|
|
67
|
-
user: User & UserData
|
|
79
|
+
user: User & UserData,
|
|
68
80
|
) => Promise<void>;
|
|
69
81
|
|
|
70
82
|
export type UserData = {
|
|
@@ -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
|
|
|
@@ -271,7 +303,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
271
303
|
{
|
|
272
304
|
type: '&',
|
|
273
305
|
ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
274
|
-
}
|
|
306
|
+
},
|
|
275
307
|
);
|
|
276
308
|
if (entity != null) {
|
|
277
309
|
return entity;
|
|
@@ -280,34 +312,33 @@ export default class User extends AbleObject<UserData> {
|
|
|
280
312
|
return entity;
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
static factorySync(
|
|
284
|
-
return super.factorySync(
|
|
315
|
+
static factorySync(): User & UserData {
|
|
316
|
+
return super.factorySync() as User & UserData;
|
|
285
317
|
}
|
|
286
318
|
|
|
287
|
-
constructor(
|
|
288
|
-
super(
|
|
319
|
+
constructor() {
|
|
320
|
+
super();
|
|
289
321
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.$updateDataProtection();
|
|
297
|
-
}
|
|
322
|
+
// Defaults.
|
|
323
|
+
this.$data.enabled = true;
|
|
324
|
+
this.$data.abilities = [];
|
|
325
|
+
this.$data.groups = [];
|
|
326
|
+
this.$data.inheritAbilities = true;
|
|
327
|
+
this.$updateDataProtection();
|
|
298
328
|
}
|
|
299
329
|
|
|
300
330
|
public static current(returnObjectIfNotExist: true): User & UserData;
|
|
301
331
|
public static current(
|
|
302
|
-
returnObjectIfNotExist?: false
|
|
332
|
+
returnObjectIfNotExist?: false,
|
|
303
333
|
): (User & UserData) | null;
|
|
304
334
|
public static current(
|
|
305
|
-
returnObjectIfNotExist?: boolean
|
|
335
|
+
returnObjectIfNotExist?: boolean,
|
|
306
336
|
): (User & UserData) | null {
|
|
307
|
-
|
|
337
|
+
const tilmeld = enforceTilmeld(this);
|
|
338
|
+
if (tilmeld.currentUser == null) {
|
|
308
339
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
309
340
|
}
|
|
310
|
-
return
|
|
341
|
+
return tilmeld.currentUser;
|
|
311
342
|
}
|
|
312
343
|
|
|
313
344
|
/**
|
|
@@ -320,7 +351,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
320
351
|
recoveryType: 'username' | 'password';
|
|
321
352
|
account: string;
|
|
322
353
|
}): Promise<{ result: boolean; message: string }> {
|
|
323
|
-
|
|
354
|
+
const tilmeld = enforceTilmeld(this);
|
|
355
|
+
if (!tilmeld.config.pwRecovery) {
|
|
324
356
|
return {
|
|
325
357
|
result: false,
|
|
326
358
|
message: 'Account recovery is not allowed.',
|
|
@@ -330,18 +362,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
330
362
|
let user: User & UserData;
|
|
331
363
|
const options: EmailOptions = {};
|
|
332
364
|
|
|
333
|
-
if (
|
|
334
|
-
!this.tilmeld.config.emailUsernames &&
|
|
335
|
-
data.recoveryType === 'username'
|
|
336
|
-
) {
|
|
365
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
337
366
|
// Create a username recovery email.
|
|
338
367
|
|
|
339
368
|
const getUser = await this.nymph.getEntity(
|
|
340
|
-
{ class:
|
|
369
|
+
{ class: tilmeld.User, skipAc: true },
|
|
341
370
|
{
|
|
342
371
|
type: '&',
|
|
343
372
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
344
|
-
}
|
|
373
|
+
},
|
|
345
374
|
);
|
|
346
375
|
|
|
347
376
|
if (getUser == null) {
|
|
@@ -364,7 +393,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
364
393
|
} else if (data.recoveryType === 'password') {
|
|
365
394
|
// Create a password recovery email.
|
|
366
395
|
|
|
367
|
-
const getUser = await
|
|
396
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
368
397
|
|
|
369
398
|
if (getUser.guid == null) {
|
|
370
399
|
return {
|
|
@@ -391,14 +420,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
391
420
|
};
|
|
392
421
|
options.locals = {
|
|
393
422
|
recoverCode: getUser.recoverSecret,
|
|
394
|
-
timeLimit:
|
|
423
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
395
424
|
};
|
|
396
425
|
} else {
|
|
397
426
|
return { result: false, message: 'Invalid recovery type.' };
|
|
398
427
|
}
|
|
399
428
|
|
|
400
429
|
// Send the email.
|
|
401
|
-
if (await
|
|
430
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
402
431
|
return {
|
|
403
432
|
result: true,
|
|
404
433
|
message:
|
|
@@ -421,22 +450,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
421
450
|
secret: string;
|
|
422
451
|
password: string;
|
|
423
452
|
}): Promise<{ result: boolean; message: string }> {
|
|
424
|
-
|
|
453
|
+
const tilmeld = enforceTilmeld(this);
|
|
454
|
+
if (!tilmeld.config.pwRecovery) {
|
|
425
455
|
return {
|
|
426
456
|
result: false,
|
|
427
457
|
message: 'Account recovery is not allowed.',
|
|
428
458
|
};
|
|
429
459
|
}
|
|
430
460
|
|
|
431
|
-
const user = await
|
|
461
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
432
462
|
|
|
433
463
|
if (
|
|
434
464
|
user.guid == null ||
|
|
435
465
|
user.recoverSecret == null ||
|
|
436
466
|
data.secret !== user.recoverSecret ||
|
|
437
467
|
strtotime(
|
|
438
|
-
'+' +
|
|
439
|
-
Math.floor((user.recoverSecretDate ?? 0) / 1000)
|
|
468
|
+
'+' + tilmeld.config.pwRecoveryTimeLimit,
|
|
469
|
+
Math.floor((user.recoverSecretDate ?? 0) / 1000),
|
|
440
470
|
) *
|
|
441
471
|
1000 <
|
|
442
472
|
Date.now()
|
|
@@ -466,27 +496,30 @@ export default class User extends AbleObject<UserData> {
|
|
|
466
496
|
}
|
|
467
497
|
|
|
468
498
|
public static getClientConfig() {
|
|
499
|
+
const tilmeld = enforceTilmeld(this);
|
|
469
500
|
return {
|
|
470
|
-
regFields:
|
|
471
|
-
userFields:
|
|
472
|
-
emailUsernames:
|
|
473
|
-
allowRegistration:
|
|
474
|
-
allowUsernameChange:
|
|
475
|
-
pwRecovery:
|
|
476
|
-
verifyEmail:
|
|
477
|
-
unverifiedAccess:
|
|
501
|
+
regFields: tilmeld.config.regFields,
|
|
502
|
+
userFields: tilmeld.config.userFields,
|
|
503
|
+
emailUsernames: tilmeld.config.emailUsernames,
|
|
504
|
+
allowRegistration: tilmeld.config.allowRegistration,
|
|
505
|
+
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
506
|
+
pwRecovery: tilmeld.config.pwRecovery,
|
|
507
|
+
verifyEmail: tilmeld.config.verifyEmail,
|
|
508
|
+
unverifiedAccess: tilmeld.config.unverifiedAccess,
|
|
478
509
|
};
|
|
479
510
|
}
|
|
480
511
|
|
|
481
512
|
public static async loginUser(data: {
|
|
482
513
|
username: string;
|
|
483
514
|
password: string;
|
|
515
|
+
code?: string;
|
|
484
516
|
additionalData?: { [k: string]: any };
|
|
485
517
|
}) {
|
|
518
|
+
const tilmeld = enforceTilmeld(this);
|
|
486
519
|
if (!('username' in data) || !data.username.length) {
|
|
487
520
|
return { result: false, message: 'Incorrect login/password.' };
|
|
488
521
|
}
|
|
489
|
-
const user = await
|
|
522
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
490
523
|
const result: { result: boolean; message: string; user?: User & UserData } =
|
|
491
524
|
await user.$login(data);
|
|
492
525
|
if (result.result) {
|
|
@@ -499,22 +532,41 @@ export default class User extends AbleObject<UserData> {
|
|
|
499
532
|
public async $login(data: {
|
|
500
533
|
username: string;
|
|
501
534
|
password: string;
|
|
535
|
+
code?: string;
|
|
502
536
|
additionalData?: { [k: string]: any };
|
|
503
537
|
}) {
|
|
504
|
-
const tilmeld = this
|
|
538
|
+
const tilmeld = enforceTilmeld(this);
|
|
505
539
|
if (this.guid == null) {
|
|
506
540
|
return { result: false, message: 'Incorrect login/password.' };
|
|
507
541
|
}
|
|
508
542
|
if (!this.$data.enabled) {
|
|
509
543
|
return { result: false, message: 'This user is disabled.' };
|
|
510
544
|
}
|
|
511
|
-
if (this.$gatekeeper()) {
|
|
545
|
+
if (await this.$gatekeeper()) {
|
|
512
546
|
return { result: true, message: 'You are already logged in.' };
|
|
513
547
|
}
|
|
514
548
|
if (!this.$checkPassword(data.password)) {
|
|
515
549
|
return { result: false, message: 'Incorrect login/password.' };
|
|
516
550
|
}
|
|
517
551
|
|
|
552
|
+
if (this.$data.totpSecret != null) {
|
|
553
|
+
if (
|
|
554
|
+
data.code == null ||
|
|
555
|
+
typeof data.code !== 'string' ||
|
|
556
|
+
data.code.length !== 6
|
|
557
|
+
) {
|
|
558
|
+
return {
|
|
559
|
+
result: false,
|
|
560
|
+
needTOTP: true,
|
|
561
|
+
message: 'You need to provide a 2FA code.',
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!this.$checkTOTPCode(data.code)) {
|
|
566
|
+
return { result: false, message: 'Incorrect 2FA code.' };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
518
570
|
try {
|
|
519
571
|
for (let callback of (this.constructor as typeof User)
|
|
520
572
|
.beforeLoginCallbacks) {
|
|
@@ -530,7 +582,79 @@ export default class User extends AbleObject<UserData> {
|
|
|
530
582
|
}
|
|
531
583
|
|
|
532
584
|
// Authentication was successful, attempt to login.
|
|
533
|
-
if (!tilmeld.login(this, true)) {
|
|
585
|
+
if (!(await tilmeld.login(this, true))) {
|
|
586
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
for (let callback of (this.constructor as typeof User)
|
|
590
|
+
.afterLoginCallbacks) {
|
|
591
|
+
if (callback) {
|
|
592
|
+
await callback(this);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Login was successful.
|
|
597
|
+
return { result: true, message: 'You are logged in.' };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
|
|
601
|
+
const tilmeld = enforceTilmeld(this);
|
|
602
|
+
if (this.guid == null) {
|
|
603
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let user = tilmeld.User.current();
|
|
607
|
+
|
|
608
|
+
if (!user || user.guid == null) {
|
|
609
|
+
return { result: false, message: "You're not logged in." };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
613
|
+
|
|
614
|
+
if (isCurrentUser) {
|
|
615
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const toAbilities = this.$data.abilities || [];
|
|
619
|
+
const fromAbilities = user.abilities || [];
|
|
620
|
+
|
|
621
|
+
if (
|
|
622
|
+
fromAbilities.indexOf('tilmeld/switch') === -1 &&
|
|
623
|
+
fromAbilities.indexOf('system/admin') === -1
|
|
624
|
+
) {
|
|
625
|
+
return {
|
|
626
|
+
result: false,
|
|
627
|
+
message: "You don't have permission to switch users.",
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (
|
|
632
|
+
toAbilities.indexOf('system/admin') !== -1 ||
|
|
633
|
+
toAbilities.indexOf('tilmeld/admin') !== -1
|
|
634
|
+
) {
|
|
635
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
for (let callback of (this.constructor as typeof User)
|
|
640
|
+
.beforeLoginCallbacks) {
|
|
641
|
+
if (callback) {
|
|
642
|
+
await callback(this, {
|
|
643
|
+
username: this.$data.username || '',
|
|
644
|
+
password: '',
|
|
645
|
+
...(data || {}),
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} catch (e: any) {
|
|
650
|
+
return {
|
|
651
|
+
result: false,
|
|
652
|
+
message: e.message,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Authentication was successful, attempt to login.
|
|
657
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
534
658
|
return { result: false, message: 'Incorrect login/password.' };
|
|
535
659
|
}
|
|
536
660
|
|
|
@@ -550,7 +674,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
550
674
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
551
675
|
*/
|
|
552
676
|
public async $logout() {
|
|
553
|
-
const tilmeld = this
|
|
677
|
+
const tilmeld = enforceTilmeld(this);
|
|
554
678
|
|
|
555
679
|
try {
|
|
556
680
|
for (let callback of (this.constructor as typeof User)
|
|
@@ -566,7 +690,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
566
690
|
};
|
|
567
691
|
}
|
|
568
692
|
|
|
569
|
-
tilmeld.logout();
|
|
693
|
+
await tilmeld.logout();
|
|
570
694
|
|
|
571
695
|
for (let callback of (this.constructor as typeof User)
|
|
572
696
|
.afterLogoutCallbacks) {
|
|
@@ -592,36 +716,36 @@ export default class User extends AbleObject<UserData> {
|
|
|
592
716
|
);
|
|
593
717
|
}
|
|
594
718
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
public async $getDescendantGroups(): Promise<(Group & GroupData)[]> {
|
|
599
|
-
if (this.$descendantGroups == null) {
|
|
600
|
-
this.$descendantGroups = [];
|
|
601
|
-
if (this.$data.group != null) {
|
|
602
|
-
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
603
|
-
}
|
|
604
|
-
for (let curGroup of this.$data.groups ?? []) {
|
|
605
|
-
this.$descendantGroups = this.$descendantGroups?.concat(
|
|
606
|
-
await curGroup.$getDescendants()
|
|
607
|
-
);
|
|
608
|
-
}
|
|
719
|
+
public $getGid() {
|
|
720
|
+
if (this.$data.group) {
|
|
721
|
+
return this.$data.group.guid;
|
|
609
722
|
}
|
|
610
|
-
return
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
public $getGids() {
|
|
727
|
+
if (this.$data.groups) {
|
|
728
|
+
return this.$data.groups
|
|
729
|
+
.map((group) => group.guid)
|
|
730
|
+
.filter((guid) => guid != null) as string[];
|
|
731
|
+
}
|
|
732
|
+
return null;
|
|
611
733
|
}
|
|
612
734
|
|
|
613
735
|
/**
|
|
614
736
|
* Get the user's group descendants.
|
|
615
737
|
*/
|
|
616
|
-
public $
|
|
738
|
+
public async $getDescendantGroups(): Promise<(Group & GroupData)[]> {
|
|
617
739
|
if (this.$descendantGroups == null) {
|
|
618
740
|
this.$descendantGroups = [];
|
|
619
741
|
if (this.$data.group != null) {
|
|
620
|
-
|
|
742
|
+
await this.$data.group.$wake();
|
|
743
|
+
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
621
744
|
}
|
|
745
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
622
746
|
for (let curGroup of this.$data.groups ?? []) {
|
|
623
747
|
this.$descendantGroups = this.$descendantGroups?.concat(
|
|
624
|
-
curGroup.$
|
|
748
|
+
await curGroup.$getDescendants(),
|
|
625
749
|
);
|
|
626
750
|
}
|
|
627
751
|
}
|
|
@@ -629,18 +753,42 @@ export default class User extends AbleObject<UserData> {
|
|
|
629
753
|
}
|
|
630
754
|
|
|
631
755
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
632
|
-
const tilmeld = this
|
|
633
|
-
this.$
|
|
756
|
+
const tilmeld = enforceTilmeld(this);
|
|
757
|
+
this.$check();
|
|
634
758
|
|
|
635
759
|
if (
|
|
636
760
|
'abilities' in input.data &&
|
|
637
|
-
input.data.abilities
|
|
638
|
-
|
|
639
|
-
tilmeld.
|
|
640
|
-
|
|
761
|
+
input.data.abilities?.includes('system/admin') !==
|
|
762
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
763
|
+
(!tilmeld.currentUser ||
|
|
764
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
765
|
+
) {
|
|
766
|
+
throw new BadDataError(
|
|
767
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (
|
|
772
|
+
'abilities' in input.data &&
|
|
773
|
+
input.data.abilities?.includes('tilmeld/admin') !==
|
|
774
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
775
|
+
(!tilmeld.currentUser ||
|
|
776
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
641
777
|
) {
|
|
642
778
|
throw new BadDataError(
|
|
643
|
-
"You don't have the authority to
|
|
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.currentUser ||
|
|
788
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
789
|
+
) {
|
|
790
|
+
throw new BadDataError(
|
|
791
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
644
792
|
);
|
|
645
793
|
}
|
|
646
794
|
|
|
@@ -649,18 +797,42 @@ export default class User extends AbleObject<UserData> {
|
|
|
649
797
|
}
|
|
650
798
|
|
|
651
799
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
652
|
-
const tilmeld = this
|
|
653
|
-
this.$
|
|
800
|
+
const tilmeld = enforceTilmeld(this);
|
|
801
|
+
this.$check();
|
|
654
802
|
|
|
655
803
|
if (
|
|
656
804
|
'abilities' in patch.set &&
|
|
657
|
-
patch.set.abilities
|
|
658
|
-
|
|
659
|
-
tilmeld.
|
|
660
|
-
|
|
805
|
+
patch.set.abilities?.includes('system/admin') !==
|
|
806
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
807
|
+
(!tilmeld.currentUser ||
|
|
808
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
809
|
+
) {
|
|
810
|
+
throw new BadDataError(
|
|
811
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (
|
|
816
|
+
'abilities' in patch.set &&
|
|
817
|
+
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
818
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
819
|
+
(!tilmeld.currentUser ||
|
|
820
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
821
|
+
) {
|
|
822
|
+
throw new BadDataError(
|
|
823
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (
|
|
828
|
+
'abilities' in patch.set &&
|
|
829
|
+
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
830
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
831
|
+
(!tilmeld.currentUser ||
|
|
832
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
661
833
|
) {
|
|
662
834
|
throw new BadDataError(
|
|
663
|
-
"You don't have the authority to
|
|
835
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
664
836
|
);
|
|
665
837
|
}
|
|
666
838
|
|
|
@@ -679,7 +851,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
679
851
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
680
852
|
*/
|
|
681
853
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
682
|
-
const tilmeld = this
|
|
854
|
+
const tilmeld = enforceTilmeld(this);
|
|
683
855
|
let user = givenUser ?? tilmeld.User.current();
|
|
684
856
|
|
|
685
857
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
@@ -703,8 +875,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
703
875
|
// Users can check to see what abilities they have.
|
|
704
876
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
705
877
|
this.$clientEnabledMethods.push('$changePassword');
|
|
878
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
706
879
|
this.$clientEnabledMethods.push('$logout');
|
|
707
880
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
881
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
882
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
883
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
884
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
708
885
|
}
|
|
709
886
|
|
|
710
887
|
if (
|
|
@@ -713,7 +890,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
713
890
|
abilities.indexOf('system/admin') !== -1)
|
|
714
891
|
) {
|
|
715
892
|
// Users who can edit other users can see most of their data.
|
|
716
|
-
this.$privateData = ['password', 'salt'];
|
|
893
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
894
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
895
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
717
896
|
this.$allowlistData = undefined;
|
|
718
897
|
} else if (isCurrentUser || isNewUser) {
|
|
719
898
|
// Users can see their own data, and edit some of it.
|
|
@@ -734,17 +913,56 @@ export default class User extends AbleObject<UserData> {
|
|
|
734
913
|
this.$allowlistData.push('phone');
|
|
735
914
|
}
|
|
736
915
|
this.$privateData = [
|
|
916
|
+
// The things that are commented are there to show what is _not_ private.
|
|
917
|
+
// 'abilities',
|
|
918
|
+
// 'username',
|
|
919
|
+
// 'nameFirst',
|
|
920
|
+
// 'nameMiddle',
|
|
921
|
+
// 'nameLast',
|
|
922
|
+
// 'name',
|
|
923
|
+
// 'email',
|
|
924
|
+
// 'avatar',
|
|
925
|
+
// 'phone',
|
|
926
|
+
// 'group',
|
|
927
|
+
// 'groups',
|
|
928
|
+
// 'inheritAbilities',
|
|
929
|
+
'enabled',
|
|
737
930
|
'secret',
|
|
931
|
+
'emailChangeDate',
|
|
738
932
|
'newEmailSecret',
|
|
739
|
-
'
|
|
933
|
+
// 'newEmailAddress',
|
|
740
934
|
'cancelEmailSecret',
|
|
741
|
-
'
|
|
935
|
+
'cancelEmailAddress',
|
|
742
936
|
'recoverSecret',
|
|
743
937
|
'recoverSecretDate',
|
|
744
|
-
'password',
|
|
745
938
|
'salt',
|
|
939
|
+
'password',
|
|
940
|
+
'passwordTemp',
|
|
941
|
+
// 'revokeTokenDate',
|
|
942
|
+
'totpSecret',
|
|
746
943
|
];
|
|
747
944
|
}
|
|
945
|
+
|
|
946
|
+
if (
|
|
947
|
+
user != null &&
|
|
948
|
+
!isCurrentUser &&
|
|
949
|
+
(abilities.indexOf('tilmeld/switch') !== -1 ||
|
|
950
|
+
abilities.indexOf('system/admin') !== -1)
|
|
951
|
+
) {
|
|
952
|
+
// Users with this ability can switch to other users.
|
|
953
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
public $updateGroupDataProtection() {
|
|
958
|
+
if (this.$data.group != null && !this.$data.group.$asleep()) {
|
|
959
|
+
this.$data.group.$updateDataProtection();
|
|
960
|
+
}
|
|
961
|
+
for (let group of this.$data.groups ?? []) {
|
|
962
|
+
if (!group.$asleep()) {
|
|
963
|
+
group.$updateDataProtection();
|
|
964
|
+
}
|
|
965
|
+
}
|
|
748
966
|
}
|
|
749
967
|
|
|
750
968
|
/**
|
|
@@ -761,35 +979,43 @@ export default class User extends AbleObject<UserData> {
|
|
|
761
979
|
* @param ability The ability.
|
|
762
980
|
* @returns True or false.
|
|
763
981
|
*/
|
|
764
|
-
public $gatekeeper(ability?: string): boolean {
|
|
765
|
-
|
|
982
|
+
public async $gatekeeper(ability?: string): Promise<boolean> {
|
|
983
|
+
await this.$wake();
|
|
984
|
+
const tilmeld = enforceTilmeld(this);
|
|
766
985
|
if (ability == null) {
|
|
767
986
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
768
987
|
}
|
|
769
988
|
// Check the cache to see if we've already checked this user.
|
|
770
989
|
if (this.$gatekeeperCache == null) {
|
|
771
|
-
|
|
772
|
-
if (this.$data.inheritAbilities) {
|
|
773
|
-
for (let curGroup of this.$data.groups ?? []) {
|
|
774
|
-
if (curGroup.enabled) {
|
|
775
|
-
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
if (
|
|
779
|
-
this.$data.group != null &&
|
|
780
|
-
this.$data.group.cdate != null &&
|
|
781
|
-
this.$data.group.enabled
|
|
782
|
-
) {
|
|
783
|
-
abilities = abilities.concat(this.$data.group.abilities ?? []);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
this.$gatekeeperCache = Object.fromEntries(
|
|
787
|
-
abilities.map((ability) => [ability, true])
|
|
788
|
-
);
|
|
990
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
789
991
|
}
|
|
790
992
|
return (
|
|
791
993
|
(ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
792
|
-
!!this.$
|
|
994
|
+
!!this.$data.abilities?.includes('system/admin')
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Build a gatekeeper cache object.
|
|
1000
|
+
*/
|
|
1001
|
+
public async $getGatekeeperCache() {
|
|
1002
|
+
await this.$wake();
|
|
1003
|
+
let abilities = this.$data.abilities ?? [];
|
|
1004
|
+
if (this.$data.inheritAbilities) {
|
|
1005
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1006
|
+
for (let curGroup of this.$data.groups ?? []) {
|
|
1007
|
+
if (curGroup.enabled) {
|
|
1008
|
+
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
await this.$data.group?.$wake();
|
|
1012
|
+
const group = this.$data.group;
|
|
1013
|
+
if (group != null && group.cdate != null && group.enabled) {
|
|
1014
|
+
abilities = abilities.concat(group.abilities ?? []);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return Object.fromEntries(
|
|
1018
|
+
abilities.map((ability) => [ability, true as true]),
|
|
793
1019
|
);
|
|
794
1020
|
}
|
|
795
1021
|
|
|
@@ -806,7 +1032,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
806
1032
|
* @returns True on success, false on failure.
|
|
807
1033
|
*/
|
|
808
1034
|
public async $sendEmailVerification() {
|
|
809
|
-
const tilmeld = this
|
|
1035
|
+
const tilmeld = enforceTilmeld(this);
|
|
810
1036
|
if (this.guid == null) {
|
|
811
1037
|
return false;
|
|
812
1038
|
}
|
|
@@ -818,7 +1044,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
818
1044
|
|
|
819
1045
|
if (this.$data.secret != null) {
|
|
820
1046
|
const link = `${verifyUrl}?action=verify&id=${encodeURIComponent(
|
|
821
|
-
this.guid
|
|
1047
|
+
this.guid,
|
|
822
1048
|
)}&secret=${encodeURIComponent(this.$data.secret)}`;
|
|
823
1049
|
success =
|
|
824
1050
|
success &&
|
|
@@ -836,13 +1062,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
836
1062
|
verifyLink: link,
|
|
837
1063
|
},
|
|
838
1064
|
},
|
|
839
|
-
this
|
|
1065
|
+
this,
|
|
840
1066
|
));
|
|
841
1067
|
}
|
|
842
1068
|
|
|
843
1069
|
if (this.$data.newEmailSecret != null) {
|
|
844
1070
|
const link = `${verifyUrl}?action=verifychange&id=${encodeURIComponent(
|
|
845
|
-
this.guid
|
|
1071
|
+
this.guid,
|
|
846
1072
|
)}&secret=${encodeURIComponent(this.$data.newEmailSecret)}`;
|
|
847
1073
|
success =
|
|
848
1074
|
success &&
|
|
@@ -862,13 +1088,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
862
1088
|
newEmail: this.$data.newEmailAddress,
|
|
863
1089
|
},
|
|
864
1090
|
},
|
|
865
|
-
this
|
|
1091
|
+
this,
|
|
866
1092
|
));
|
|
867
1093
|
}
|
|
868
1094
|
|
|
869
1095
|
if (this.$data.cancelEmailSecret != null) {
|
|
870
1096
|
const link = `${verifyUrl}?action=cancelchange&id=${encodeURIComponent(
|
|
871
|
-
this.guid
|
|
1097
|
+
this.guid,
|
|
872
1098
|
)}&secret=${encodeURIComponent(this.$data.cancelEmailSecret)}`;
|
|
873
1099
|
success =
|
|
874
1100
|
success &&
|
|
@@ -888,7 +1114,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
888
1114
|
newEmail: this.$data.newEmailAddress ?? this.$data.email,
|
|
889
1115
|
},
|
|
890
1116
|
},
|
|
891
|
-
this
|
|
1117
|
+
this,
|
|
892
1118
|
));
|
|
893
1119
|
}
|
|
894
1120
|
|
|
@@ -901,13 +1127,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
901
1127
|
* @param group The group.
|
|
902
1128
|
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
903
1129
|
*/
|
|
904
|
-
public $addGroup(group: Group & GroupData) {
|
|
905
|
-
|
|
906
|
-
|
|
1130
|
+
public async $addGroup(group: Group & GroupData) {
|
|
1131
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1132
|
+
let groups = this.$data.groups;
|
|
1133
|
+
if (groups == null) {
|
|
1134
|
+
groups = [];
|
|
907
1135
|
}
|
|
908
|
-
if (!group.$inArray(
|
|
909
|
-
|
|
910
|
-
|
|
1136
|
+
if (!group.$inArray(groups)) {
|
|
1137
|
+
groups.push(group);
|
|
1138
|
+
this.$data.groups = groups;
|
|
1139
|
+
return groups;
|
|
911
1140
|
}
|
|
912
1141
|
return true;
|
|
913
1142
|
}
|
|
@@ -919,7 +1148,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
919
1148
|
* @returns True if the passwords match, otherwise false.
|
|
920
1149
|
*/
|
|
921
1150
|
public $checkPassword(password: string) {
|
|
922
|
-
const tilmeld = this
|
|
1151
|
+
const tilmeld = enforceTilmeld(this);
|
|
923
1152
|
switch (tilmeld.config.pwMethod) {
|
|
924
1153
|
case 'plain':
|
|
925
1154
|
return this.$data.password === password;
|
|
@@ -940,10 +1169,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
940
1169
|
* @param group The group.
|
|
941
1170
|
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
942
1171
|
*/
|
|
943
|
-
public $delGroup(group: Group & GroupData) {
|
|
944
|
-
|
|
1172
|
+
public async $delGroup(group: Group & GroupData) {
|
|
1173
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1174
|
+
let groups = this.$data.groups || [];
|
|
1175
|
+
if (this.$data.groups != null && group.$inArray(groups)) {
|
|
945
1176
|
const newGroups: (Group & GroupData)[] = [];
|
|
946
|
-
for (let curGroup of
|
|
1177
|
+
for (let curGroup of groups) {
|
|
947
1178
|
if (!group.$is(curGroup)) {
|
|
948
1179
|
newGroups.push(curGroup);
|
|
949
1180
|
}
|
|
@@ -960,7 +1191,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
960
1191
|
* @param group The group, or the group's GUID.
|
|
961
1192
|
* @returns True or false.
|
|
962
1193
|
*/
|
|
963
|
-
public $inGroup(group: (Group & GroupData) | string) {
|
|
1194
|
+
public async $inGroup(group: (Group & GroupData) | string) {
|
|
964
1195
|
if (typeof group === 'string') {
|
|
965
1196
|
if (this.$data.group?.guid === group) {
|
|
966
1197
|
return true;
|
|
@@ -983,16 +1214,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
983
1214
|
* @param group The group, or the group's GUID.
|
|
984
1215
|
* @returns True or false.
|
|
985
1216
|
*/
|
|
986
|
-
public $isDescendant(group: (Group & GroupData) | string) {
|
|
1217
|
+
public async $isDescendant(group: (Group & GroupData) | string) {
|
|
987
1218
|
// Check to see if the user is in a descendant group of the given group.
|
|
1219
|
+
await this.$data.group?.$wake();
|
|
988
1220
|
if (
|
|
1221
|
+
this.$data.group != null &&
|
|
989
1222
|
this.$data.group?.cdate != null &&
|
|
990
|
-
this.$data.group
|
|
1223
|
+
(await this.$data.group?.$isDescendant(group))
|
|
991
1224
|
) {
|
|
992
1225
|
return true;
|
|
993
1226
|
}
|
|
1227
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
994
1228
|
for (let curGroup of this.$data.groups ?? []) {
|
|
995
|
-
if (curGroup.$isDescendant(group)) {
|
|
1229
|
+
if (await curGroup.$isDescendant(group)) {
|
|
996
1230
|
return true;
|
|
997
1231
|
}
|
|
998
1232
|
}
|
|
@@ -1008,6 +1242,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1008
1242
|
public async $changePassword(data: {
|
|
1009
1243
|
newPassword: string;
|
|
1010
1244
|
currentPassword: string;
|
|
1245
|
+
revokeCurrentTokens?: boolean;
|
|
1011
1246
|
}): Promise<{ result: boolean; message: string }> {
|
|
1012
1247
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1013
1248
|
return { result: false, message: 'Please specify a password.' };
|
|
@@ -1016,7 +1251,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
1016
1251
|
return { result: false, message: 'Incorrect password.' };
|
|
1017
1252
|
}
|
|
1018
1253
|
this.$data.passwordTemp = data.newPassword;
|
|
1254
|
+
if (data.revokeCurrentTokens) {
|
|
1255
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1256
|
+
}
|
|
1019
1257
|
if (await this.$save()) {
|
|
1258
|
+
if (data.revokeCurrentTokens) {
|
|
1259
|
+
const tilmeld = enforceTilmeld(this);
|
|
1260
|
+
await tilmeld.login(this, true);
|
|
1261
|
+
}
|
|
1020
1262
|
return { result: true, message: 'Your password has been changed.' };
|
|
1021
1263
|
} else {
|
|
1022
1264
|
return { result: false, message: "Couldn't save new password." };
|
|
@@ -1030,7 +1272,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1030
1272
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1031
1273
|
*/
|
|
1032
1274
|
public $password(password: string) {
|
|
1033
|
-
const tilmeld = this
|
|
1275
|
+
const tilmeld = enforceTilmeld(this);
|
|
1034
1276
|
switch (tilmeld.config.pwMethod) {
|
|
1035
1277
|
case 'plain':
|
|
1036
1278
|
delete this.$data.salt;
|
|
@@ -1044,28 +1286,244 @@ export default class User extends AbleObject<UserData> {
|
|
|
1044
1286
|
default:
|
|
1045
1287
|
this.$data.salt = nanoid();
|
|
1046
1288
|
this.$data.password = sha256(password + this.$data.salt).toString(
|
|
1047
|
-
Base64
|
|
1289
|
+
Base64,
|
|
1048
1290
|
);
|
|
1049
1291
|
break;
|
|
1050
1292
|
}
|
|
1051
1293
|
return this.$data.password;
|
|
1052
1294
|
}
|
|
1053
1295
|
|
|
1296
|
+
/**
|
|
1297
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
1298
|
+
*
|
|
1299
|
+
* @param data The input data from the client.
|
|
1300
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1301
|
+
*/
|
|
1302
|
+
public async $revokeCurrentTokens(data: {
|
|
1303
|
+
password: string;
|
|
1304
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1305
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1306
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1310
|
+
|
|
1311
|
+
if (await this.$save()) {
|
|
1312
|
+
const tilmeld = enforceTilmeld(this);
|
|
1313
|
+
await tilmeld.login(this, true);
|
|
1314
|
+
return {
|
|
1315
|
+
result: true,
|
|
1316
|
+
message: 'You have logged out of all other sessions.',
|
|
1317
|
+
};
|
|
1318
|
+
} else {
|
|
1319
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
public async $hasTOTPSecret() {
|
|
1324
|
+
return this.$data.totpSecret != null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Check the given code against the user's TOTP secret.
|
|
1329
|
+
*
|
|
1330
|
+
* @param code The code in question.
|
|
1331
|
+
* @returns True if the code is valid, otherwise false.
|
|
1332
|
+
*/
|
|
1333
|
+
public $checkTOTPCode(code: string) {
|
|
1334
|
+
if (this.$data.totpSecret == null) {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const tilmeld = enforceTilmeld(this);
|
|
1339
|
+
|
|
1340
|
+
const totp = new TOTP({
|
|
1341
|
+
issuer: tilmeld.config.appName,
|
|
1342
|
+
label: this.$data.username,
|
|
1343
|
+
algorithm: 'SHA1',
|
|
1344
|
+
digits: 6,
|
|
1345
|
+
period: 30,
|
|
1346
|
+
secret: this.$data.totpSecret,
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1354
|
+
*
|
|
1355
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1356
|
+
*/
|
|
1357
|
+
public async $getNewTOTPSecret() {
|
|
1358
|
+
const tilmeld = enforceTilmeld(this);
|
|
1359
|
+
|
|
1360
|
+
if (this.$data.totpSecret != null) {
|
|
1361
|
+
throw new BadDataError('You already have a 2FA secret.');
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const secret = new Secret();
|
|
1365
|
+
|
|
1366
|
+
const totp = new TOTP({
|
|
1367
|
+
issuer: tilmeld.config.appName,
|
|
1368
|
+
label: this.$data.username,
|
|
1369
|
+
algorithm: 'SHA1',
|
|
1370
|
+
digits: 6,
|
|
1371
|
+
period: 30,
|
|
1372
|
+
secret,
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
const uri = totp.toString();
|
|
1376
|
+
|
|
1377
|
+
const qrcode = await toDataURL(uri);
|
|
1378
|
+
|
|
1379
|
+
return {
|
|
1380
|
+
uri,
|
|
1381
|
+
qrcode,
|
|
1382
|
+
secret: secret.base32,
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1388
|
+
*
|
|
1389
|
+
* @param data The input data from the client.
|
|
1390
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1391
|
+
*/
|
|
1392
|
+
public async $saveTOTPSecret(data: {
|
|
1393
|
+
password: string;
|
|
1394
|
+
secret: string;
|
|
1395
|
+
code: string;
|
|
1396
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1397
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1398
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (this.$data.totpSecret != null) {
|
|
1402
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (
|
|
1406
|
+
data.secret == null ||
|
|
1407
|
+
typeof data.secret !== 'string' ||
|
|
1408
|
+
data.secret === ''
|
|
1409
|
+
) {
|
|
1410
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (
|
|
1414
|
+
data.code == null ||
|
|
1415
|
+
typeof data.code !== 'string' ||
|
|
1416
|
+
data.code.length !== 6
|
|
1417
|
+
) {
|
|
1418
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const tilmeld = enforceTilmeld(this);
|
|
1422
|
+
|
|
1423
|
+
const totp = new TOTP({
|
|
1424
|
+
issuer: tilmeld.config.appName,
|
|
1425
|
+
label: this.$data.username,
|
|
1426
|
+
algorithm: 'SHA1',
|
|
1427
|
+
digits: 6,
|
|
1428
|
+
period: 30,
|
|
1429
|
+
secret: data.secret,
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1433
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
this.$data.totpSecret = data.secret;
|
|
1437
|
+
|
|
1438
|
+
if (await this.$save()) {
|
|
1439
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1440
|
+
} else {
|
|
1441
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1447
|
+
* account.
|
|
1448
|
+
*
|
|
1449
|
+
* @param data The input data from the client.
|
|
1450
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1451
|
+
*/
|
|
1452
|
+
public async $removeTOTPSecret(data?: {
|
|
1453
|
+
password: string;
|
|
1454
|
+
code: string;
|
|
1455
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1456
|
+
const tilmeld = enforceTilmeld(this);
|
|
1457
|
+
|
|
1458
|
+
if (data) {
|
|
1459
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1460
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
if (
|
|
1464
|
+
data.code == null ||
|
|
1465
|
+
typeof data.code !== 'string' ||
|
|
1466
|
+
data.code.length !== 6
|
|
1467
|
+
) {
|
|
1468
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
if (this.$data.totpSecret == null) {
|
|
1472
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const totp = new TOTP({
|
|
1476
|
+
issuer: tilmeld.config.appName,
|
|
1477
|
+
label: this.$data.username,
|
|
1478
|
+
algorithm: 'SHA1',
|
|
1479
|
+
digits: 6,
|
|
1480
|
+
period: 30,
|
|
1481
|
+
secret: this.$data.totpSecret,
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1485
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1486
|
+
}
|
|
1487
|
+
} else if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1488
|
+
return {
|
|
1489
|
+
result: false,
|
|
1490
|
+
message: 'You must provide your password and 2FA code.',
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
delete this.$data.totpSecret;
|
|
1495
|
+
|
|
1496
|
+
if (await this.$save()) {
|
|
1497
|
+
return {
|
|
1498
|
+
result: true,
|
|
1499
|
+
message: 'Two factor secret has been removed.',
|
|
1500
|
+
};
|
|
1501
|
+
} else {
|
|
1502
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1054
1506
|
/**
|
|
1055
1507
|
* Check that a username is valid.
|
|
1056
1508
|
*
|
|
1057
1509
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1058
1510
|
*/
|
|
1059
1511
|
public async $checkUsername(): Promise<{ result: boolean; message: string }> {
|
|
1060
|
-
const tilmeld = this
|
|
1512
|
+
const tilmeld = enforceTilmeld(this);
|
|
1061
1513
|
if (!tilmeld.config.emailUsernames) {
|
|
1062
1514
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1063
1515
|
return { result: false, message: 'Please specify a username.' };
|
|
1064
1516
|
}
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1517
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1518
|
+
return {
|
|
1519
|
+
result: false,
|
|
1520
|
+
message:
|
|
1521
|
+
'Usernames must be at least ' +
|
|
1522
|
+
tilmeld.config.minUsernameLength +
|
|
1523
|
+
' characters.',
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
1069
1527
|
return {
|
|
1070
1528
|
result: false,
|
|
1071
1529
|
message:
|
|
@@ -1078,7 +1536,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1078
1536
|
if (
|
|
1079
1537
|
difference(
|
|
1080
1538
|
this.$data.username.split(''),
|
|
1081
|
-
tilmeld.config.validChars.split('')
|
|
1539
|
+
tilmeld.config.validChars.split(''),
|
|
1082
1540
|
).length
|
|
1083
1541
|
) {
|
|
1084
1542
|
return {
|
|
@@ -1105,12 +1563,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
1105
1563
|
}
|
|
1106
1564
|
const test = await this.$nymph.getEntity(
|
|
1107
1565
|
{ class: tilmeld.User, skipAc: true },
|
|
1108
|
-
selector
|
|
1566
|
+
selector,
|
|
1109
1567
|
);
|
|
1110
1568
|
if (test != null) {
|
|
1111
1569
|
return { result: false, message: 'That username is taken.' };
|
|
1112
1570
|
}
|
|
1113
1571
|
|
|
1572
|
+
for (let callback of (this.constructor as typeof User)
|
|
1573
|
+
.checkUsernameCallbacks) {
|
|
1574
|
+
if (callback) {
|
|
1575
|
+
const result = await callback(this, {
|
|
1576
|
+
username: this.$data.username,
|
|
1577
|
+
});
|
|
1578
|
+
if (!result.result) {
|
|
1579
|
+
return { message: 'Username is not available.', ...result };
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1114
1584
|
return {
|
|
1115
1585
|
result: true,
|
|
1116
1586
|
message:
|
|
@@ -1121,10 +1591,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1121
1591
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1122
1592
|
return { result: false, message: 'Please specify an email.' };
|
|
1123
1593
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1594
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1595
|
+
return {
|
|
1596
|
+
result: false,
|
|
1597
|
+
message:
|
|
1598
|
+
'Emails must be at least ' +
|
|
1599
|
+
tilmeld.config.minUsernameLength +
|
|
1600
|
+
' characters.',
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
1128
1604
|
return {
|
|
1129
1605
|
result: false,
|
|
1130
1606
|
message:
|
|
@@ -1144,7 +1620,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1144
1620
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1145
1621
|
*/
|
|
1146
1622
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
1147
|
-
const tilmeld = this
|
|
1623
|
+
const tilmeld = enforceTilmeld(this);
|
|
1148
1624
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
1149
1625
|
if (tilmeld.config.verifyEmail) {
|
|
1150
1626
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -1167,7 +1643,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1167
1643
|
}
|
|
1168
1644
|
const test = await this.$nymph.getEntity(
|
|
1169
1645
|
{ class: tilmeld.User, skipAc: true },
|
|
1170
|
-
selector
|
|
1646
|
+
selector,
|
|
1171
1647
|
);
|
|
1172
1648
|
if (test != null) {
|
|
1173
1649
|
return {
|
|
@@ -1189,7 +1665,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1189
1665
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1190
1666
|
*/
|
|
1191
1667
|
public async $checkPhone(): Promise<{ result: boolean; message: string }> {
|
|
1192
|
-
const tilmeld = this
|
|
1668
|
+
const tilmeld = enforceTilmeld(this);
|
|
1193
1669
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
1194
1670
|
return { result: false, message: 'Please specify a phone number.' };
|
|
1195
1671
|
}
|
|
@@ -1211,7 +1687,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1211
1687
|
}
|
|
1212
1688
|
const test = await this.$nymph.getEntity(
|
|
1213
1689
|
{ class: tilmeld.User, skipAc: true },
|
|
1214
|
-
selector
|
|
1690
|
+
selector,
|
|
1215
1691
|
);
|
|
1216
1692
|
if (test != null) {
|
|
1217
1693
|
return { result: false, message: 'Phone number is in use.' };
|
|
@@ -1228,7 +1704,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1228
1704
|
password: string;
|
|
1229
1705
|
additionalData?: { [k: string]: any };
|
|
1230
1706
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1231
|
-
let tilmeld = this
|
|
1707
|
+
let tilmeld = enforceTilmeld(this);
|
|
1232
1708
|
if (!tilmeld.config.allowRegistration) {
|
|
1233
1709
|
return {
|
|
1234
1710
|
result: false,
|
|
@@ -1244,21 +1720,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
1244
1720
|
};
|
|
1245
1721
|
}
|
|
1246
1722
|
|
|
1247
|
-
try {
|
|
1248
|
-
for (let callback of (this.constructor as typeof User)
|
|
1249
|
-
.beforeRegisterCallbacks) {
|
|
1250
|
-
if (callback) {
|
|
1251
|
-
await callback(this, data);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
} catch (e: any) {
|
|
1255
|
-
return {
|
|
1256
|
-
result: false,
|
|
1257
|
-
loggedin: false,
|
|
1258
|
-
message: e.message,
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1723
|
if (!('password' in data) || !data.password.length) {
|
|
1263
1724
|
return {
|
|
1264
1725
|
result: false,
|
|
@@ -1290,9 +1751,29 @@ export default class User extends AbleObject<UserData> {
|
|
|
1290
1751
|
const nymph = this.$nymph;
|
|
1291
1752
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1292
1753
|
this.$nymph = tnymph;
|
|
1293
|
-
tilmeld = this
|
|
1754
|
+
tilmeld = enforceTilmeld(this);
|
|
1755
|
+
|
|
1756
|
+
let message = '';
|
|
1757
|
+
let loggedin = false;
|
|
1294
1758
|
|
|
1295
1759
|
try {
|
|
1760
|
+
try {
|
|
1761
|
+
for (let callback of (this.constructor as typeof User)
|
|
1762
|
+
.beforeRegisterCallbacks) {
|
|
1763
|
+
if (callback) {
|
|
1764
|
+
await callback(this, data);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
} catch (e: any) {
|
|
1768
|
+
await tnymph.rollback(transaction);
|
|
1769
|
+
this.$nymph = nymph;
|
|
1770
|
+
return {
|
|
1771
|
+
result: false,
|
|
1772
|
+
loggedin: false,
|
|
1773
|
+
message: e.message,
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1296
1777
|
// Add primary group.
|
|
1297
1778
|
let generatedPrimaryGroup: (Group & GroupData) | null = null;
|
|
1298
1779
|
if (tilmeld.config.generatePrimary) {
|
|
@@ -1307,7 +1788,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1307
1788
|
{
|
|
1308
1789
|
type: '&',
|
|
1309
1790
|
equal: ['defaultPrimary', true],
|
|
1310
|
-
}
|
|
1791
|
+
},
|
|
1311
1792
|
);
|
|
1312
1793
|
if (parent != null) {
|
|
1313
1794
|
generatedPrimaryGroup.parent = parent;
|
|
@@ -1329,7 +1810,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1329
1810
|
{
|
|
1330
1811
|
type: '&',
|
|
1331
1812
|
equal: ['defaultPrimary', true],
|
|
1332
|
-
}
|
|
1813
|
+
},
|
|
1333
1814
|
);
|
|
1334
1815
|
if (group != null) {
|
|
1335
1816
|
this.$data.group = group;
|
|
@@ -1344,7 +1825,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1344
1825
|
{
|
|
1345
1826
|
type: '&',
|
|
1346
1827
|
equal: ['unverifiedSecondary', true],
|
|
1347
|
-
}
|
|
1828
|
+
},
|
|
1348
1829
|
);
|
|
1349
1830
|
} else {
|
|
1350
1831
|
// Add the default secondaries.
|
|
@@ -1353,7 +1834,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1353
1834
|
{
|
|
1354
1835
|
type: '&',
|
|
1355
1836
|
equal: ['defaultSecondary', true],
|
|
1356
|
-
}
|
|
1837
|
+
},
|
|
1357
1838
|
);
|
|
1358
1839
|
}
|
|
1359
1840
|
|
|
@@ -1403,13 +1884,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1403
1884
|
userPhone: this.$data.phone,
|
|
1404
1885
|
},
|
|
1405
1886
|
},
|
|
1406
|
-
this
|
|
1887
|
+
this,
|
|
1407
1888
|
);
|
|
1408
1889
|
}
|
|
1409
1890
|
|
|
1410
|
-
let message = '';
|
|
1411
|
-
let loggedin = false;
|
|
1412
|
-
|
|
1413
1891
|
// Save the primary group.
|
|
1414
1892
|
if (generatedPrimaryGroup != null) {
|
|
1415
1893
|
generatedPrimaryGroup.user = this;
|
|
@@ -1439,7 +1917,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1439
1917
|
tilmeld.config.unverifiedAccess &&
|
|
1440
1918
|
!madeAdmin
|
|
1441
1919
|
) {
|
|
1442
|
-
if (!tilmeld.login(this, true)) {
|
|
1920
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1443
1921
|
throw new Error('An error occurred trying to log you in.');
|
|
1444
1922
|
}
|
|
1445
1923
|
message +=
|
|
@@ -1448,7 +1926,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1448
1926
|
'registration.';
|
|
1449
1927
|
loggedin = true;
|
|
1450
1928
|
} else {
|
|
1451
|
-
if (!tilmeld.login(this, true)) {
|
|
1929
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1452
1930
|
throw new Error('An error occurred trying to log you in.');
|
|
1453
1931
|
}
|
|
1454
1932
|
message += "You're now registered and logged in!";
|
|
@@ -1464,20 +1942,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
1464
1942
|
});
|
|
1465
1943
|
}
|
|
1466
1944
|
}
|
|
1467
|
-
|
|
1468
|
-
try {
|
|
1469
|
-
await tnymph.commit(transaction);
|
|
1470
|
-
this.$nymph = nymph;
|
|
1471
|
-
this.$nymph.tilmeld?.fillSession(this);
|
|
1472
|
-
} catch (e: any) {
|
|
1473
|
-
throw e;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
return {
|
|
1477
|
-
result: true,
|
|
1478
|
-
loggedin,
|
|
1479
|
-
message,
|
|
1480
|
-
};
|
|
1481
1945
|
} else {
|
|
1482
1946
|
await tnymph.rollback(transaction);
|
|
1483
1947
|
this.$nymph = nymph;
|
|
@@ -1492,10 +1956,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
1492
1956
|
this.$nymph = nymph;
|
|
1493
1957
|
throw e;
|
|
1494
1958
|
}
|
|
1959
|
+
|
|
1960
|
+
try {
|
|
1961
|
+
await tnymph.commit(transaction);
|
|
1962
|
+
this.$nymph = nymph;
|
|
1963
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
1964
|
+
} catch (e: any) {
|
|
1965
|
+
throw e;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
return {
|
|
1969
|
+
result: true,
|
|
1970
|
+
loggedin,
|
|
1971
|
+
message,
|
|
1972
|
+
};
|
|
1495
1973
|
}
|
|
1496
1974
|
|
|
1497
1975
|
public async $save() {
|
|
1498
|
-
let tilmeld = this
|
|
1976
|
+
let tilmeld = enforceTilmeld(this);
|
|
1499
1977
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1500
1978
|
return false;
|
|
1501
1979
|
}
|
|
@@ -1503,10 +1981,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1503
1981
|
if (
|
|
1504
1982
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1505
1983
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1506
|
-
this.$
|
|
1984
|
+
this.$data.abilities?.includes('system/admin')
|
|
1507
1985
|
) {
|
|
1508
1986
|
throw new BadDataError(
|
|
1509
|
-
"You don't have the authority to modify system admins."
|
|
1987
|
+
"You don't have the authority to modify system admins.",
|
|
1510
1988
|
);
|
|
1511
1989
|
}
|
|
1512
1990
|
|
|
@@ -1624,10 +2102,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1624
2102
|
new Date(
|
|
1625
2103
|
strtotime(
|
|
1626
2104
|
'+' + tilmeld.config.emailRateLimit,
|
|
1627
|
-
Math.floor(this.$data.emailChangeDate / 1000)
|
|
1628
|
-
) * 1000
|
|
2105
|
+
Math.floor(this.$data.emailChangeDate / 1000),
|
|
2106
|
+
) * 1000,
|
|
1629
2107
|
).toString() +
|
|
1630
|
-
' to change your email address again.'
|
|
2108
|
+
' to change your email address again.',
|
|
1631
2109
|
);
|
|
1632
2110
|
} else {
|
|
1633
2111
|
if (
|
|
@@ -1693,21 +2171,20 @@ export default class User extends AbleObject<UserData> {
|
|
|
1693
2171
|
const nymph = this.$nymph;
|
|
1694
2172
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1695
2173
|
this.$nymph = tnymph;
|
|
1696
|
-
tilmeld = this
|
|
2174
|
+
tilmeld = enforceTilmeld(this);
|
|
1697
2175
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
this.$is(this.$data.group.user)
|
|
1702
|
-
) {
|
|
2176
|
+
const group = this.$data.group;
|
|
2177
|
+
await group?.$wake();
|
|
2178
|
+
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1703
2179
|
try {
|
|
1704
2180
|
// Update the user's generated primary group.
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
await
|
|
2181
|
+
group.groupname = this.$data.username;
|
|
2182
|
+
group.avatar = this.$data.avatar;
|
|
2183
|
+
group.email = this.$data.email;
|
|
2184
|
+
group.name = this.$data.name;
|
|
2185
|
+
group.phone = this.$data.phone;
|
|
2186
|
+
await group.$saveSkipAC();
|
|
2187
|
+
this.$data.group = group;
|
|
1711
2188
|
} catch (e: any) {
|
|
1712
2189
|
await tnymph.rollback(transaction);
|
|
1713
2190
|
this.$nymph = nymph;
|
|
@@ -1744,9 +2221,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1744
2221
|
this.$gatekeeperCache = undefined;
|
|
1745
2222
|
await tnymph.commit(transaction);
|
|
1746
2223
|
|
|
1747
|
-
|
|
2224
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
2225
|
+
|
|
2226
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
1748
2227
|
// Update the user in the session cache.
|
|
1749
|
-
|
|
2228
|
+
await tilmeld.fillSession(this);
|
|
1750
2229
|
}
|
|
1751
2230
|
} else {
|
|
1752
2231
|
await tnymph.rollback(transaction);
|
|
@@ -1772,16 +2251,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1772
2251
|
}
|
|
1773
2252
|
|
|
1774
2253
|
public async $delete() {
|
|
1775
|
-
const tilmeld = this
|
|
2254
|
+
const tilmeld = enforceTilmeld(this);
|
|
1776
2255
|
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1777
2256
|
throw new BadDataError("You don't have the authority to delete users.");
|
|
1778
2257
|
}
|
|
1779
2258
|
if (
|
|
1780
2259
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1781
|
-
this.$
|
|
2260
|
+
this.$data.abilities?.includes('system/admin')
|
|
1782
2261
|
) {
|
|
1783
2262
|
throw new BadDataError(
|
|
1784
|
-
"You don't have the authority to delete system admins."
|
|
2263
|
+
"You don't have the authority to delete system admins.",
|
|
1785
2264
|
);
|
|
1786
2265
|
}
|
|
1787
2266
|
if (tilmeld.User.current(true).$is(this)) {
|
|
@@ -1792,7 +2271,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1792
2271
|
|
|
1793
2272
|
public static on<T extends EventType>(
|
|
1794
2273
|
event: T,
|
|
1795
|
-
callback: T extends '
|
|
2274
|
+
callback: T extends 'checkUsername'
|
|
2275
|
+
? TilmeldCheckUsernameCallback
|
|
2276
|
+
: T extends 'beforeRegister'
|
|
1796
2277
|
? TilmeldBeforeRegisterCallback
|
|
1797
2278
|
: T extends 'afterRegister'
|
|
1798
2279
|
? TilmeldAfterRegisterCallback
|
|
@@ -1804,9 +2285,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1804
2285
|
? TilmeldBeforeLogoutCallback
|
|
1805
2286
|
: T extends 'afterLogout'
|
|
1806
2287
|
? TilmeldAfterLogoutCallback
|
|
1807
|
-
: never
|
|
2288
|
+
: never,
|
|
1808
2289
|
) {
|
|
1809
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2290
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2291
|
+
? 'checkUsernameCallbacks'
|
|
2292
|
+
: T extends 'beforeRegister'
|
|
1810
2293
|
? 'beforeRegisterCallbacks'
|
|
1811
2294
|
: T extends 'afterRegister'
|
|
1812
2295
|
? 'afterRegisterCallbacks'
|
|
@@ -1829,7 +2312,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1829
2312
|
|
|
1830
2313
|
public static off<T extends EventType>(
|
|
1831
2314
|
event: T,
|
|
1832
|
-
callback: T extends '
|
|
2315
|
+
callback: T extends 'checkUsername'
|
|
2316
|
+
? TilmeldCheckUsernameCallback
|
|
2317
|
+
: T extends 'beforeRegister'
|
|
1833
2318
|
? TilmeldBeforeRegisterCallback
|
|
1834
2319
|
: T extends 'afterRegister'
|
|
1835
2320
|
? TilmeldAfterRegisterCallback
|
|
@@ -1841,9 +2326,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1841
2326
|
? TilmeldBeforeLogoutCallback
|
|
1842
2327
|
: T extends 'afterLogout'
|
|
1843
2328
|
? TilmeldAfterLogoutCallback
|
|
1844
|
-
: never
|
|
2329
|
+
: never,
|
|
1845
2330
|
) {
|
|
1846
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2331
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2332
|
+
? 'checkUsernameCallbacks'
|
|
2333
|
+
: T extends 'beforeRegister'
|
|
1847
2334
|
? 'beforeRegisterCallbacks'
|
|
1848
2335
|
: T extends 'afterRegister'
|
|
1849
2336
|
? 'afterRegisterCallbacks'
|