@nymphjs/tilmeld 1.0.0-beta.6 → 1.0.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +284 -0
- package/README.md +14 -1
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +16 -1
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +119 -8
- package/dist/Group.js +230 -97
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +3 -2
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +237 -9
- package/dist/Tilmeld.js +559 -195
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +333 -14
- package/dist/User.js +797 -204
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +3 -2
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +250 -3
- package/dist/conf/defaults.js +146 -98
- 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/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -2
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -2
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -2
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -2
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -26
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +3 -3
- package/src/Group.ts +194 -143
- package/src/Tilmeld.ts +384 -249
- package/src/User.test.ts +3 -3
- package/src/User.ts +876 -266
- package/src/conf/d.ts +26 -13
- package/src/conf/defaults.ts +151 -104
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/index.ts +2 -0
- package/src/tilmeldJoiProps.ts +9 -9
- package/tsconfig.json +1 -1
- 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
|
|
|
@@ -257,10 +289,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
257
289
|
*/
|
|
258
290
|
public $originalEmail?: string;
|
|
259
291
|
|
|
260
|
-
static async factory(guid?: string): Promise<User & UserData> {
|
|
261
|
-
return (await super.factory(guid)) as User & UserData;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
292
|
static async factoryUsername(username?: string): Promise<User & UserData> {
|
|
265
293
|
const entity = new this();
|
|
266
294
|
if (username != null) {
|
|
@@ -271,7 +299,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
271
299
|
{
|
|
272
300
|
type: '&',
|
|
273
301
|
ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
274
|
-
}
|
|
302
|
+
},
|
|
275
303
|
);
|
|
276
304
|
if (entity != null) {
|
|
277
305
|
return entity;
|
|
@@ -280,34 +308,29 @@ export default class User extends AbleObject<UserData> {
|
|
|
280
308
|
return entity;
|
|
281
309
|
}
|
|
282
310
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
constructor(guid?: string) {
|
|
288
|
-
super(guid);
|
|
311
|
+
constructor() {
|
|
312
|
+
super();
|
|
289
313
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.$updateDataProtection();
|
|
297
|
-
}
|
|
314
|
+
// Defaults.
|
|
315
|
+
this.$data.enabled = true;
|
|
316
|
+
this.$data.abilities = [];
|
|
317
|
+
this.$data.groups = [];
|
|
318
|
+
this.$data.inheritAbilities = true;
|
|
319
|
+
this.$updateDataProtection();
|
|
298
320
|
}
|
|
299
321
|
|
|
300
322
|
public static current(returnObjectIfNotExist: true): User & UserData;
|
|
301
323
|
public static current(
|
|
302
|
-
returnObjectIfNotExist?: false
|
|
324
|
+
returnObjectIfNotExist?: false,
|
|
303
325
|
): (User & UserData) | null;
|
|
304
326
|
public static current(
|
|
305
|
-
returnObjectIfNotExist?: boolean
|
|
327
|
+
returnObjectIfNotExist?: boolean,
|
|
306
328
|
): (User & UserData) | null {
|
|
307
|
-
|
|
329
|
+
const tilmeld = enforceTilmeld(this);
|
|
330
|
+
if (tilmeld.currentUser == null) {
|
|
308
331
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
309
332
|
}
|
|
310
|
-
return
|
|
333
|
+
return tilmeld.currentUser;
|
|
311
334
|
}
|
|
312
335
|
|
|
313
336
|
/**
|
|
@@ -320,7 +343,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
320
343
|
recoveryType: 'username' | 'password';
|
|
321
344
|
account: string;
|
|
322
345
|
}): Promise<{ result: boolean; message: string }> {
|
|
323
|
-
|
|
346
|
+
const tilmeld = enforceTilmeld(this);
|
|
347
|
+
if (!tilmeld.config.pwRecovery) {
|
|
324
348
|
return {
|
|
325
349
|
result: false,
|
|
326
350
|
message: 'Account recovery is not allowed.',
|
|
@@ -330,18 +354,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
330
354
|
let user: User & UserData;
|
|
331
355
|
const options: EmailOptions = {};
|
|
332
356
|
|
|
333
|
-
if (
|
|
334
|
-
!this.tilmeld.config.emailUsernames &&
|
|
335
|
-
data.recoveryType === 'username'
|
|
336
|
-
) {
|
|
357
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
337
358
|
// Create a username recovery email.
|
|
338
359
|
|
|
339
360
|
const getUser = await this.nymph.getEntity(
|
|
340
|
-
{ class:
|
|
361
|
+
{ class: tilmeld.User, skipAc: true },
|
|
341
362
|
{
|
|
342
363
|
type: '&',
|
|
343
364
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
344
|
-
}
|
|
365
|
+
},
|
|
345
366
|
);
|
|
346
367
|
|
|
347
368
|
if (getUser == null) {
|
|
@@ -364,7 +385,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
364
385
|
} else if (data.recoveryType === 'password') {
|
|
365
386
|
// Create a password recovery email.
|
|
366
387
|
|
|
367
|
-
const getUser = await
|
|
388
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
368
389
|
|
|
369
390
|
if (getUser.guid == null) {
|
|
370
391
|
return {
|
|
@@ -391,14 +412,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
391
412
|
};
|
|
392
413
|
options.locals = {
|
|
393
414
|
recoverCode: getUser.recoverSecret,
|
|
394
|
-
timeLimit:
|
|
415
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
395
416
|
};
|
|
396
417
|
} else {
|
|
397
418
|
return { result: false, message: 'Invalid recovery type.' };
|
|
398
419
|
}
|
|
399
420
|
|
|
421
|
+
this.nymph.config.debugLog(
|
|
422
|
+
'tilmeld',
|
|
423
|
+
`User account recovery (${options.template}) request for "${user.username}".`,
|
|
424
|
+
);
|
|
425
|
+
|
|
400
426
|
// Send the email.
|
|
401
|
-
if (await
|
|
427
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
402
428
|
return {
|
|
403
429
|
result: true,
|
|
404
430
|
message:
|
|
@@ -421,22 +447,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
421
447
|
secret: string;
|
|
422
448
|
password: string;
|
|
423
449
|
}): Promise<{ result: boolean; message: string }> {
|
|
424
|
-
|
|
450
|
+
const tilmeld = enforceTilmeld(this);
|
|
451
|
+
if (!tilmeld.config.pwRecovery) {
|
|
425
452
|
return {
|
|
426
453
|
result: false,
|
|
427
454
|
message: 'Account recovery is not allowed.',
|
|
428
455
|
};
|
|
429
456
|
}
|
|
430
457
|
|
|
431
|
-
const user = await
|
|
458
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
432
459
|
|
|
433
460
|
if (
|
|
434
461
|
user.guid == null ||
|
|
435
462
|
user.recoverSecret == null ||
|
|
436
463
|
data.secret !== user.recoverSecret ||
|
|
437
464
|
strtotime(
|
|
438
|
-
'+' +
|
|
439
|
-
Math.floor((user.recoverSecretDate ?? 0) / 1000)
|
|
465
|
+
'+' + tilmeld.config.pwRecoveryTimeLimit,
|
|
466
|
+
Math.floor((user.recoverSecretDate ?? 0) / 1000),
|
|
440
467
|
) *
|
|
441
468
|
1000 <
|
|
442
469
|
Date.now()
|
|
@@ -451,6 +478,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
451
478
|
return { result: false, message: 'Password cannot be empty.' };
|
|
452
479
|
}
|
|
453
480
|
|
|
481
|
+
this.nymph.config.debugLog(
|
|
482
|
+
'tilmeld',
|
|
483
|
+
`User account recovery for "${user.username}".`,
|
|
484
|
+
);
|
|
485
|
+
|
|
454
486
|
user.$password(data.password);
|
|
455
487
|
delete user.recoverSecret;
|
|
456
488
|
delete user.recoverSecretDate;
|
|
@@ -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,22 +534,41 @@ 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
|
}
|
|
508
544
|
if (!this.$data.enabled) {
|
|
509
545
|
return { result: false, message: 'This user is disabled.' };
|
|
510
546
|
}
|
|
511
|
-
if (this.$gatekeeper()) {
|
|
547
|
+
if (await this.$gatekeeper()) {
|
|
512
548
|
return { result: true, message: 'You are already logged in.' };
|
|
513
549
|
}
|
|
514
550
|
if (!this.$checkPassword(data.password)) {
|
|
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) {
|
|
@@ -529,8 +583,90 @@ export default class User extends AbleObject<UserData> {
|
|
|
529
583
|
};
|
|
530
584
|
}
|
|
531
585
|
|
|
586
|
+
this.$nymph.config.debugLog(
|
|
587
|
+
'tilmeld',
|
|
588
|
+
`User login for "${this.$data.username}".`,
|
|
589
|
+
);
|
|
590
|
+
|
|
532
591
|
// Authentication was successful, attempt to login.
|
|
533
|
-
if (!tilmeld.login(this, true)) {
|
|
592
|
+
if (!(await tilmeld.login(this, true))) {
|
|
593
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for (let callback of (this.constructor as typeof User)
|
|
597
|
+
.afterLoginCallbacks) {
|
|
598
|
+
if (callback) {
|
|
599
|
+
await callback(this);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Login was successful.
|
|
604
|
+
return { result: true, message: 'You are logged in.' };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
|
|
608
|
+
const tilmeld = enforceTilmeld(this);
|
|
609
|
+
if (this.guid == null) {
|
|
610
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let user = tilmeld.User.current();
|
|
614
|
+
|
|
615
|
+
if (!user || user.guid == null) {
|
|
616
|
+
return { result: false, message: "You're not logged in." };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
620
|
+
|
|
621
|
+
if (isCurrentUser) {
|
|
622
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const toAbilities = this.$data.abilities || [];
|
|
626
|
+
const fromAbilities = user.abilities || [];
|
|
627
|
+
|
|
628
|
+
if (
|
|
629
|
+
!fromAbilities.includes('tilmeld/switch') &&
|
|
630
|
+
!fromAbilities.includes('system/admin')
|
|
631
|
+
) {
|
|
632
|
+
return {
|
|
633
|
+
result: false,
|
|
634
|
+
message: "You don't have permission to switch users.",
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (
|
|
639
|
+
toAbilities.includes('system/admin') ||
|
|
640
|
+
toAbilities.includes('tilmeld/admin')
|
|
641
|
+
) {
|
|
642
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
for (let callback of (this.constructor as typeof User)
|
|
647
|
+
.beforeLoginCallbacks) {
|
|
648
|
+
if (callback) {
|
|
649
|
+
await callback(this, {
|
|
650
|
+
username: this.$data.username || '',
|
|
651
|
+
password: '',
|
|
652
|
+
...(data || {}),
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
} catch (e: any) {
|
|
657
|
+
return {
|
|
658
|
+
result: false,
|
|
659
|
+
message: e.message,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
this.$nymph.config.debugLog(
|
|
664
|
+
'tilmeld',
|
|
665
|
+
`User login switch for "${user.username}" to "${this.$data.username}".`,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
// Authentication was successful, attempt to login.
|
|
669
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
534
670
|
return { result: false, message: 'Incorrect login/password.' };
|
|
535
671
|
}
|
|
536
672
|
|
|
@@ -550,7 +686,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
550
686
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
551
687
|
*/
|
|
552
688
|
public async $logout() {
|
|
553
|
-
const tilmeld = this
|
|
689
|
+
const tilmeld = enforceTilmeld(this);
|
|
554
690
|
|
|
555
691
|
try {
|
|
556
692
|
for (let callback of (this.constructor as typeof User)
|
|
@@ -566,7 +702,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
566
702
|
};
|
|
567
703
|
}
|
|
568
704
|
|
|
569
|
-
|
|
705
|
+
this.$nymph.config.debugLog(
|
|
706
|
+
'tilmeld',
|
|
707
|
+
`User logout for "${this.$data.username}".`,
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
await tilmeld.logout();
|
|
570
711
|
|
|
571
712
|
for (let callback of (this.constructor as typeof User)
|
|
572
713
|
.afterLogoutCallbacks) {
|
|
@@ -592,36 +733,36 @@ export default class User extends AbleObject<UserData> {
|
|
|
592
733
|
);
|
|
593
734
|
}
|
|
594
735
|
|
|
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
|
-
}
|
|
736
|
+
public $getGid() {
|
|
737
|
+
if (this.$data.group) {
|
|
738
|
+
return this.$data.group.guid;
|
|
609
739
|
}
|
|
610
|
-
return
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
public $getGids() {
|
|
744
|
+
if (this.$data.groups) {
|
|
745
|
+
return this.$data.groups
|
|
746
|
+
.map((group) => group.guid)
|
|
747
|
+
.filter((guid) => guid != null) as string[];
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
611
750
|
}
|
|
612
751
|
|
|
613
752
|
/**
|
|
614
753
|
* Get the user's group descendants.
|
|
615
754
|
*/
|
|
616
|
-
public $
|
|
755
|
+
public async $getDescendantGroups(): Promise<(Group & GroupData)[]> {
|
|
617
756
|
if (this.$descendantGroups == null) {
|
|
618
757
|
this.$descendantGroups = [];
|
|
619
758
|
if (this.$data.group != null) {
|
|
620
|
-
|
|
759
|
+
await this.$data.group.$wake();
|
|
760
|
+
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
621
761
|
}
|
|
762
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
622
763
|
for (let curGroup of this.$data.groups ?? []) {
|
|
623
764
|
this.$descendantGroups = this.$descendantGroups?.concat(
|
|
624
|
-
curGroup.$
|
|
765
|
+
await curGroup.$getDescendants(),
|
|
625
766
|
);
|
|
626
767
|
}
|
|
627
768
|
}
|
|
@@ -629,18 +770,42 @@ export default class User extends AbleObject<UserData> {
|
|
|
629
770
|
}
|
|
630
771
|
|
|
631
772
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
632
|
-
const tilmeld = this
|
|
633
|
-
this.$
|
|
773
|
+
const tilmeld = enforceTilmeld(this);
|
|
774
|
+
this.$check();
|
|
634
775
|
|
|
635
776
|
if (
|
|
636
777
|
'abilities' in input.data &&
|
|
637
|
-
input.data.abilities
|
|
638
|
-
|
|
639
|
-
tilmeld.
|
|
640
|
-
|
|
778
|
+
input.data.abilities?.includes('system/admin') !==
|
|
779
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
780
|
+
(!tilmeld.currentUser ||
|
|
781
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
782
|
+
) {
|
|
783
|
+
throw new BadDataError(
|
|
784
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (
|
|
789
|
+
'abilities' in input.data &&
|
|
790
|
+
input.data.abilities?.includes('tilmeld/admin') !==
|
|
791
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
792
|
+
(!tilmeld.currentUser ||
|
|
793
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
794
|
+
) {
|
|
795
|
+
throw new BadDataError(
|
|
796
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (
|
|
801
|
+
'abilities' in input.data &&
|
|
802
|
+
input.data.abilities?.includes('tilmeld/switch') !==
|
|
803
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
804
|
+
(!tilmeld.currentUser ||
|
|
805
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
641
806
|
) {
|
|
642
807
|
throw new BadDataError(
|
|
643
|
-
"You don't have the authority to
|
|
808
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
644
809
|
);
|
|
645
810
|
}
|
|
646
811
|
|
|
@@ -649,18 +814,42 @@ export default class User extends AbleObject<UserData> {
|
|
|
649
814
|
}
|
|
650
815
|
|
|
651
816
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
652
|
-
const tilmeld = this
|
|
653
|
-
this.$
|
|
817
|
+
const tilmeld = enforceTilmeld(this);
|
|
818
|
+
this.$check();
|
|
654
819
|
|
|
655
820
|
if (
|
|
656
821
|
'abilities' in patch.set &&
|
|
657
|
-
patch.set.abilities
|
|
658
|
-
|
|
659
|
-
tilmeld.
|
|
660
|
-
|
|
822
|
+
patch.set.abilities?.includes('system/admin') !==
|
|
823
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
824
|
+
(!tilmeld.currentUser ||
|
|
825
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
826
|
+
) {
|
|
827
|
+
throw new BadDataError(
|
|
828
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (
|
|
833
|
+
'abilities' in patch.set &&
|
|
834
|
+
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
835
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
836
|
+
(!tilmeld.currentUser ||
|
|
837
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
838
|
+
) {
|
|
839
|
+
throw new BadDataError(
|
|
840
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (
|
|
845
|
+
'abilities' in patch.set &&
|
|
846
|
+
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
847
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
848
|
+
(!tilmeld.currentUser ||
|
|
849
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
661
850
|
) {
|
|
662
851
|
throw new BadDataError(
|
|
663
|
-
"You don't have the authority to
|
|
852
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
664
853
|
);
|
|
665
854
|
}
|
|
666
855
|
|
|
@@ -679,7 +868,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
679
868
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
680
869
|
*/
|
|
681
870
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
682
|
-
const tilmeld = this
|
|
871
|
+
const tilmeld = enforceTilmeld(this);
|
|
683
872
|
let user = givenUser ?? tilmeld.User.current();
|
|
684
873
|
|
|
685
874
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
@@ -703,17 +892,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
703
892
|
// Users can check to see what abilities they have.
|
|
704
893
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
705
894
|
this.$clientEnabledMethods.push('$changePassword');
|
|
895
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
706
896
|
this.$clientEnabledMethods.push('$logout');
|
|
707
897
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
898
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
899
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
900
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
901
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
708
902
|
}
|
|
709
903
|
|
|
710
904
|
if (
|
|
711
905
|
user != null &&
|
|
712
|
-
(abilities.
|
|
713
|
-
abilities.
|
|
906
|
+
(abilities.includes('tilmeld/admin') ||
|
|
907
|
+
abilities.includes('system/admin'))
|
|
714
908
|
) {
|
|
715
909
|
// Users who can edit other users can see most of their data.
|
|
716
|
-
this.$privateData = ['password', 'salt'];
|
|
910
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
911
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
912
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
717
913
|
this.$allowlistData = undefined;
|
|
718
914
|
} else if (isCurrentUser || isNewUser) {
|
|
719
915
|
// Users can see their own data, and edit some of it.
|
|
@@ -721,30 +917,69 @@ export default class User extends AbleObject<UserData> {
|
|
|
721
917
|
this.$allowlistData.push('username');
|
|
722
918
|
}
|
|
723
919
|
this.$allowlistData.push('avatar');
|
|
724
|
-
if (tilmeld.config.userFields.
|
|
920
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
725
921
|
this.$allowlistData.push('nameFirst');
|
|
726
922
|
this.$allowlistData.push('nameMiddle');
|
|
727
923
|
this.$allowlistData.push('nameLast');
|
|
728
924
|
this.$allowlistData.push('name');
|
|
729
925
|
}
|
|
730
|
-
if (tilmeld.config.userFields.
|
|
926
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
731
927
|
this.$allowlistData.push('email');
|
|
732
928
|
}
|
|
733
|
-
if (tilmeld.config.userFields.
|
|
929
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
734
930
|
this.$allowlistData.push('phone');
|
|
735
931
|
}
|
|
736
932
|
this.$privateData = [
|
|
933
|
+
// The things that are commented are there to show what is _not_ private.
|
|
934
|
+
// 'abilities',
|
|
935
|
+
// 'username',
|
|
936
|
+
// 'nameFirst',
|
|
937
|
+
// 'nameMiddle',
|
|
938
|
+
// 'nameLast',
|
|
939
|
+
// 'name',
|
|
940
|
+
// 'email',
|
|
941
|
+
// 'avatar',
|
|
942
|
+
// 'phone',
|
|
943
|
+
// 'group',
|
|
944
|
+
// 'groups',
|
|
945
|
+
// 'inheritAbilities',
|
|
946
|
+
'enabled',
|
|
737
947
|
'secret',
|
|
948
|
+
'emailChangeDate',
|
|
738
949
|
'newEmailSecret',
|
|
739
|
-
'
|
|
950
|
+
// 'newEmailAddress',
|
|
740
951
|
'cancelEmailSecret',
|
|
741
|
-
'
|
|
952
|
+
'cancelEmailAddress',
|
|
742
953
|
'recoverSecret',
|
|
743
954
|
'recoverSecretDate',
|
|
744
|
-
'password',
|
|
745
955
|
'salt',
|
|
956
|
+
'password',
|
|
957
|
+
'passwordTemp',
|
|
958
|
+
// 'revokeTokenDate',
|
|
959
|
+
'totpSecret',
|
|
746
960
|
];
|
|
747
961
|
}
|
|
962
|
+
|
|
963
|
+
if (
|
|
964
|
+
user != null &&
|
|
965
|
+
!isCurrentUser &&
|
|
966
|
+
(abilities.includes('tilmeld/switch') ||
|
|
967
|
+
abilities.includes('system/admin'))
|
|
968
|
+
) {
|
|
969
|
+
// Users with this ability can switch to other users.
|
|
970
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
public $updateGroupDataProtection() {
|
|
975
|
+
if (this.$data.group != null && !this.$data.group.$asleep()) {
|
|
976
|
+
this.$data.group.$updateDataProtection();
|
|
977
|
+
}
|
|
978
|
+
for (let group of this.$data.groups ?? []) {
|
|
979
|
+
if (!group.$asleep()) {
|
|
980
|
+
group.$updateDataProtection();
|
|
981
|
+
}
|
|
982
|
+
}
|
|
748
983
|
}
|
|
749
984
|
|
|
750
985
|
/**
|
|
@@ -761,35 +996,43 @@ export default class User extends AbleObject<UserData> {
|
|
|
761
996
|
* @param ability The ability.
|
|
762
997
|
* @returns True or false.
|
|
763
998
|
*/
|
|
764
|
-
public $gatekeeper(ability?: string): boolean {
|
|
765
|
-
|
|
999
|
+
public async $gatekeeper(ability?: string): Promise<boolean> {
|
|
1000
|
+
await this.$wake();
|
|
1001
|
+
const tilmeld = enforceTilmeld(this);
|
|
766
1002
|
if (ability == null) {
|
|
767
1003
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
768
1004
|
}
|
|
769
1005
|
// Check the cache to see if we've already checked this user.
|
|
770
1006
|
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
|
-
);
|
|
1007
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
789
1008
|
}
|
|
790
1009
|
return (
|
|
791
1010
|
(ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
792
|
-
!!this.$
|
|
1011
|
+
!!this.$data.abilities?.includes('system/admin')
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Build a gatekeeper cache object.
|
|
1017
|
+
*/
|
|
1018
|
+
public async $getGatekeeperCache() {
|
|
1019
|
+
await this.$wake();
|
|
1020
|
+
let abilities = this.$data.abilities ?? [];
|
|
1021
|
+
if (this.$data.inheritAbilities) {
|
|
1022
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1023
|
+
for (let curGroup of this.$data.groups ?? []) {
|
|
1024
|
+
if (curGroup.enabled) {
|
|
1025
|
+
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
await this.$data.group?.$wake();
|
|
1029
|
+
const group = this.$data.group;
|
|
1030
|
+
if (group != null && group.cdate != null && group.enabled) {
|
|
1031
|
+
abilities = abilities.concat(group.abilities ?? []);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return Object.fromEntries(
|
|
1035
|
+
abilities.map((ability) => [ability, true as true]),
|
|
793
1036
|
);
|
|
794
1037
|
}
|
|
795
1038
|
|
|
@@ -806,7 +1049,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
806
1049
|
* @returns True on success, false on failure.
|
|
807
1050
|
*/
|
|
808
1051
|
public async $sendEmailVerification() {
|
|
809
|
-
const tilmeld = this
|
|
1052
|
+
const tilmeld = enforceTilmeld(this);
|
|
810
1053
|
if (this.guid == null) {
|
|
811
1054
|
return false;
|
|
812
1055
|
}
|
|
@@ -818,7 +1061,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
818
1061
|
|
|
819
1062
|
if (this.$data.secret != null) {
|
|
820
1063
|
const link = `${verifyUrl}?action=verify&id=${encodeURIComponent(
|
|
821
|
-
this.guid
|
|
1064
|
+
this.guid,
|
|
822
1065
|
)}&secret=${encodeURIComponent(this.$data.secret)}`;
|
|
823
1066
|
success =
|
|
824
1067
|
success &&
|
|
@@ -836,13 +1079,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
836
1079
|
verifyLink: link,
|
|
837
1080
|
},
|
|
838
1081
|
},
|
|
839
|
-
this
|
|
1082
|
+
this,
|
|
840
1083
|
));
|
|
841
1084
|
}
|
|
842
1085
|
|
|
843
1086
|
if (this.$data.newEmailSecret != null) {
|
|
844
1087
|
const link = `${verifyUrl}?action=verifychange&id=${encodeURIComponent(
|
|
845
|
-
this.guid
|
|
1088
|
+
this.guid,
|
|
846
1089
|
)}&secret=${encodeURIComponent(this.$data.newEmailSecret)}`;
|
|
847
1090
|
success =
|
|
848
1091
|
success &&
|
|
@@ -862,13 +1105,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
862
1105
|
newEmail: this.$data.newEmailAddress,
|
|
863
1106
|
},
|
|
864
1107
|
},
|
|
865
|
-
this
|
|
1108
|
+
this,
|
|
866
1109
|
));
|
|
867
1110
|
}
|
|
868
1111
|
|
|
869
1112
|
if (this.$data.cancelEmailSecret != null) {
|
|
870
1113
|
const link = `${verifyUrl}?action=cancelchange&id=${encodeURIComponent(
|
|
871
|
-
this.guid
|
|
1114
|
+
this.guid,
|
|
872
1115
|
)}&secret=${encodeURIComponent(this.$data.cancelEmailSecret)}`;
|
|
873
1116
|
success =
|
|
874
1117
|
success &&
|
|
@@ -888,7 +1131,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
888
1131
|
newEmail: this.$data.newEmailAddress ?? this.$data.email,
|
|
889
1132
|
},
|
|
890
1133
|
},
|
|
891
|
-
this
|
|
1134
|
+
this,
|
|
892
1135
|
));
|
|
893
1136
|
}
|
|
894
1137
|
|
|
@@ -901,13 +1144,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
901
1144
|
* @param group The group.
|
|
902
1145
|
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
903
1146
|
*/
|
|
904
|
-
public $addGroup(group: Group & GroupData) {
|
|
905
|
-
|
|
906
|
-
|
|
1147
|
+
public async $addGroup(group: Group & GroupData) {
|
|
1148
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1149
|
+
let groups = this.$data.groups;
|
|
1150
|
+
if (groups == null) {
|
|
1151
|
+
groups = [];
|
|
907
1152
|
}
|
|
908
|
-
if (!group.$inArray(
|
|
909
|
-
|
|
910
|
-
|
|
1153
|
+
if (!group.$inArray(groups)) {
|
|
1154
|
+
groups.push(group);
|
|
1155
|
+
this.$data.groups = groups;
|
|
1156
|
+
return groups;
|
|
911
1157
|
}
|
|
912
1158
|
return true;
|
|
913
1159
|
}
|
|
@@ -919,7 +1165,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
919
1165
|
* @returns True if the passwords match, otherwise false.
|
|
920
1166
|
*/
|
|
921
1167
|
public $checkPassword(password: string) {
|
|
922
|
-
const tilmeld = this
|
|
1168
|
+
const tilmeld = enforceTilmeld(this);
|
|
923
1169
|
switch (tilmeld.config.pwMethod) {
|
|
924
1170
|
case 'plain':
|
|
925
1171
|
return this.$data.password === password;
|
|
@@ -940,10 +1186,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
940
1186
|
* @param group The group.
|
|
941
1187
|
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
942
1188
|
*/
|
|
943
|
-
public $delGroup(group: Group & GroupData) {
|
|
944
|
-
|
|
1189
|
+
public async $delGroup(group: Group & GroupData) {
|
|
1190
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1191
|
+
let groups = this.$data.groups || [];
|
|
1192
|
+
if (this.$data.groups != null && group.$inArray(groups)) {
|
|
945
1193
|
const newGroups: (Group & GroupData)[] = [];
|
|
946
|
-
for (let curGroup of
|
|
1194
|
+
for (let curGroup of groups) {
|
|
947
1195
|
if (!group.$is(curGroup)) {
|
|
948
1196
|
newGroups.push(curGroup);
|
|
949
1197
|
}
|
|
@@ -960,7 +1208,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
960
1208
|
* @param group The group, or the group's GUID.
|
|
961
1209
|
* @returns True or false.
|
|
962
1210
|
*/
|
|
963
|
-
public $inGroup(group: (Group & GroupData) | string) {
|
|
1211
|
+
public async $inGroup(group: (Group & GroupData) | string) {
|
|
964
1212
|
if (typeof group === 'string') {
|
|
965
1213
|
if (this.$data.group?.guid === group) {
|
|
966
1214
|
return true;
|
|
@@ -983,16 +1231,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
983
1231
|
* @param group The group, or the group's GUID.
|
|
984
1232
|
* @returns True or false.
|
|
985
1233
|
*/
|
|
986
|
-
public $isDescendant(group: (Group & GroupData) | string) {
|
|
1234
|
+
public async $isDescendant(group: (Group & GroupData) | string) {
|
|
987
1235
|
// Check to see if the user is in a descendant group of the given group.
|
|
1236
|
+
await this.$data.group?.$wake();
|
|
988
1237
|
if (
|
|
1238
|
+
this.$data.group != null &&
|
|
989
1239
|
this.$data.group?.cdate != null &&
|
|
990
|
-
this.$data.group
|
|
1240
|
+
(await this.$data.group?.$isDescendant(group))
|
|
991
1241
|
) {
|
|
992
1242
|
return true;
|
|
993
1243
|
}
|
|
1244
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
994
1245
|
for (let curGroup of this.$data.groups ?? []) {
|
|
995
|
-
if (curGroup.$isDescendant(group)) {
|
|
1246
|
+
if (await curGroup.$isDescendant(group)) {
|
|
996
1247
|
return true;
|
|
997
1248
|
}
|
|
998
1249
|
}
|
|
@@ -1008,15 +1259,31 @@ export default class User extends AbleObject<UserData> {
|
|
|
1008
1259
|
public async $changePassword(data: {
|
|
1009
1260
|
newPassword: string;
|
|
1010
1261
|
currentPassword: string;
|
|
1262
|
+
revokeCurrentTokens?: boolean;
|
|
1011
1263
|
}): Promise<{ result: boolean; message: string }> {
|
|
1264
|
+
this.$nymph.config.debugLog(
|
|
1265
|
+
'tilmeld',
|
|
1266
|
+
`Password change request for "${this.$data.username}".`,
|
|
1267
|
+
);
|
|
1012
1268
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1013
1269
|
return { result: false, message: 'Please specify a password.' };
|
|
1014
1270
|
}
|
|
1015
1271
|
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
1272
|
+
this.$nymph.config.debugLog(
|
|
1273
|
+
'tilmeld',
|
|
1274
|
+
`Incorrect password for change request for "${this.$data.username}".`,
|
|
1275
|
+
);
|
|
1016
1276
|
return { result: false, message: 'Incorrect password.' };
|
|
1017
1277
|
}
|
|
1018
1278
|
this.$data.passwordTemp = data.newPassword;
|
|
1279
|
+
if (data.revokeCurrentTokens) {
|
|
1280
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1281
|
+
}
|
|
1019
1282
|
if (await this.$save()) {
|
|
1283
|
+
if (data.revokeCurrentTokens) {
|
|
1284
|
+
const tilmeld = enforceTilmeld(this);
|
|
1285
|
+
await tilmeld.login(this, true);
|
|
1286
|
+
}
|
|
1020
1287
|
return { result: true, message: 'Your password has been changed.' };
|
|
1021
1288
|
} else {
|
|
1022
1289
|
return { result: false, message: "Couldn't save new password." };
|
|
@@ -1030,7 +1297,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1030
1297
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1031
1298
|
*/
|
|
1032
1299
|
public $password(password: string) {
|
|
1033
|
-
const tilmeld = this
|
|
1300
|
+
const tilmeld = enforceTilmeld(this);
|
|
1034
1301
|
switch (tilmeld.config.pwMethod) {
|
|
1035
1302
|
case 'plain':
|
|
1036
1303
|
delete this.$data.salt;
|
|
@@ -1044,28 +1311,250 @@ export default class User extends AbleObject<UserData> {
|
|
|
1044
1311
|
default:
|
|
1045
1312
|
this.$data.salt = nanoid();
|
|
1046
1313
|
this.$data.password = sha256(password + this.$data.salt).toString(
|
|
1047
|
-
Base64
|
|
1314
|
+
Base64,
|
|
1048
1315
|
);
|
|
1049
1316
|
break;
|
|
1050
1317
|
}
|
|
1051
1318
|
return this.$data.password;
|
|
1052
1319
|
}
|
|
1053
1320
|
|
|
1321
|
+
/**
|
|
1322
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
1323
|
+
*
|
|
1324
|
+
* @param data The input data from the client.
|
|
1325
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1326
|
+
*/
|
|
1327
|
+
public async $revokeCurrentTokens(data: {
|
|
1328
|
+
password: string;
|
|
1329
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1330
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1331
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1335
|
+
|
|
1336
|
+
if (await this.$save()) {
|
|
1337
|
+
const tilmeld = enforceTilmeld(this);
|
|
1338
|
+
await tilmeld.login(this, true);
|
|
1339
|
+
return {
|
|
1340
|
+
result: true,
|
|
1341
|
+
message: 'You have logged out of all other sessions.',
|
|
1342
|
+
};
|
|
1343
|
+
} else {
|
|
1344
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
public async $hasTOTPSecret() {
|
|
1349
|
+
return this.$data.totpSecret != null;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Check the given code against the user's TOTP secret.
|
|
1354
|
+
*
|
|
1355
|
+
* @param code The code in question.
|
|
1356
|
+
* @returns True if the code is valid, otherwise false.
|
|
1357
|
+
*/
|
|
1358
|
+
public $checkTOTPCode(code: string) {
|
|
1359
|
+
if (this.$data.totpSecret == null) {
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const tilmeld = enforceTilmeld(this);
|
|
1364
|
+
|
|
1365
|
+
const totp = new TOTP({
|
|
1366
|
+
issuer: tilmeld.config.appName,
|
|
1367
|
+
label: this.$data.username,
|
|
1368
|
+
algorithm: 'SHA1',
|
|
1369
|
+
digits: 6,
|
|
1370
|
+
period: 30,
|
|
1371
|
+
secret: this.$data.totpSecret,
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1379
|
+
*
|
|
1380
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1381
|
+
*/
|
|
1382
|
+
public async $getNewTOTPSecret() {
|
|
1383
|
+
const tilmeld = enforceTilmeld(this);
|
|
1384
|
+
|
|
1385
|
+
if (this.$data.totpSecret != null) {
|
|
1386
|
+
throw new BadDataError('You already have a 2FA secret.');
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const secret = new Secret();
|
|
1390
|
+
|
|
1391
|
+
const totp = new TOTP({
|
|
1392
|
+
issuer: tilmeld.config.appName,
|
|
1393
|
+
label: this.$data.username,
|
|
1394
|
+
algorithm: 'SHA1',
|
|
1395
|
+
digits: 6,
|
|
1396
|
+
period: 30,
|
|
1397
|
+
secret,
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
const uri = totp.toString();
|
|
1401
|
+
|
|
1402
|
+
const qrcode = await toDataURL(uri);
|
|
1403
|
+
|
|
1404
|
+
return {
|
|
1405
|
+
uri,
|
|
1406
|
+
qrcode,
|
|
1407
|
+
secret: secret.base32,
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1413
|
+
*
|
|
1414
|
+
* @param data The input data from the client.
|
|
1415
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1416
|
+
*/
|
|
1417
|
+
public async $saveTOTPSecret(data: {
|
|
1418
|
+
password: string;
|
|
1419
|
+
secret: string;
|
|
1420
|
+
code: string;
|
|
1421
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1422
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1423
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (this.$data.totpSecret != null) {
|
|
1427
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (
|
|
1431
|
+
data.secret == null ||
|
|
1432
|
+
typeof data.secret !== 'string' ||
|
|
1433
|
+
data.secret === ''
|
|
1434
|
+
) {
|
|
1435
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (
|
|
1439
|
+
data.code == null ||
|
|
1440
|
+
typeof data.code !== 'string' ||
|
|
1441
|
+
data.code.length !== 6
|
|
1442
|
+
) {
|
|
1443
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
const tilmeld = enforceTilmeld(this);
|
|
1447
|
+
|
|
1448
|
+
const totp = new TOTP({
|
|
1449
|
+
issuer: tilmeld.config.appName,
|
|
1450
|
+
label: this.$data.username,
|
|
1451
|
+
algorithm: 'SHA1',
|
|
1452
|
+
digits: 6,
|
|
1453
|
+
period: 30,
|
|
1454
|
+
secret: data.secret,
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1458
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
this.$data.totpSecret = data.secret;
|
|
1462
|
+
|
|
1463
|
+
if (await this.$save()) {
|
|
1464
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1465
|
+
} else {
|
|
1466
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1472
|
+
* account.
|
|
1473
|
+
*
|
|
1474
|
+
* @param data The input data from the client.
|
|
1475
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1476
|
+
*/
|
|
1477
|
+
public async $removeTOTPSecret(data?: {
|
|
1478
|
+
password: string;
|
|
1479
|
+
code: string;
|
|
1480
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1481
|
+
const tilmeld = enforceTilmeld(this);
|
|
1482
|
+
|
|
1483
|
+
if (data) {
|
|
1484
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1485
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (
|
|
1489
|
+
data.code == null ||
|
|
1490
|
+
typeof data.code !== 'string' ||
|
|
1491
|
+
data.code.length !== 6
|
|
1492
|
+
) {
|
|
1493
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (this.$data.totpSecret == null) {
|
|
1497
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const totp = new TOTP({
|
|
1501
|
+
issuer: tilmeld.config.appName,
|
|
1502
|
+
label: this.$data.username,
|
|
1503
|
+
algorithm: 'SHA1',
|
|
1504
|
+
digits: 6,
|
|
1505
|
+
period: 30,
|
|
1506
|
+
secret: this.$data.totpSecret,
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1510
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1511
|
+
}
|
|
1512
|
+
} else if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1513
|
+
return {
|
|
1514
|
+
result: false,
|
|
1515
|
+
message: 'You must provide your password and 2FA code.',
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
delete this.$data.totpSecret;
|
|
1520
|
+
|
|
1521
|
+
if (await this.$save()) {
|
|
1522
|
+
return {
|
|
1523
|
+
result: true,
|
|
1524
|
+
message: 'Two factor secret has been removed.',
|
|
1525
|
+
};
|
|
1526
|
+
} else {
|
|
1527
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1054
1531
|
/**
|
|
1055
1532
|
* Check that a username is valid.
|
|
1056
1533
|
*
|
|
1057
1534
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1058
1535
|
*/
|
|
1059
1536
|
public async $checkUsername(): Promise<{ result: boolean; message: string }> {
|
|
1060
|
-
const tilmeld = this
|
|
1537
|
+
const tilmeld = enforceTilmeld(this);
|
|
1538
|
+
|
|
1539
|
+
this.$nymph.config.debugLog(
|
|
1540
|
+
'tilmeld',
|
|
1541
|
+
`Username check "${this.$data.username}".`,
|
|
1542
|
+
);
|
|
1543
|
+
|
|
1061
1544
|
if (!tilmeld.config.emailUsernames) {
|
|
1062
1545
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1063
1546
|
return { result: false, message: 'Please specify a username.' };
|
|
1064
1547
|
}
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1548
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1549
|
+
return {
|
|
1550
|
+
result: false,
|
|
1551
|
+
message:
|
|
1552
|
+
'Usernames must be at least ' +
|
|
1553
|
+
tilmeld.config.minUsernameLength +
|
|
1554
|
+
' characters.',
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
1069
1558
|
return {
|
|
1070
1559
|
result: false,
|
|
1071
1560
|
message:
|
|
@@ -1078,7 +1567,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1078
1567
|
if (
|
|
1079
1568
|
difference(
|
|
1080
1569
|
this.$data.username.split(''),
|
|
1081
|
-
tilmeld.config.validChars.split('')
|
|
1570
|
+
tilmeld.config.validChars.split(''),
|
|
1082
1571
|
).length
|
|
1083
1572
|
) {
|
|
1084
1573
|
return {
|
|
@@ -1105,12 +1594,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
1105
1594
|
}
|
|
1106
1595
|
const test = await this.$nymph.getEntity(
|
|
1107
1596
|
{ class: tilmeld.User, skipAc: true },
|
|
1108
|
-
selector
|
|
1597
|
+
selector,
|
|
1109
1598
|
);
|
|
1110
1599
|
if (test != null) {
|
|
1111
1600
|
return { result: false, message: 'That username is taken.' };
|
|
1112
1601
|
}
|
|
1113
1602
|
|
|
1603
|
+
for (let callback of (this.constructor as typeof User)
|
|
1604
|
+
.checkUsernameCallbacks) {
|
|
1605
|
+
if (callback) {
|
|
1606
|
+
const result = await callback(this, {
|
|
1607
|
+
username: this.$data.username,
|
|
1608
|
+
});
|
|
1609
|
+
if (!result.result) {
|
|
1610
|
+
return { message: 'Username is not available.', ...result };
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1114
1615
|
return {
|
|
1115
1616
|
result: true,
|
|
1116
1617
|
message:
|
|
@@ -1121,10 +1622,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1121
1622
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1122
1623
|
return { result: false, message: 'Please specify an email.' };
|
|
1123
1624
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1625
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1626
|
+
return {
|
|
1627
|
+
result: false,
|
|
1628
|
+
message:
|
|
1629
|
+
'Emails must be at least ' +
|
|
1630
|
+
tilmeld.config.minUsernameLength +
|
|
1631
|
+
' characters.',
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
1128
1635
|
return {
|
|
1129
1636
|
result: false,
|
|
1130
1637
|
message:
|
|
@@ -1144,7 +1651,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1144
1651
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1145
1652
|
*/
|
|
1146
1653
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
1147
|
-
const tilmeld = this
|
|
1654
|
+
const tilmeld = enforceTilmeld(this);
|
|
1655
|
+
|
|
1656
|
+
this.$nymph.config.debugLog(
|
|
1657
|
+
'tilmeld',
|
|
1658
|
+
`Email check "${this.$data.email}".`,
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1148
1661
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
1149
1662
|
if (tilmeld.config.verifyEmail) {
|
|
1150
1663
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -1167,7 +1680,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1167
1680
|
}
|
|
1168
1681
|
const test = await this.$nymph.getEntity(
|
|
1169
1682
|
{ class: tilmeld.User, skipAc: true },
|
|
1170
|
-
selector
|
|
1683
|
+
selector,
|
|
1171
1684
|
);
|
|
1172
1685
|
if (test != null) {
|
|
1173
1686
|
return {
|
|
@@ -1189,7 +1702,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1189
1702
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1190
1703
|
*/
|
|
1191
1704
|
public async $checkPhone(): Promise<{ result: boolean; message: string }> {
|
|
1192
|
-
const tilmeld = this
|
|
1705
|
+
const tilmeld = enforceTilmeld(this);
|
|
1706
|
+
|
|
1707
|
+
this.$nymph.config.debugLog(
|
|
1708
|
+
'tilmeld',
|
|
1709
|
+
`Phone check "${this.$data.phone}".`,
|
|
1710
|
+
);
|
|
1711
|
+
|
|
1193
1712
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
1194
1713
|
return { result: false, message: 'Please specify a phone number.' };
|
|
1195
1714
|
}
|
|
@@ -1211,7 +1730,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1211
1730
|
}
|
|
1212
1731
|
const test = await this.$nymph.getEntity(
|
|
1213
1732
|
{ class: tilmeld.User, skipAc: true },
|
|
1214
|
-
selector
|
|
1733
|
+
selector,
|
|
1215
1734
|
);
|
|
1216
1735
|
if (test != null) {
|
|
1217
1736
|
return { result: false, message: 'Phone number is in use.' };
|
|
@@ -1228,7 +1747,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1228
1747
|
password: string;
|
|
1229
1748
|
additionalData?: { [k: string]: any };
|
|
1230
1749
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1231
|
-
let tilmeld = this
|
|
1750
|
+
let tilmeld = enforceTilmeld(this);
|
|
1232
1751
|
if (!tilmeld.config.allowRegistration) {
|
|
1233
1752
|
return {
|
|
1234
1753
|
result: false,
|
|
@@ -1244,21 +1763,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
1244
1763
|
};
|
|
1245
1764
|
}
|
|
1246
1765
|
|
|
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
1766
|
if (!('password' in data) || !data.password.length) {
|
|
1263
1767
|
return {
|
|
1264
1768
|
result: false,
|
|
@@ -1272,7 +1776,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1272
1776
|
}
|
|
1273
1777
|
|
|
1274
1778
|
this.$password(data.password);
|
|
1275
|
-
if (tilmeld.config.regFields.
|
|
1779
|
+
if (tilmeld.config.regFields.includes('name')) {
|
|
1276
1780
|
this.$data.name =
|
|
1277
1781
|
this.$data.nameFirst +
|
|
1278
1782
|
(this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
|
|
@@ -1290,9 +1794,29 @@ export default class User extends AbleObject<UserData> {
|
|
|
1290
1794
|
const nymph = this.$nymph;
|
|
1291
1795
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1292
1796
|
this.$nymph = tnymph;
|
|
1293
|
-
tilmeld = this
|
|
1797
|
+
tilmeld = enforceTilmeld(this);
|
|
1798
|
+
|
|
1799
|
+
let message = '';
|
|
1800
|
+
let loggedin = false;
|
|
1294
1801
|
|
|
1295
1802
|
try {
|
|
1803
|
+
try {
|
|
1804
|
+
for (let callback of (this.constructor as typeof User)
|
|
1805
|
+
.beforeRegisterCallbacks) {
|
|
1806
|
+
if (callback) {
|
|
1807
|
+
await callback(this, data);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
} catch (e: any) {
|
|
1811
|
+
await tnymph.rollback(transaction);
|
|
1812
|
+
this.$nymph = nymph;
|
|
1813
|
+
return {
|
|
1814
|
+
result: false,
|
|
1815
|
+
loggedin: false,
|
|
1816
|
+
message: e.message,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1296
1820
|
// Add primary group.
|
|
1297
1821
|
let generatedPrimaryGroup: (Group & GroupData) | null = null;
|
|
1298
1822
|
if (tilmeld.config.generatePrimary) {
|
|
@@ -1300,14 +1824,18 @@ export default class User extends AbleObject<UserData> {
|
|
|
1300
1824
|
generatedPrimaryGroup = await tilmeld.Group.factory();
|
|
1301
1825
|
generatedPrimaryGroup.groupname = this.$data.username;
|
|
1302
1826
|
generatedPrimaryGroup.avatar = this.$data.avatar;
|
|
1303
|
-
|
|
1304
|
-
|
|
1827
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
1828
|
+
generatedPrimaryGroup.name = this.$data.name;
|
|
1829
|
+
}
|
|
1830
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
1831
|
+
generatedPrimaryGroup.email = this.$data.email;
|
|
1832
|
+
}
|
|
1305
1833
|
const parent = await tnymph.getEntity(
|
|
1306
1834
|
{ class: tilmeld.Group },
|
|
1307
1835
|
{
|
|
1308
1836
|
type: '&',
|
|
1309
1837
|
equal: ['defaultPrimary', true],
|
|
1310
|
-
}
|
|
1838
|
+
},
|
|
1311
1839
|
);
|
|
1312
1840
|
if (parent != null) {
|
|
1313
1841
|
generatedPrimaryGroup.parent = parent;
|
|
@@ -1329,7 +1857,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1329
1857
|
{
|
|
1330
1858
|
type: '&',
|
|
1331
1859
|
equal: ['defaultPrimary', true],
|
|
1332
|
-
}
|
|
1860
|
+
},
|
|
1333
1861
|
);
|
|
1334
1862
|
if (group != null) {
|
|
1335
1863
|
this.$data.group = group;
|
|
@@ -1337,14 +1865,18 @@ export default class User extends AbleObject<UserData> {
|
|
|
1337
1865
|
}
|
|
1338
1866
|
|
|
1339
1867
|
// Add secondary groups.
|
|
1340
|
-
if (
|
|
1868
|
+
if (
|
|
1869
|
+
tilmeld.config.userFields.includes('email') &&
|
|
1870
|
+
tilmeld.config.verifyEmail &&
|
|
1871
|
+
tilmeld.config.unverifiedAccess
|
|
1872
|
+
) {
|
|
1341
1873
|
// Add the default secondaries for unverified users.
|
|
1342
1874
|
this.$data.groups = await tnymph.getEntities(
|
|
1343
1875
|
{ class: tilmeld.Group },
|
|
1344
1876
|
{
|
|
1345
1877
|
type: '&',
|
|
1346
1878
|
equal: ['unverifiedSecondary', true],
|
|
1347
|
-
}
|
|
1879
|
+
},
|
|
1348
1880
|
);
|
|
1349
1881
|
} else {
|
|
1350
1882
|
// Add the default secondaries.
|
|
@@ -1353,11 +1885,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
1353
1885
|
{
|
|
1354
1886
|
type: '&',
|
|
1355
1887
|
equal: ['defaultSecondary', true],
|
|
1356
|
-
}
|
|
1888
|
+
},
|
|
1357
1889
|
);
|
|
1358
1890
|
}
|
|
1359
1891
|
|
|
1360
|
-
if (
|
|
1892
|
+
if (
|
|
1893
|
+
tilmeld.config.userFields.includes('email') &&
|
|
1894
|
+
tilmeld.config.verifyEmail
|
|
1895
|
+
) {
|
|
1361
1896
|
// The user will be enabled after verifying their e-mail address.
|
|
1362
1897
|
if (!tilmeld.config.unverifiedAccess) {
|
|
1363
1898
|
this.$data.enabled = false;
|
|
@@ -1384,7 +1919,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
1384
1919
|
}
|
|
1385
1920
|
}
|
|
1386
1921
|
|
|
1922
|
+
this.$nymph.config.debugLog(
|
|
1923
|
+
'tilmeld',
|
|
1924
|
+
`Registering new user "${this.$data.username}".`,
|
|
1925
|
+
);
|
|
1387
1926
|
if (await this.$saveSkipAC()) {
|
|
1927
|
+
this.$nymph.config.debugLog(
|
|
1928
|
+
'tilmeld',
|
|
1929
|
+
`New user registered "${this.$data.username}".`,
|
|
1930
|
+
);
|
|
1388
1931
|
// Send the new user registered email.
|
|
1389
1932
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
1390
1933
|
await tilmeld.config.sendEmail(
|
|
@@ -1403,13 +1946,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1403
1946
|
userPhone: this.$data.phone,
|
|
1404
1947
|
},
|
|
1405
1948
|
},
|
|
1406
|
-
this
|
|
1949
|
+
this,
|
|
1407
1950
|
);
|
|
1408
1951
|
}
|
|
1409
1952
|
|
|
1410
|
-
let message = '';
|
|
1411
|
-
let loggedin = false;
|
|
1412
|
-
|
|
1413
1953
|
// Save the primary group.
|
|
1414
1954
|
if (generatedPrimaryGroup != null) {
|
|
1415
1955
|
generatedPrimaryGroup.user = this;
|
|
@@ -1429,7 +1969,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
1429
1969
|
if (
|
|
1430
1970
|
tilmeld.config.verifyEmail &&
|
|
1431
1971
|
!tilmeld.config.unverifiedAccess &&
|
|
1432
|
-
!madeAdmin
|
|
1972
|
+
!madeAdmin &&
|
|
1973
|
+
tilmeld.config.userFields.includes('email')
|
|
1433
1974
|
) {
|
|
1434
1975
|
message +=
|
|
1435
1976
|
`Almost there. An email has been sent to ${this.$data.email} ` +
|
|
@@ -1437,9 +1978,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1437
1978
|
} else if (
|
|
1438
1979
|
tilmeld.config.verifyEmail &&
|
|
1439
1980
|
tilmeld.config.unverifiedAccess &&
|
|
1440
|
-
!madeAdmin
|
|
1981
|
+
!madeAdmin &&
|
|
1982
|
+
tilmeld.config.userFields.includes('email')
|
|
1441
1983
|
) {
|
|
1442
|
-
if (!tilmeld.login(this, true)) {
|
|
1984
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1443
1985
|
throw new Error('An error occurred trying to log you in.');
|
|
1444
1986
|
}
|
|
1445
1987
|
message +=
|
|
@@ -1448,7 +1990,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1448
1990
|
'registration.';
|
|
1449
1991
|
loggedin = true;
|
|
1450
1992
|
} else {
|
|
1451
|
-
if (!tilmeld.login(this, true)) {
|
|
1993
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1452
1994
|
throw new Error('An error occurred trying to log you in.');
|
|
1453
1995
|
}
|
|
1454
1996
|
message += "You're now registered and logged in!";
|
|
@@ -1464,21 +2006,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1464
2006
|
});
|
|
1465
2007
|
}
|
|
1466
2008
|
}
|
|
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
2009
|
} else {
|
|
2010
|
+
this.$nymph.config.debugError(
|
|
2011
|
+
'tilmeld',
|
|
2012
|
+
`Error registering new user "${this.$data.username}".`,
|
|
2013
|
+
);
|
|
1482
2014
|
await tnymph.rollback(transaction);
|
|
1483
2015
|
this.$nymph = nymph;
|
|
1484
2016
|
return {
|
|
@@ -1488,14 +2020,32 @@ export default class User extends AbleObject<UserData> {
|
|
|
1488
2020
|
};
|
|
1489
2021
|
}
|
|
1490
2022
|
} catch (e: any) {
|
|
2023
|
+
this.$nymph.config.debugError(
|
|
2024
|
+
'tilmeld',
|
|
2025
|
+
`Error registering new user "${this.$data.username}": ${e}`,
|
|
2026
|
+
);
|
|
1491
2027
|
await tnymph.rollback(transaction);
|
|
1492
2028
|
this.$nymph = nymph;
|
|
1493
2029
|
throw e;
|
|
1494
2030
|
}
|
|
2031
|
+
|
|
2032
|
+
try {
|
|
2033
|
+
await tnymph.commit(transaction);
|
|
2034
|
+
this.$nymph = nymph;
|
|
2035
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
2036
|
+
} catch (e: any) {
|
|
2037
|
+
throw e;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
return {
|
|
2041
|
+
result: true,
|
|
2042
|
+
loggedin,
|
|
2043
|
+
message,
|
|
2044
|
+
};
|
|
1495
2045
|
}
|
|
1496
2046
|
|
|
1497
2047
|
public async $save() {
|
|
1498
|
-
let tilmeld = this
|
|
2048
|
+
let tilmeld = enforceTilmeld(this);
|
|
1499
2049
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1500
2050
|
return false;
|
|
1501
2051
|
}
|
|
@@ -1503,10 +2053,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1503
2053
|
if (
|
|
1504
2054
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1505
2055
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1506
|
-
this.$
|
|
2056
|
+
this.$data.abilities?.includes('system/admin')
|
|
1507
2057
|
) {
|
|
1508
2058
|
throw new BadDataError(
|
|
1509
|
-
"You don't have the authority to modify system admins."
|
|
2059
|
+
"You don't have the authority to modify system admins.",
|
|
1510
2060
|
);
|
|
1511
2061
|
}
|
|
1512
2062
|
|
|
@@ -1528,49 +2078,88 @@ export default class User extends AbleObject<UserData> {
|
|
|
1528
2078
|
this.$data.enabled = !!this.$data.enabled;
|
|
1529
2079
|
|
|
1530
2080
|
// Clear empty values.
|
|
1531
|
-
if (
|
|
2081
|
+
if (
|
|
2082
|
+
this.$data.nameFirst === '' ||
|
|
2083
|
+
!tilmeld.config.userFields.includes('name')
|
|
2084
|
+
) {
|
|
1532
2085
|
delete this.$data.nameFirst;
|
|
1533
2086
|
}
|
|
1534
|
-
if (
|
|
2087
|
+
if (
|
|
2088
|
+
this.$data.nameMiddle === '' ||
|
|
2089
|
+
!tilmeld.config.userFields.includes('name')
|
|
2090
|
+
) {
|
|
1535
2091
|
delete this.$data.nameMiddle;
|
|
1536
2092
|
}
|
|
1537
|
-
if (
|
|
2093
|
+
if (
|
|
2094
|
+
this.$data.nameLast === '' ||
|
|
2095
|
+
!tilmeld.config.userFields.includes('name')
|
|
2096
|
+
) {
|
|
1538
2097
|
delete this.$data.nameLast;
|
|
1539
2098
|
}
|
|
1540
|
-
if (this.$data.name === '') {
|
|
2099
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
1541
2100
|
delete this.$data.name;
|
|
1542
2101
|
}
|
|
1543
|
-
if (
|
|
2102
|
+
if (
|
|
2103
|
+
this.$data.email === '' ||
|
|
2104
|
+
!tilmeld.config.userFields.includes('email')
|
|
2105
|
+
) {
|
|
1544
2106
|
delete this.$data.email;
|
|
1545
2107
|
}
|
|
1546
2108
|
if (this.$data.avatar === '') {
|
|
1547
2109
|
delete this.$data.avatar;
|
|
1548
2110
|
}
|
|
1549
|
-
if (
|
|
2111
|
+
if (
|
|
2112
|
+
this.$data.phone === '' ||
|
|
2113
|
+
!tilmeld.config.userFields.includes('phone')
|
|
2114
|
+
) {
|
|
1550
2115
|
delete this.$data.phone;
|
|
1551
2116
|
}
|
|
1552
|
-
if (
|
|
2117
|
+
if (
|
|
2118
|
+
this.$data.secret === '' ||
|
|
2119
|
+
!tilmeld.config.userFields.includes('email')
|
|
2120
|
+
) {
|
|
1553
2121
|
delete this.$data.secret;
|
|
1554
2122
|
}
|
|
1555
|
-
if (
|
|
2123
|
+
if (
|
|
2124
|
+
this.$data.emailChangeDate === 0 ||
|
|
2125
|
+
!tilmeld.config.userFields.includes('email')
|
|
2126
|
+
) {
|
|
1556
2127
|
delete this.$data.emailChangeDate;
|
|
1557
2128
|
}
|
|
1558
|
-
if (
|
|
2129
|
+
if (
|
|
2130
|
+
this.$data.newEmailSecret === '' ||
|
|
2131
|
+
!tilmeld.config.userFields.includes('email')
|
|
2132
|
+
) {
|
|
1559
2133
|
delete this.$data.newEmailSecret;
|
|
1560
2134
|
}
|
|
1561
|
-
if (
|
|
2135
|
+
if (
|
|
2136
|
+
this.$data.newEmailAddress === '' ||
|
|
2137
|
+
!tilmeld.config.userFields.includes('email')
|
|
2138
|
+
) {
|
|
1562
2139
|
delete this.$data.newEmailAddress;
|
|
1563
2140
|
}
|
|
1564
|
-
if (
|
|
2141
|
+
if (
|
|
2142
|
+
this.$data.cancelEmailSecret === '' ||
|
|
2143
|
+
!tilmeld.config.userFields.includes('email')
|
|
2144
|
+
) {
|
|
1565
2145
|
delete this.$data.cancelEmailSecret;
|
|
1566
2146
|
}
|
|
1567
|
-
if (
|
|
2147
|
+
if (
|
|
2148
|
+
this.$data.cancelEmailAddress === '' ||
|
|
2149
|
+
!tilmeld.config.userFields.includes('email')
|
|
2150
|
+
) {
|
|
1568
2151
|
delete this.$data.cancelEmailAddress;
|
|
1569
2152
|
}
|
|
1570
|
-
if (
|
|
2153
|
+
if (
|
|
2154
|
+
this.$data.recoverSecret === '' ||
|
|
2155
|
+
!tilmeld.config.userFields.includes('email')
|
|
2156
|
+
) {
|
|
1571
2157
|
delete this.$data.recoverSecret;
|
|
1572
2158
|
}
|
|
1573
|
-
if (
|
|
2159
|
+
if (
|
|
2160
|
+
this.$data.recoverSecretDate === 0 ||
|
|
2161
|
+
!tilmeld.config.userFields.includes('email')
|
|
2162
|
+
) {
|
|
1574
2163
|
delete this.$data.recoverSecretDate;
|
|
1575
2164
|
}
|
|
1576
2165
|
if (this.$data.password === '') {
|
|
@@ -1588,7 +2177,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1588
2177
|
if (!unCheck.result) {
|
|
1589
2178
|
throw new BadUsernameError(unCheck.message);
|
|
1590
2179
|
}
|
|
1591
|
-
if (
|
|
2180
|
+
if (
|
|
2181
|
+
!tilmeld.config.emailUsernames &&
|
|
2182
|
+
tilmeld.config.userFields.includes('email')
|
|
2183
|
+
) {
|
|
1592
2184
|
const emCheck = await this.$checkEmail();
|
|
1593
2185
|
if (!emCheck.result) {
|
|
1594
2186
|
throw new BadEmailError(emCheck.message);
|
|
@@ -1596,7 +2188,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1596
2188
|
}
|
|
1597
2189
|
|
|
1598
2190
|
// Email changes.
|
|
1599
|
-
if (
|
|
2191
|
+
if (
|
|
2192
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
2193
|
+
tilmeld.config.userFields.includes('email')
|
|
2194
|
+
) {
|
|
1600
2195
|
// The user isn't an admin, so email address changes should contain some
|
|
1601
2196
|
// security measures.
|
|
1602
2197
|
if (tilmeld.config.verifyEmail) {
|
|
@@ -1604,7 +2199,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1604
2199
|
if (this.guid == null) {
|
|
1605
2200
|
// If this is the first user, they'll be made an admin, so they don't
|
|
1606
2201
|
// need to verify.
|
|
1607
|
-
if (
|
|
2202
|
+
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1608
2203
|
this.$data.secret = nanoid();
|
|
1609
2204
|
sendVerification = true;
|
|
1610
2205
|
}
|
|
@@ -1624,10 +2219,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1624
2219
|
new Date(
|
|
1625
2220
|
strtotime(
|
|
1626
2221
|
'+' + tilmeld.config.emailRateLimit,
|
|
1627
|
-
Math.floor(this.$data.emailChangeDate / 1000)
|
|
1628
|
-
) * 1000
|
|
2222
|
+
Math.floor(this.$data.emailChangeDate / 1000),
|
|
2223
|
+
) * 1000,
|
|
1629
2224
|
).toString() +
|
|
1630
|
-
' to change your email address again.'
|
|
2225
|
+
' to change your email address again.',
|
|
1631
2226
|
);
|
|
1632
2227
|
} else {
|
|
1633
2228
|
if (
|
|
@@ -1683,7 +2278,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1683
2278
|
}
|
|
1684
2279
|
|
|
1685
2280
|
try {
|
|
1686
|
-
tilmeld.config.validatorUser(this);
|
|
2281
|
+
tilmeld.config.validatorUser(tilmeld, this);
|
|
1687
2282
|
} catch (e: any) {
|
|
1688
2283
|
throw new BadDataError(e?.message);
|
|
1689
2284
|
}
|
|
@@ -1693,21 +2288,26 @@ export default class User extends AbleObject<UserData> {
|
|
|
1693
2288
|
const nymph = this.$nymph;
|
|
1694
2289
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1695
2290
|
this.$nymph = tnymph;
|
|
1696
|
-
tilmeld = this
|
|
2291
|
+
tilmeld = enforceTilmeld(this);
|
|
1697
2292
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
this.$is(this.$data.group.user)
|
|
1702
|
-
) {
|
|
2293
|
+
const group = this.$data.group;
|
|
2294
|
+
await group?.$wake();
|
|
2295
|
+
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1703
2296
|
try {
|
|
1704
2297
|
// Update the user's generated primary group.
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2298
|
+
group.groupname = this.$data.username;
|
|
2299
|
+
group.avatar = this.$data.avatar;
|
|
2300
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
2301
|
+
group.email = this.$data.email;
|
|
2302
|
+
}
|
|
2303
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
2304
|
+
group.name = this.$data.name;
|
|
2305
|
+
}
|
|
2306
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
2307
|
+
group.phone = this.$data.phone;
|
|
2308
|
+
}
|
|
2309
|
+
await group.$saveSkipAC();
|
|
2310
|
+
this.$data.group = group;
|
|
1711
2311
|
} catch (e: any) {
|
|
1712
2312
|
await tnymph.rollback(transaction);
|
|
1713
2313
|
this.$nymph = nymph;
|
|
@@ -1744,9 +2344,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1744
2344
|
this.$gatekeeperCache = undefined;
|
|
1745
2345
|
await tnymph.commit(transaction);
|
|
1746
2346
|
|
|
1747
|
-
|
|
2347
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
2348
|
+
|
|
2349
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
1748
2350
|
// Update the user in the session cache.
|
|
1749
|
-
|
|
2351
|
+
await tilmeld.fillSession(this);
|
|
1750
2352
|
}
|
|
1751
2353
|
} else {
|
|
1752
2354
|
await tnymph.rollback(transaction);
|
|
@@ -1772,16 +2374,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1772
2374
|
}
|
|
1773
2375
|
|
|
1774
2376
|
public async $delete() {
|
|
1775
|
-
const tilmeld = this
|
|
2377
|
+
const tilmeld = enforceTilmeld(this);
|
|
1776
2378
|
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1777
2379
|
throw new BadDataError("You don't have the authority to delete users.");
|
|
1778
2380
|
}
|
|
1779
2381
|
if (
|
|
1780
2382
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1781
|
-
this.$
|
|
2383
|
+
this.$data.abilities?.includes('system/admin')
|
|
1782
2384
|
) {
|
|
1783
2385
|
throw new BadDataError(
|
|
1784
|
-
"You don't have the authority to delete system admins."
|
|
2386
|
+
"You don't have the authority to delete system admins.",
|
|
1785
2387
|
);
|
|
1786
2388
|
}
|
|
1787
2389
|
if (tilmeld.User.current(true).$is(this)) {
|
|
@@ -1792,7 +2394,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1792
2394
|
|
|
1793
2395
|
public static on<T extends EventType>(
|
|
1794
2396
|
event: T,
|
|
1795
|
-
callback: T extends '
|
|
2397
|
+
callback: T extends 'checkUsername'
|
|
2398
|
+
? TilmeldCheckUsernameCallback
|
|
2399
|
+
: T extends 'beforeRegister'
|
|
1796
2400
|
? TilmeldBeforeRegisterCallback
|
|
1797
2401
|
: T extends 'afterRegister'
|
|
1798
2402
|
? TilmeldAfterRegisterCallback
|
|
@@ -1804,9 +2408,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1804
2408
|
? TilmeldBeforeLogoutCallback
|
|
1805
2409
|
: T extends 'afterLogout'
|
|
1806
2410
|
? TilmeldAfterLogoutCallback
|
|
1807
|
-
: never
|
|
2411
|
+
: never,
|
|
1808
2412
|
) {
|
|
1809
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2413
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2414
|
+
? 'checkUsernameCallbacks'
|
|
2415
|
+
: T extends 'beforeRegister'
|
|
1810
2416
|
? 'beforeRegisterCallbacks'
|
|
1811
2417
|
: T extends 'afterRegister'
|
|
1812
2418
|
? 'afterRegisterCallbacks'
|
|
@@ -1829,7 +2435,9 @@ export default class User extends AbleObject<UserData> {
|
|
|
1829
2435
|
|
|
1830
2436
|
public static off<T extends EventType>(
|
|
1831
2437
|
event: T,
|
|
1832
|
-
callback: T extends '
|
|
2438
|
+
callback: T extends 'checkUsername'
|
|
2439
|
+
? TilmeldCheckUsernameCallback
|
|
2440
|
+
: T extends 'beforeRegister'
|
|
1833
2441
|
? TilmeldBeforeRegisterCallback
|
|
1834
2442
|
: T extends 'afterRegister'
|
|
1835
2443
|
? TilmeldAfterRegisterCallback
|
|
@@ -1841,9 +2449,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
1841
2449
|
? TilmeldBeforeLogoutCallback
|
|
1842
2450
|
: T extends 'afterLogout'
|
|
1843
2451
|
? TilmeldAfterLogoutCallback
|
|
1844
|
-
: never
|
|
2452
|
+
: never,
|
|
1845
2453
|
) {
|
|
1846
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
2454
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2455
|
+
? 'checkUsernameCallbacks'
|
|
2456
|
+
: T extends 'beforeRegister'
|
|
1847
2457
|
? 'beforeRegisterCallbacks'
|
|
1848
2458
|
: T extends 'afterRegister'
|
|
1849
2459
|
? 'afterRegisterCallbacks'
|