@nymphjs/tilmeld 1.0.0-beta.10 → 1.0.0-beta.100
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 +455 -0
- package/README.md +15 -2
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +18 -6
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +181 -15
- package/dist/Group.js +551 -192
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +83 -13
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +241 -12
- package/dist/Tilmeld.js +652 -253
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +2 -2
- package/dist/Tilmeld.types.js +1 -2
- package/dist/User.d.ts +395 -22
- package/dist/User.js +1355 -368
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +130 -13
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +306 -7
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +196 -141
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +18 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -6
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -6
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -6
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -6
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
- package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
- package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
- package/dist/errors/index.d.ts +6 -6
- package/dist/errors/index.js +6 -22
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +16 -35
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.js +13 -18
- package/dist/tilmeldJoiProps.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +34 -31
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +104 -6
- package/src/Group.ts +661 -226
- package/src/Tilmeld.ts +441 -254
- package/src/Tilmeld.types.ts +2 -2
- package/src/User.test.ts +160 -7
- package/src/User.ts +1596 -457
- package/src/conf/d.ts +83 -17
- package/src/conf/defaults.ts +172 -109
- package/src/conf/index.ts +2 -2
- 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/errors/index.ts +6 -6
- package/src/index.ts +12 -10
- package/src/tilmeldJoiProps.ts +11 -11
- package/src/type/locutus.d.ts +3 -0
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
package/src/User.ts
CHANGED
|
@@ -1,49 +1,87 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type {
|
|
3
|
+
Nymph,
|
|
2
4
|
EntityData,
|
|
3
5
|
EntityJson,
|
|
4
6
|
EntityPatch,
|
|
5
7
|
Selector,
|
|
6
8
|
SerializedEntityData,
|
|
7
9
|
} from '@nymphjs/nymph';
|
|
8
|
-
import { EmailOptions } from 'email-templates';
|
|
9
|
-
import strtotime from 'locutus/php/datetime/strtotime';
|
|
10
|
-
import Base64 from 'crypto-js/enc-base64';
|
|
11
|
-
import sha256 from 'crypto-js/sha256';
|
|
12
|
-
import md5 from 'crypto-js/md5';
|
|
13
|
-
import { difference } from 'lodash';
|
|
14
10
|
import { humanSecret, nanoid } from '@nymphjs/guid';
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
11
|
+
import type { EmailOptions } from 'email-templates';
|
|
12
|
+
import strtotime from 'locutus/php/datetime/strtotime.js';
|
|
13
|
+
import { difference, xor } from 'lodash-es';
|
|
14
|
+
import { TOTP, Secret } from 'otpauth';
|
|
15
|
+
import { toDataURL } from 'qrcode';
|
|
16
|
+
import { splitn } from '@sciactive/splitn';
|
|
17
|
+
|
|
18
|
+
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
19
|
+
import AbleObject from './AbleObject.js';
|
|
20
|
+
import GroupClass from './Group.js';
|
|
21
|
+
import type { GroupData } from './Group.js';
|
|
19
22
|
import {
|
|
23
|
+
AccessControlError,
|
|
20
24
|
BadDataError,
|
|
21
25
|
BadEmailError,
|
|
22
26
|
BadUsernameError,
|
|
23
27
|
EmailChangeRateLimitExceededError,
|
|
24
|
-
} from './errors';
|
|
28
|
+
} from './errors/index.js';
|
|
25
29
|
|
|
26
30
|
export type EventType =
|
|
31
|
+
| 'checkUsername'
|
|
27
32
|
| 'beforeRegister'
|
|
28
33
|
| 'afterRegister'
|
|
34
|
+
| 'beforeSave'
|
|
35
|
+
| 'afterSave'
|
|
29
36
|
| 'beforeLogin'
|
|
30
37
|
| 'afterLogin'
|
|
31
38
|
| 'beforeLogout'
|
|
32
|
-
| 'afterLogout'
|
|
39
|
+
| 'afterLogout'
|
|
40
|
+
| 'beforeDelete'
|
|
41
|
+
| 'afterDelete';
|
|
42
|
+
/**
|
|
43
|
+
* This is run when the user has entered an otherwise valid username into the
|
|
44
|
+
* signup form. It should return a result, which when false will stop the
|
|
45
|
+
* process and return the included message, disallowing the username.
|
|
46
|
+
*/
|
|
47
|
+
export type TilmeldCheckUsernameCallback = (
|
|
48
|
+
user: User & UserData,
|
|
49
|
+
data: { username: string },
|
|
50
|
+
) => Promise<{ result: boolean; message?: string }>;
|
|
33
51
|
/**
|
|
34
|
-
*
|
|
52
|
+
* This is run before the user data checks, so the only checks before are
|
|
35
53
|
* whether registration is allowed and whether the user is already registered.
|
|
36
54
|
*/
|
|
37
55
|
export type TilmeldBeforeRegisterCallback = (
|
|
38
56
|
user: User & UserData,
|
|
39
|
-
data: { password: string; additionalData?: { [k: string]: any } }
|
|
57
|
+
data: { password: string; additionalData?: { [k: string]: any } },
|
|
40
58
|
) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* This is run before the transaction is committed, and you can perform
|
|
61
|
+
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
62
|
+
*/
|
|
41
63
|
export type TilmeldAfterRegisterCallback = (
|
|
42
64
|
user: User & UserData,
|
|
43
|
-
result: { loggedin: boolean; message: string }
|
|
65
|
+
result: { loggedin: boolean; message: string },
|
|
66
|
+
) => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* This is run just before the user is saved, so all checks have passed.
|
|
69
|
+
*
|
|
70
|
+
* The transaction is available in `user.$nymph`.
|
|
71
|
+
*/
|
|
72
|
+
export type TilmeldBeforeSaveCallback = (
|
|
73
|
+
user: User & UserData,
|
|
44
74
|
) => Promise<void>;
|
|
45
75
|
/**
|
|
46
|
-
*
|
|
76
|
+
* This is run before the transaction is committed, and you can perform
|
|
77
|
+
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
78
|
+
*
|
|
79
|
+
* This is run after a new email verification email has been sent, so throwing
|
|
80
|
+
* an error will cause confusion for the user if they've changed their email.
|
|
81
|
+
*/
|
|
82
|
+
export type TilmeldAfterSaveCallback = (user: User & UserData) => Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* This is run after the authentication checks, but before the login action.
|
|
47
85
|
*/
|
|
48
86
|
export type TilmeldBeforeLoginCallback = (
|
|
49
87
|
user: User & UserData,
|
|
@@ -51,20 +89,32 @@ export type TilmeldBeforeLoginCallback = (
|
|
|
51
89
|
username: string;
|
|
52
90
|
password: string;
|
|
53
91
|
additionalData?: { [k: string]: any };
|
|
54
|
-
}
|
|
92
|
+
},
|
|
55
93
|
) => Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* This is run before the transaction is committed, and you can perform
|
|
58
|
-
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
59
|
-
*/
|
|
60
94
|
export type TilmeldAfterLoginCallback = (
|
|
61
|
-
user: User & UserData
|
|
95
|
+
user: User & UserData,
|
|
62
96
|
) => Promise<void>;
|
|
63
97
|
export type TilmeldBeforeLogoutCallback = (
|
|
64
|
-
user: User & UserData
|
|
98
|
+
user: User & UserData,
|
|
65
99
|
) => Promise<void>;
|
|
66
100
|
export type TilmeldAfterLogoutCallback = (
|
|
67
|
-
user: User & UserData
|
|
101
|
+
user: User & UserData,
|
|
102
|
+
) => Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* This is run before the user is logged out (if the current user is being
|
|
105
|
+
* deleted).
|
|
106
|
+
*
|
|
107
|
+
* The transaction is available in `user.$nymph`.
|
|
108
|
+
*/
|
|
109
|
+
export type TilmeldBeforeDeleteCallback = (
|
|
110
|
+
user: User & UserData,
|
|
111
|
+
) => Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* This is run before the transaction is committed, and you can perform
|
|
114
|
+
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
115
|
+
*/
|
|
116
|
+
export type TilmeldAfterDeleteCallback = (
|
|
117
|
+
user: User & UserData,
|
|
68
118
|
) => Promise<void>;
|
|
69
119
|
|
|
70
120
|
export type UserData = {
|
|
@@ -108,11 +158,11 @@ export type UserData = {
|
|
|
108
158
|
/**
|
|
109
159
|
* The user's primary group.
|
|
110
160
|
*/
|
|
111
|
-
group?:
|
|
161
|
+
group?: GroupClass & GroupData;
|
|
112
162
|
/**
|
|
113
163
|
* The user's secondary groups.
|
|
114
164
|
*/
|
|
115
|
-
groups?: (
|
|
165
|
+
groups?: (GroupClass & GroupData)[];
|
|
116
166
|
/**
|
|
117
167
|
* Whether the user should inherit the abilities of his groups.
|
|
118
168
|
*/
|
|
@@ -135,7 +185,8 @@ export type UserData = {
|
|
|
135
185
|
*/
|
|
136
186
|
newEmailSecret?: string;
|
|
137
187
|
/**
|
|
138
|
-
*
|
|
188
|
+
* If the user has changed their email address, this is the new one, awaiting
|
|
189
|
+
* verification.
|
|
139
190
|
*/
|
|
140
191
|
newEmailAddress?: string;
|
|
141
192
|
/**
|
|
@@ -167,6 +218,15 @@ export type UserData = {
|
|
|
167
218
|
* database.
|
|
168
219
|
*/
|
|
169
220
|
passwordTemp?: string;
|
|
221
|
+
/**
|
|
222
|
+
* If set, this timestamp is the cutoff point for JWT issue dates. Any token
|
|
223
|
+
* issued before this date will not authenticate the user.
|
|
224
|
+
*/
|
|
225
|
+
revokeTokenDate?: number;
|
|
226
|
+
/**
|
|
227
|
+
* Two factor auth secret.
|
|
228
|
+
*/
|
|
229
|
+
totpSecret?: string;
|
|
170
230
|
};
|
|
171
231
|
|
|
172
232
|
/**
|
|
@@ -182,15 +242,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
182
242
|
/**
|
|
183
243
|
* The instance of Tilmeld to use for queries.
|
|
184
244
|
*/
|
|
185
|
-
static tilmeld: Tilmeld;
|
|
186
245
|
static ETYPE = 'tilmeld_user';
|
|
187
246
|
static class = 'User';
|
|
247
|
+
private static checkUsernameCallbacks: TilmeldCheckUsernameCallback[] = [];
|
|
188
248
|
private static beforeRegisterCallbacks: TilmeldBeforeRegisterCallback[] = [];
|
|
189
249
|
private static afterRegisterCallbacks: TilmeldAfterRegisterCallback[] = [];
|
|
250
|
+
private static beforeSaveCallbacks: TilmeldBeforeSaveCallback[] = [];
|
|
251
|
+
private static afterSaveCallbacks: TilmeldAfterSaveCallback[] = [];
|
|
190
252
|
private static beforeLoginCallbacks: TilmeldBeforeLoginCallback[] = [];
|
|
191
253
|
private static afterLoginCallbacks: TilmeldAfterLoginCallback[] = [];
|
|
192
254
|
private static beforeLogoutCallbacks: TilmeldBeforeLogoutCallback[] = [];
|
|
193
255
|
private static afterLogoutCallbacks: TilmeldAfterLogoutCallback[] = [];
|
|
256
|
+
private static beforeDeleteCallbacks: TilmeldBeforeDeleteCallback[] = [];
|
|
257
|
+
private static afterDeleteCallbacks: TilmeldAfterDeleteCallback[] = [];
|
|
194
258
|
|
|
195
259
|
private static DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
196
260
|
'$checkUsername',
|
|
@@ -200,23 +264,33 @@ export default class User extends AbleObject<UserData> {
|
|
|
200
264
|
'$register',
|
|
201
265
|
];
|
|
202
266
|
private static DEFAULT_PRIVATE_DATA = [
|
|
267
|
+
// The things that are commented are there to show what is _not_ private.
|
|
268
|
+
'abilities',
|
|
269
|
+
// 'username',
|
|
270
|
+
// 'nameFirst',
|
|
271
|
+
// 'nameMiddle',
|
|
272
|
+
// 'nameLast',
|
|
273
|
+
// 'name',
|
|
203
274
|
'email',
|
|
204
|
-
'
|
|
275
|
+
// 'avatar',
|
|
205
276
|
'phone',
|
|
206
277
|
'group',
|
|
207
278
|
'groups',
|
|
208
|
-
'abilities',
|
|
209
279
|
'inheritAbilities',
|
|
210
|
-
'
|
|
211
|
-
'recoverSecretDate',
|
|
212
|
-
'password',
|
|
213
|
-
'salt',
|
|
280
|
+
'enabled',
|
|
214
281
|
'secret',
|
|
215
|
-
'
|
|
282
|
+
'emailChangeDate',
|
|
216
283
|
'newEmailSecret',
|
|
217
|
-
'
|
|
284
|
+
'newEmailAddress',
|
|
218
285
|
'cancelEmailSecret',
|
|
219
|
-
'
|
|
286
|
+
'cancelEmailAddress',
|
|
287
|
+
'recoverSecret',
|
|
288
|
+
'recoverSecretDate',
|
|
289
|
+
'salt',
|
|
290
|
+
'password',
|
|
291
|
+
'passwordTemp',
|
|
292
|
+
'revokeTokenDate',
|
|
293
|
+
'totpSecret',
|
|
220
294
|
];
|
|
221
295
|
private static DEFAULT_ALLOWLIST_DATA: string[] = [];
|
|
222
296
|
|
|
@@ -227,6 +301,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
227
301
|
'sendRecovery',
|
|
228
302
|
'recover',
|
|
229
303
|
'getClientConfig',
|
|
304
|
+
'getDomainUsers',
|
|
230
305
|
];
|
|
231
306
|
protected $privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
232
307
|
public static searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -241,13 +316,17 @@ export default class User extends AbleObject<UserData> {
|
|
|
241
316
|
*/
|
|
242
317
|
private $gatekeeperCache?: { [k: string]: true };
|
|
243
318
|
/**
|
|
244
|
-
* This
|
|
319
|
+
* This should only be used by the backend.
|
|
245
320
|
*/
|
|
246
321
|
private $skipAcWhenSaving = false;
|
|
322
|
+
/**
|
|
323
|
+
* This should only be used by the backend.
|
|
324
|
+
*/
|
|
325
|
+
private $skipAcWhenDeleting = false;
|
|
247
326
|
/**
|
|
248
327
|
* The user's group descendants.
|
|
249
328
|
*/
|
|
250
|
-
private $descendantGroups?: (
|
|
329
|
+
private $descendantGroups?: (GroupClass & GroupData)[];
|
|
251
330
|
/**
|
|
252
331
|
* Used to save the current email address to send verification if it changes
|
|
253
332
|
* from the frontend.
|
|
@@ -256,10 +335,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
256
335
|
* verification, don't set this.
|
|
257
336
|
*/
|
|
258
337
|
public $originalEmail?: string;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Used to save the current username for domain admin permissions.
|
|
340
|
+
*/
|
|
341
|
+
public $originalUsername?: string;
|
|
263
342
|
|
|
264
343
|
static async factoryUsername(username?: string): Promise<User & UserData> {
|
|
265
344
|
const entity = new this();
|
|
@@ -271,7 +350,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
271
350
|
{
|
|
272
351
|
type: '&',
|
|
273
352
|
ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
274
|
-
}
|
|
353
|
+
},
|
|
275
354
|
);
|
|
276
355
|
if (entity != null) {
|
|
277
356
|
return entity;
|
|
@@ -280,34 +359,18 @@ export default class User extends AbleObject<UserData> {
|
|
|
280
359
|
return entity;
|
|
281
360
|
}
|
|
282
361
|
|
|
283
|
-
static factorySync(guid?: string): User & UserData {
|
|
284
|
-
return super.factorySync(guid) as User & UserData;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
constructor(guid?: string) {
|
|
288
|
-
super(guid);
|
|
289
|
-
|
|
290
|
-
if (this.guid == null) {
|
|
291
|
-
// Defaults.
|
|
292
|
-
this.$data.enabled = true;
|
|
293
|
-
this.$data.abilities = [];
|
|
294
|
-
this.$data.groups = [];
|
|
295
|
-
this.$data.inheritAbilities = true;
|
|
296
|
-
this.$updateDataProtection();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
362
|
public static current(returnObjectIfNotExist: true): User & UserData;
|
|
301
363
|
public static current(
|
|
302
|
-
returnObjectIfNotExist?: false
|
|
364
|
+
returnObjectIfNotExist?: false,
|
|
303
365
|
): (User & UserData) | null;
|
|
304
366
|
public static current(
|
|
305
|
-
returnObjectIfNotExist?: boolean
|
|
367
|
+
returnObjectIfNotExist?: boolean,
|
|
306
368
|
): (User & UserData) | null {
|
|
307
|
-
|
|
369
|
+
const tilmeld = enforceTilmeld(this);
|
|
370
|
+
if (tilmeld.currentUser == null) {
|
|
308
371
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
309
372
|
}
|
|
310
|
-
return
|
|
373
|
+
return tilmeld.currentUser;
|
|
311
374
|
}
|
|
312
375
|
|
|
313
376
|
/**
|
|
@@ -320,7 +383,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
320
383
|
recoveryType: 'username' | 'password';
|
|
321
384
|
account: string;
|
|
322
385
|
}): Promise<{ result: boolean; message: string }> {
|
|
323
|
-
|
|
386
|
+
const tilmeld = enforceTilmeld(this);
|
|
387
|
+
if (!tilmeld.config.pwRecovery) {
|
|
324
388
|
return {
|
|
325
389
|
result: false,
|
|
326
390
|
message: 'Account recovery is not allowed.',
|
|
@@ -330,18 +394,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
330
394
|
let user: User & UserData;
|
|
331
395
|
const options: EmailOptions = {};
|
|
332
396
|
|
|
333
|
-
if (
|
|
334
|
-
!this.tilmeld.config.emailUsernames &&
|
|
335
|
-
data.recoveryType === 'username'
|
|
336
|
-
) {
|
|
397
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
337
398
|
// Create a username recovery email.
|
|
338
399
|
|
|
339
400
|
const getUser = await this.nymph.getEntity(
|
|
340
|
-
{ class:
|
|
401
|
+
{ class: tilmeld.User, skipAc: true },
|
|
341
402
|
{
|
|
342
403
|
type: '&',
|
|
343
404
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
344
|
-
}
|
|
405
|
+
},
|
|
345
406
|
);
|
|
346
407
|
|
|
347
408
|
if (getUser == null) {
|
|
@@ -364,7 +425,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
364
425
|
} else if (data.recoveryType === 'password') {
|
|
365
426
|
// Create a password recovery email.
|
|
366
427
|
|
|
367
|
-
const getUser = await
|
|
428
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
368
429
|
|
|
369
430
|
if (getUser.guid == null) {
|
|
370
431
|
return {
|
|
@@ -391,14 +452,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
391
452
|
};
|
|
392
453
|
options.locals = {
|
|
393
454
|
recoverCode: getUser.recoverSecret,
|
|
394
|
-
timeLimit:
|
|
455
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
395
456
|
};
|
|
396
457
|
} else {
|
|
397
458
|
return { result: false, message: 'Invalid recovery type.' };
|
|
398
459
|
}
|
|
399
460
|
|
|
461
|
+
this.nymph.config.debugLog(
|
|
462
|
+
'tilmeld',
|
|
463
|
+
`User account recovery (${options.template}) request for "${user.username}".`,
|
|
464
|
+
);
|
|
465
|
+
|
|
400
466
|
// Send the email.
|
|
401
|
-
if (await
|
|
467
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
402
468
|
return {
|
|
403
469
|
result: true,
|
|
404
470
|
message:
|
|
@@ -421,22 +487,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
421
487
|
secret: string;
|
|
422
488
|
password: string;
|
|
423
489
|
}): Promise<{ result: boolean; message: string }> {
|
|
424
|
-
|
|
490
|
+
const tilmeld = enforceTilmeld(this);
|
|
491
|
+
if (!tilmeld.config.pwRecovery) {
|
|
425
492
|
return {
|
|
426
493
|
result: false,
|
|
427
494
|
message: 'Account recovery is not allowed.',
|
|
428
495
|
};
|
|
429
496
|
}
|
|
430
497
|
|
|
431
|
-
const user = await
|
|
498
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
432
499
|
|
|
433
500
|
if (
|
|
434
501
|
user.guid == null ||
|
|
435
502
|
user.recoverSecret == null ||
|
|
436
503
|
data.secret !== user.recoverSecret ||
|
|
437
504
|
strtotime(
|
|
438
|
-
'+' +
|
|
439
|
-
Math.floor((user.recoverSecretDate ?? 0) / 1000)
|
|
505
|
+
'+' + tilmeld.config.pwRecoveryTimeLimit,
|
|
506
|
+
Math.floor((user.recoverSecretDate ?? 0) / 1000),
|
|
440
507
|
) *
|
|
441
508
|
1000 <
|
|
442
509
|
Date.now()
|
|
@@ -451,6 +518,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
451
518
|
return { result: false, message: 'Password cannot be empty.' };
|
|
452
519
|
}
|
|
453
520
|
|
|
521
|
+
this.nymph.config.debugLog(
|
|
522
|
+
'tilmeld',
|
|
523
|
+
`User account recovery for "${user.username}".`,
|
|
524
|
+
);
|
|
525
|
+
|
|
454
526
|
user.$password(data.password);
|
|
455
527
|
delete user.recoverSecret;
|
|
456
528
|
delete user.recoverSecretDate;
|
|
@@ -466,27 +538,31 @@ export default class User extends AbleObject<UserData> {
|
|
|
466
538
|
}
|
|
467
539
|
|
|
468
540
|
public static getClientConfig() {
|
|
541
|
+
const tilmeld = enforceTilmeld(this);
|
|
469
542
|
return {
|
|
470
|
-
regFields:
|
|
471
|
-
userFields:
|
|
472
|
-
emailUsernames:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
543
|
+
regFields: tilmeld.config.regFields,
|
|
544
|
+
userFields: tilmeld.config.userFields,
|
|
545
|
+
emailUsernames: tilmeld.config.emailUsernames,
|
|
546
|
+
domainSupport: tilmeld.config.domainSupport,
|
|
547
|
+
allowRegistration: tilmeld.config.allowRegistration,
|
|
548
|
+
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
549
|
+
pwRecovery: tilmeld.config.pwRecovery,
|
|
550
|
+
verifyEmail: tilmeld.config.verifyEmail,
|
|
551
|
+
unverifiedAccess: tilmeld.config.unverifiedAccess,
|
|
478
552
|
};
|
|
479
553
|
}
|
|
480
554
|
|
|
481
555
|
public static async loginUser(data: {
|
|
482
556
|
username: string;
|
|
483
557
|
password: string;
|
|
558
|
+
code?: string;
|
|
484
559
|
additionalData?: { [k: string]: any };
|
|
485
560
|
}) {
|
|
561
|
+
const tilmeld = enforceTilmeld(this);
|
|
486
562
|
if (!('username' in data) || !data.username.length) {
|
|
487
563
|
return { result: false, message: 'Incorrect login/password.' };
|
|
488
564
|
}
|
|
489
|
-
const user = await
|
|
565
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
490
566
|
const result: { result: boolean; message: string; user?: User & UserData } =
|
|
491
567
|
await user.$login(data);
|
|
492
568
|
if (result.result) {
|
|
@@ -496,23 +572,150 @@ export default class User extends AbleObject<UserData> {
|
|
|
496
572
|
return result;
|
|
497
573
|
}
|
|
498
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Get the users in a domain. This is only accessible to the domain's admins.
|
|
577
|
+
*/
|
|
578
|
+
public static async getDomainUsers(
|
|
579
|
+
domain: string,
|
|
580
|
+
options?: {
|
|
581
|
+
limit?: number;
|
|
582
|
+
offset?: number;
|
|
583
|
+
sort?: string;
|
|
584
|
+
reverse?: boolean;
|
|
585
|
+
},
|
|
586
|
+
) {
|
|
587
|
+
const tilmeld = enforceTilmeld(this);
|
|
588
|
+
|
|
589
|
+
if (!tilmeld.config.domainSupport) {
|
|
590
|
+
throw new AccessControlError('Domain support is not enabled.');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!domain) {
|
|
594
|
+
throw new BadDataError('Domain is required.');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
domain = domain.toLowerCase();
|
|
598
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
599
|
+
throw new BadDataError(tilmeld.config.validDomainRegexNotice);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
603
|
+
throw new AccessControlError(
|
|
604
|
+
'You do not have the authority to manage this domain.',
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return await this.nymph.getEntities(
|
|
609
|
+
{
|
|
610
|
+
class: this.nymph.getEntityClass(User),
|
|
611
|
+
...(options?.limit != null
|
|
612
|
+
? {
|
|
613
|
+
limit: options.limit,
|
|
614
|
+
}
|
|
615
|
+
: {}),
|
|
616
|
+
...(options?.offset != null
|
|
617
|
+
? {
|
|
618
|
+
offset: options.offset,
|
|
619
|
+
}
|
|
620
|
+
: {}),
|
|
621
|
+
...(options?.sort != null
|
|
622
|
+
? {
|
|
623
|
+
sort: options.sort,
|
|
624
|
+
}
|
|
625
|
+
: {}),
|
|
626
|
+
...(options?.reverse != null
|
|
627
|
+
? {
|
|
628
|
+
reverse: options.reverse,
|
|
629
|
+
}
|
|
630
|
+
: {}),
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
type: '&',
|
|
634
|
+
like: ['username', `%@${domain.replace(/([\\%_])/g, (s) => `\\${s}`)}`],
|
|
635
|
+
},
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
constructor() {
|
|
640
|
+
super();
|
|
641
|
+
|
|
642
|
+
// Defaults.
|
|
643
|
+
this.$data.enabled = true;
|
|
644
|
+
this.$data.abilities = [];
|
|
645
|
+
this.$data.groups = [];
|
|
646
|
+
this.$data.inheritAbilities = true;
|
|
647
|
+
this.$updateDataProtection();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
$setNymph(nymph: Nymph) {
|
|
651
|
+
this.$nymph = nymph;
|
|
652
|
+
if (!this.$asleep()) {
|
|
653
|
+
if (this.$data.group && this.$data.group.$nymph !== nymph) {
|
|
654
|
+
this.$data.group.$setNymph(nymph);
|
|
655
|
+
}
|
|
656
|
+
for (const group of this.$data.groups ?? []) {
|
|
657
|
+
if (group && group.$nymph !== nymph) {
|
|
658
|
+
group.$setNymph(nymph);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async $getUniques(): Promise<string[]> {
|
|
665
|
+
const tilmeld = enforceTilmeld(this);
|
|
666
|
+
const uniques = [`u:${this.$data.username}`];
|
|
667
|
+
if (
|
|
668
|
+
!tilmeld.config.emailUsernames &&
|
|
669
|
+
tilmeld.config.userFields.includes('email')
|
|
670
|
+
) {
|
|
671
|
+
uniques.push(`e:${this.$data.email}`);
|
|
672
|
+
|
|
673
|
+
if (this.$data.newEmailAddress != null) {
|
|
674
|
+
uniques.push(`e:${this.$data.newEmailAddress}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return uniques;
|
|
678
|
+
}
|
|
679
|
+
|
|
499
680
|
public async $login(data: {
|
|
500
681
|
username: string;
|
|
501
682
|
password: string;
|
|
683
|
+
code?: string;
|
|
502
684
|
additionalData?: { [k: string]: any };
|
|
503
685
|
}) {
|
|
504
|
-
const tilmeld = this
|
|
686
|
+
const tilmeld = enforceTilmeld(this);
|
|
505
687
|
if (this.guid == null) {
|
|
688
|
+
await tilmeld.config.failedLoginAttempt(null, data);
|
|
689
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
690
|
+
}
|
|
691
|
+
if (!this.$checkPassword(data.password)) {
|
|
692
|
+
await tilmeld.config.failedLoginAttempt(this, data);
|
|
506
693
|
return { result: false, message: 'Incorrect login/password.' };
|
|
507
694
|
}
|
|
508
695
|
if (!this.$data.enabled) {
|
|
509
|
-
return { result: false, message: '
|
|
696
|
+
return { result: false, message: 'Your account is disabled.' };
|
|
510
697
|
}
|
|
511
|
-
if (this.$gatekeeper()) {
|
|
698
|
+
if (await this.$gatekeeper()) {
|
|
512
699
|
return { result: true, message: 'You are already logged in.' };
|
|
513
700
|
}
|
|
514
|
-
|
|
515
|
-
|
|
701
|
+
|
|
702
|
+
if (this.$data.totpSecret != null) {
|
|
703
|
+
if (
|
|
704
|
+
data.code == null ||
|
|
705
|
+
typeof data.code !== 'string' ||
|
|
706
|
+
data.code.length !== 6
|
|
707
|
+
) {
|
|
708
|
+
return {
|
|
709
|
+
result: false,
|
|
710
|
+
needTOTP: true,
|
|
711
|
+
message: 'You need to provide a 2FA code.',
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (!this.$checkTOTPCode(data.code)) {
|
|
716
|
+
await tilmeld.config.failedLoginAttempt(this, data);
|
|
717
|
+
return { result: false, message: 'Incorrect 2FA code.' };
|
|
718
|
+
}
|
|
516
719
|
}
|
|
517
720
|
|
|
518
721
|
try {
|
|
@@ -529,8 +732,90 @@ export default class User extends AbleObject<UserData> {
|
|
|
529
732
|
};
|
|
530
733
|
}
|
|
531
734
|
|
|
735
|
+
this.$nymph.config.debugLog(
|
|
736
|
+
'tilmeld',
|
|
737
|
+
`User login for "${this.$data.username}".`,
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
// Authentication was successful, attempt to login.
|
|
741
|
+
if (!(await tilmeld.login(this, true))) {
|
|
742
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
for (let callback of (this.constructor as typeof User)
|
|
746
|
+
.afterLoginCallbacks) {
|
|
747
|
+
if (callback) {
|
|
748
|
+
await callback(this);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Login was successful.
|
|
753
|
+
return { result: true, message: 'You are logged in.' };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
|
|
757
|
+
const tilmeld = enforceTilmeld(this);
|
|
758
|
+
if (this.guid == null) {
|
|
759
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let user = tilmeld.User.current();
|
|
763
|
+
|
|
764
|
+
if (!user || user.guid == null) {
|
|
765
|
+
return { result: false, message: "You're not logged in." };
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
769
|
+
|
|
770
|
+
if (isCurrentUser) {
|
|
771
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const toAbilities = this.$data.abilities || [];
|
|
775
|
+
const fromAbilities = user.abilities || [];
|
|
776
|
+
|
|
777
|
+
if (
|
|
778
|
+
!fromAbilities.includes('tilmeld/switch') &&
|
|
779
|
+
!fromAbilities.includes('system/admin')
|
|
780
|
+
) {
|
|
781
|
+
return {
|
|
782
|
+
result: false,
|
|
783
|
+
message: "You don't have permission to switch users.",
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (
|
|
788
|
+
toAbilities.includes('system/admin') ||
|
|
789
|
+
toAbilities.includes('tilmeld/admin')
|
|
790
|
+
) {
|
|
791
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
for (let callback of (this.constructor as typeof User)
|
|
796
|
+
.beforeLoginCallbacks) {
|
|
797
|
+
if (callback) {
|
|
798
|
+
await callback(this, {
|
|
799
|
+
username: this.$data.username || '',
|
|
800
|
+
password: '',
|
|
801
|
+
...(data || {}),
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
} catch (e: any) {
|
|
806
|
+
return {
|
|
807
|
+
result: false,
|
|
808
|
+
message: e.message,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
this.$nymph.config.debugLog(
|
|
813
|
+
'tilmeld',
|
|
814
|
+
`User login switch for "${user.username}" to "${this.$data.username}".`,
|
|
815
|
+
);
|
|
816
|
+
|
|
532
817
|
// Authentication was successful, attempt to login.
|
|
533
|
-
if (!tilmeld.
|
|
818
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
534
819
|
return { result: false, message: 'Incorrect login/password.' };
|
|
535
820
|
}
|
|
536
821
|
|
|
@@ -550,7 +835,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
550
835
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
551
836
|
*/
|
|
552
837
|
public async $logout() {
|
|
553
|
-
const tilmeld = this
|
|
838
|
+
const tilmeld = enforceTilmeld(this);
|
|
554
839
|
|
|
555
840
|
try {
|
|
556
841
|
for (let callback of (this.constructor as typeof User)
|
|
@@ -566,7 +851,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
566
851
|
};
|
|
567
852
|
}
|
|
568
853
|
|
|
569
|
-
|
|
854
|
+
this.$nymph.config.debugLog(
|
|
855
|
+
'tilmeld',
|
|
856
|
+
`User logout for "${this.$data.username}".`,
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
await tilmeld.logout();
|
|
570
860
|
|
|
571
861
|
for (let callback of (this.constructor as typeof User)
|
|
572
862
|
.afterLogoutCallbacks) {
|
|
@@ -587,89 +877,176 @@ export default class User extends AbleObject<UserData> {
|
|
|
587
877
|
}
|
|
588
878
|
return (
|
|
589
879
|
'https://secure.gravatar.com/avatar/' +
|
|
590
|
-
|
|
880
|
+
crypto
|
|
881
|
+
.createHash('sha256')
|
|
882
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
883
|
+
.digest('hex')
|
|
884
|
+
.toLowerCase() +
|
|
591
885
|
'?d=identicon&s=40'
|
|
592
886
|
);
|
|
593
887
|
}
|
|
594
888
|
|
|
889
|
+
public $getGid() {
|
|
890
|
+
if (this.$data.group) {
|
|
891
|
+
return this.$data.group.guid;
|
|
892
|
+
}
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
public $getGids() {
|
|
897
|
+
if (this.$data.groups) {
|
|
898
|
+
return this.$data.groups
|
|
899
|
+
.map((group) => group.guid)
|
|
900
|
+
.filter((guid) => guid != null) as string[];
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
|
|
595
905
|
/**
|
|
596
906
|
* Get the user's group descendants.
|
|
597
907
|
*/
|
|
598
|
-
public async $getDescendantGroups(): Promise<(
|
|
908
|
+
public async $getDescendantGroups(): Promise<(GroupClass & GroupData)[]> {
|
|
599
909
|
if (this.$descendantGroups == null) {
|
|
600
910
|
this.$descendantGroups = [];
|
|
601
911
|
if (this.$data.group != null) {
|
|
912
|
+
await this.$data.group.$wake();
|
|
602
913
|
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
603
914
|
}
|
|
915
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
604
916
|
for (let curGroup of this.$data.groups ?? []) {
|
|
605
917
|
this.$descendantGroups = this.$descendantGroups?.concat(
|
|
606
|
-
await curGroup.$getDescendants()
|
|
918
|
+
await curGroup.$getDescendants(),
|
|
607
919
|
);
|
|
608
920
|
}
|
|
609
921
|
}
|
|
610
922
|
return this.$descendantGroups;
|
|
611
923
|
}
|
|
612
924
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (
|
|
618
|
-
this.$
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
925
|
+
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
926
|
+
const tilmeld = enforceTilmeld(this);
|
|
927
|
+
this.$check();
|
|
928
|
+
|
|
929
|
+
if ('abilities' in input.data) {
|
|
930
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// If the user is a new domain user, the data protection needs to be updated
|
|
934
|
+
// before the data is accepted, or else some things won't be accepted
|
|
935
|
+
// correctly (enabled, passwordTemp).
|
|
936
|
+
let reloadDataProtection = false;
|
|
937
|
+
if (tilmeld.config.domainSupport) {
|
|
938
|
+
let [_username, domain] = splitn(input.data.username ?? '', '@', 2);
|
|
939
|
+
// Lowercase the domain part.
|
|
940
|
+
if (domain) {
|
|
941
|
+
domain = domain.toLowerCase();
|
|
626
942
|
}
|
|
943
|
+
const user = tilmeld.User.current();
|
|
944
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
945
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
946
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
947
|
+
const abilities =
|
|
948
|
+
(isCurrentUser || user == null
|
|
949
|
+
? this.$data.abilities
|
|
950
|
+
: user?.abilities) ?? [];
|
|
951
|
+
const isNewUser = input.guid == null;
|
|
952
|
+
const isDomainUser =
|
|
953
|
+
!isCurrentUser &&
|
|
954
|
+
user != null &&
|
|
955
|
+
domain &&
|
|
956
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
957
|
+
|
|
958
|
+
reloadDataProtection = !!(isNewUser && isDomainUser);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
962
|
+
|
|
963
|
+
if (reloadDataProtection) {
|
|
964
|
+
// Load it again after data protection is run.
|
|
965
|
+
this.$updateDataProtection();
|
|
966
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
627
967
|
}
|
|
628
|
-
return this.$descendantGroups;
|
|
629
968
|
}
|
|
630
969
|
|
|
631
|
-
public $
|
|
632
|
-
|
|
633
|
-
|
|
970
|
+
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
971
|
+
this.$check();
|
|
972
|
+
|
|
973
|
+
if ('abilities' in patch.set) {
|
|
974
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
super.$jsonAcceptPatch(patch, allowConflict);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private $checkIncomingAbilities(incoming: any) {
|
|
981
|
+
const tilmeld = enforceTilmeld(this);
|
|
634
982
|
|
|
635
983
|
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
984
|
+
!Array.isArray(incoming) ||
|
|
985
|
+
incoming.find((ability) => typeof ability !== 'string')
|
|
986
|
+
) {
|
|
987
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
if (
|
|
991
|
+
incoming.includes('system/admin') !==
|
|
992
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
993
|
+
(!tilmeld.currentUser ||
|
|
994
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
641
995
|
) {
|
|
642
996
|
throw new BadDataError(
|
|
643
|
-
"You don't have the authority to
|
|
997
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
644
998
|
);
|
|
645
999
|
}
|
|
646
1000
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1001
|
+
if (
|
|
1002
|
+
incoming.includes('tilmeld/admin') !==
|
|
1003
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
1004
|
+
(!tilmeld.currentUser ||
|
|
1005
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
1006
|
+
) {
|
|
1007
|
+
throw new BadDataError(
|
|
1008
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
654
1011
|
|
|
655
1012
|
if (
|
|
656
|
-
'
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
!tilmeld.gatekeeper('system/admin')
|
|
1013
|
+
incoming.includes('tilmeld/switch') !==
|
|
1014
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
1015
|
+
(!tilmeld.currentUser ||
|
|
1016
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
661
1017
|
) {
|
|
662
1018
|
throw new BadDataError(
|
|
663
|
-
"You don't have the authority to
|
|
1019
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
664
1020
|
);
|
|
665
1021
|
}
|
|
666
1022
|
|
|
667
|
-
|
|
668
|
-
|
|
1023
|
+
if (
|
|
1024
|
+
xor(
|
|
1025
|
+
incoming.filter((ability: string) => ability.startsWith('tilmeld/')),
|
|
1026
|
+
this.$data.abilities?.filter((ability: string) =>
|
|
1027
|
+
ability.startsWith('tilmeld/'),
|
|
1028
|
+
) ?? [],
|
|
1029
|
+
).length &&
|
|
1030
|
+
(!tilmeld.currentUser ||
|
|
1031
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
1032
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))
|
|
1033
|
+
) {
|
|
1034
|
+
throw new BadDataError(
|
|
1035
|
+
"You don't have the authority to grant or revoke Tilmeld abilities.",
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
669
1038
|
}
|
|
670
1039
|
|
|
671
|
-
public $putData(
|
|
672
|
-
|
|
1040
|
+
public $putData(
|
|
1041
|
+
data: EntityData,
|
|
1042
|
+
sdata?: SerializedEntityData,
|
|
1043
|
+
source?: 'server',
|
|
1044
|
+
) {
|
|
1045
|
+
super.$putData(data, sdata, source);
|
|
1046
|
+
if (source === 'server') {
|
|
1047
|
+
this.$originalEmail = this.$data.email;
|
|
1048
|
+
this.$originalUsername = this.$data.username;
|
|
1049
|
+
}
|
|
673
1050
|
this.$updateDataProtection();
|
|
674
1051
|
}
|
|
675
1052
|
|
|
@@ -679,8 +1056,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
679
1056
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
680
1057
|
*/
|
|
681
1058
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
682
|
-
const tilmeld = this
|
|
1059
|
+
const tilmeld = enforceTilmeld(this);
|
|
683
1060
|
let user = givenUser ?? tilmeld.User.current();
|
|
1061
|
+
let [_username, domain] = tilmeld.config.domainSupport
|
|
1062
|
+
? splitn(this.$originalUsername ?? this.$data.username ?? '', '@', 2)
|
|
1063
|
+
: [this.$originalUsername ?? this.$data.username, null];
|
|
1064
|
+
// Lowercase the domain part.
|
|
1065
|
+
if (domain) {
|
|
1066
|
+
domain = domain.toLowerCase();
|
|
1067
|
+
}
|
|
684
1068
|
|
|
685
1069
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
686
1070
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -698,53 +1082,165 @@ export default class User extends AbleObject<UserData> {
|
|
|
698
1082
|
? this.$data.abilities
|
|
699
1083
|
: user?.abilities) ?? [];
|
|
700
1084
|
const isNewUser = this.guid == null;
|
|
1085
|
+
const isDomainUser =
|
|
1086
|
+
tilmeld.config.domainSupport &&
|
|
1087
|
+
!isCurrentUser &&
|
|
1088
|
+
user != null &&
|
|
1089
|
+
domain &&
|
|
1090
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
701
1091
|
|
|
702
1092
|
if (isCurrentUser) {
|
|
703
1093
|
// Users can check to see what abilities they have.
|
|
704
1094
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
705
1095
|
this.$clientEnabledMethods.push('$changePassword');
|
|
1096
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
706
1097
|
this.$clientEnabledMethods.push('$logout');
|
|
707
1098
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
1099
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
1100
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
1101
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
1102
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (isDomainUser) {
|
|
1106
|
+
// Domain admins can check to see what abilities their users have.
|
|
1107
|
+
this.$clientEnabledMethods.push('$gatekeeper');
|
|
1108
|
+
this.$clientEnabledMethods.push('$changePassword');
|
|
1109
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
1110
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
1111
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
1112
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
1113
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
708
1114
|
}
|
|
709
1115
|
|
|
710
1116
|
if (
|
|
711
1117
|
user != null &&
|
|
712
|
-
(abilities.
|
|
713
|
-
abilities.
|
|
1118
|
+
(abilities.includes('tilmeld/admin') ||
|
|
1119
|
+
abilities.includes('system/admin'))
|
|
714
1120
|
) {
|
|
715
1121
|
// Users who can edit other users can see most of their data.
|
|
716
|
-
this.$privateData = ['password', 'salt'];
|
|
1122
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
1123
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
1124
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
717
1125
|
this.$allowlistData = undefined;
|
|
718
|
-
} else if (
|
|
719
|
-
//
|
|
1126
|
+
} else if (isDomainUser) {
|
|
1127
|
+
// Domain admins can see their users' data, and edit some of it.
|
|
720
1128
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
721
1129
|
this.$allowlistData.push('username');
|
|
722
1130
|
}
|
|
723
1131
|
this.$allowlistData.push('avatar');
|
|
724
|
-
if (tilmeld.config.userFields.
|
|
1132
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
725
1133
|
this.$allowlistData.push('nameFirst');
|
|
726
1134
|
this.$allowlistData.push('nameMiddle');
|
|
727
1135
|
this.$allowlistData.push('nameLast');
|
|
728
1136
|
this.$allowlistData.push('name');
|
|
729
1137
|
}
|
|
730
|
-
if (tilmeld.config.userFields.
|
|
1138
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
731
1139
|
this.$allowlistData.push('email');
|
|
732
1140
|
}
|
|
733
|
-
if (tilmeld.config.userFields.
|
|
1141
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
734
1142
|
this.$allowlistData.push('phone');
|
|
735
1143
|
}
|
|
1144
|
+
this.$allowlistData.push('enabled');
|
|
1145
|
+
this.$allowlistData.push('passwordTemp');
|
|
736
1146
|
this.$privateData = [
|
|
1147
|
+
// The things commented are there to show what is _not_ private.
|
|
1148
|
+
// 'abilities',
|
|
1149
|
+
// 'username',
|
|
1150
|
+
// 'nameFirst',
|
|
1151
|
+
// 'nameMiddle',
|
|
1152
|
+
// 'nameLast',
|
|
1153
|
+
// 'name',
|
|
1154
|
+
// 'email',
|
|
1155
|
+
// 'avatar',
|
|
1156
|
+
// 'phone',
|
|
1157
|
+
// 'group',
|
|
1158
|
+
// 'groups',
|
|
1159
|
+
// 'inheritAbilities',
|
|
1160
|
+
// 'enabled',
|
|
737
1161
|
'secret',
|
|
1162
|
+
'emailChangeDate',
|
|
738
1163
|
'newEmailSecret',
|
|
739
|
-
'
|
|
1164
|
+
// 'newEmailAddress',
|
|
740
1165
|
'cancelEmailSecret',
|
|
741
|
-
'
|
|
1166
|
+
'cancelEmailAddress',
|
|
742
1167
|
'recoverSecret',
|
|
743
1168
|
'recoverSecretDate',
|
|
1169
|
+
'salt',
|
|
744
1170
|
'password',
|
|
1171
|
+
// 'passwordTemp',
|
|
1172
|
+
// 'revokeTokenDate',
|
|
1173
|
+
'totpSecret',
|
|
1174
|
+
];
|
|
1175
|
+
} else if (isCurrentUser || isNewUser) {
|
|
1176
|
+
// Users can see their own data, and edit some of it.
|
|
1177
|
+
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
1178
|
+
this.$allowlistData.push('username');
|
|
1179
|
+
}
|
|
1180
|
+
this.$allowlistData.push('avatar');
|
|
1181
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
1182
|
+
this.$allowlistData.push('nameFirst');
|
|
1183
|
+
this.$allowlistData.push('nameMiddle');
|
|
1184
|
+
this.$allowlistData.push('nameLast');
|
|
1185
|
+
this.$allowlistData.push('name');
|
|
1186
|
+
}
|
|
1187
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
1188
|
+
this.$allowlistData.push('email');
|
|
1189
|
+
}
|
|
1190
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
1191
|
+
this.$allowlistData.push('phone');
|
|
1192
|
+
}
|
|
1193
|
+
this.$privateData = [
|
|
1194
|
+
// The things commented are there to show what is _not_ private.
|
|
1195
|
+
// 'abilities',
|
|
1196
|
+
// 'username',
|
|
1197
|
+
// 'nameFirst',
|
|
1198
|
+
// 'nameMiddle',
|
|
1199
|
+
// 'nameLast',
|
|
1200
|
+
// 'name',
|
|
1201
|
+
// 'email',
|
|
1202
|
+
// 'avatar',
|
|
1203
|
+
// 'phone',
|
|
1204
|
+
// 'group',
|
|
1205
|
+
// 'groups',
|
|
1206
|
+
// 'inheritAbilities',
|
|
1207
|
+
'enabled',
|
|
1208
|
+
'secret',
|
|
1209
|
+
'emailChangeDate',
|
|
1210
|
+
'newEmailSecret',
|
|
1211
|
+
// 'newEmailAddress',
|
|
1212
|
+
'cancelEmailSecret',
|
|
1213
|
+
'cancelEmailAddress',
|
|
1214
|
+
'recoverSecret',
|
|
1215
|
+
'recoverSecretDate',
|
|
745
1216
|
'salt',
|
|
1217
|
+
'password',
|
|
1218
|
+
'passwordTemp',
|
|
1219
|
+
// 'revokeTokenDate',
|
|
1220
|
+
'totpSecret',
|
|
746
1221
|
];
|
|
747
1222
|
}
|
|
1223
|
+
|
|
1224
|
+
if (
|
|
1225
|
+
user != null &&
|
|
1226
|
+
!isCurrentUser &&
|
|
1227
|
+
(abilities.includes('tilmeld/switch') ||
|
|
1228
|
+
abilities.includes('system/admin'))
|
|
1229
|
+
) {
|
|
1230
|
+
// Users with this ability can switch to other users.
|
|
1231
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
public $updateGroupDataProtection() {
|
|
1236
|
+
if (this.$data.group != null && !this.$data.group.$asleep()) {
|
|
1237
|
+
this.$data.group.$updateDataProtection();
|
|
1238
|
+
}
|
|
1239
|
+
for (let group of this.$data.groups ?? []) {
|
|
1240
|
+
if (!group.$asleep()) {
|
|
1241
|
+
group.$updateDataProtection();
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
748
1244
|
}
|
|
749
1245
|
|
|
750
1246
|
/**
|
|
@@ -761,35 +1257,43 @@ export default class User extends AbleObject<UserData> {
|
|
|
761
1257
|
* @param ability The ability.
|
|
762
1258
|
* @returns True or false.
|
|
763
1259
|
*/
|
|
764
|
-
public $gatekeeper(ability?: string): boolean {
|
|
765
|
-
|
|
1260
|
+
public async $gatekeeper(ability?: string): Promise<boolean> {
|
|
1261
|
+
await this.$wake();
|
|
1262
|
+
const tilmeld = enforceTilmeld(this);
|
|
766
1263
|
if (ability == null) {
|
|
767
1264
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
768
1265
|
}
|
|
769
1266
|
// Check the cache to see if we've already checked this user.
|
|
770
1267
|
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
|
-
);
|
|
1268
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
789
1269
|
}
|
|
790
1270
|
return (
|
|
791
1271
|
(ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
792
|
-
!!this.$
|
|
1272
|
+
!!this.$data.abilities?.includes('system/admin')
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Build a gatekeeper cache object.
|
|
1278
|
+
*/
|
|
1279
|
+
public async $getGatekeeperCache() {
|
|
1280
|
+
await this.$wake();
|
|
1281
|
+
let abilities = this.$data.abilities ?? [];
|
|
1282
|
+
if (this.$data.inheritAbilities) {
|
|
1283
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1284
|
+
for (let curGroup of this.$data.groups ?? []) {
|
|
1285
|
+
if (curGroup.enabled) {
|
|
1286
|
+
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
await this.$data.group?.$wake();
|
|
1290
|
+
const group = this.$data.group;
|
|
1291
|
+
if (group != null && group.cdate != null && group.enabled) {
|
|
1292
|
+
abilities = abilities.concat(group.abilities ?? []);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return Object.fromEntries(
|
|
1296
|
+
abilities.map((ability) => [ability, true as true]),
|
|
793
1297
|
);
|
|
794
1298
|
}
|
|
795
1299
|
|
|
@@ -806,7 +1310,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
806
1310
|
* @returns True on success, false on failure.
|
|
807
1311
|
*/
|
|
808
1312
|
public async $sendEmailVerification() {
|
|
809
|
-
const tilmeld = this
|
|
1313
|
+
const tilmeld = enforceTilmeld(this);
|
|
810
1314
|
if (this.guid == null) {
|
|
811
1315
|
return false;
|
|
812
1316
|
}
|
|
@@ -818,7 +1322,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
818
1322
|
|
|
819
1323
|
if (this.$data.secret != null) {
|
|
820
1324
|
const link = `${verifyUrl}?action=verify&id=${encodeURIComponent(
|
|
821
|
-
this.guid
|
|
1325
|
+
this.guid,
|
|
822
1326
|
)}&secret=${encodeURIComponent(this.$data.secret)}`;
|
|
823
1327
|
success =
|
|
824
1328
|
success &&
|
|
@@ -836,13 +1340,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
836
1340
|
verifyLink: link,
|
|
837
1341
|
},
|
|
838
1342
|
},
|
|
839
|
-
this
|
|
1343
|
+
this,
|
|
840
1344
|
));
|
|
841
1345
|
}
|
|
842
1346
|
|
|
843
1347
|
if (this.$data.newEmailSecret != null) {
|
|
844
1348
|
const link = `${verifyUrl}?action=verifychange&id=${encodeURIComponent(
|
|
845
|
-
this.guid
|
|
1349
|
+
this.guid,
|
|
846
1350
|
)}&secret=${encodeURIComponent(this.$data.newEmailSecret)}`;
|
|
847
1351
|
success =
|
|
848
1352
|
success &&
|
|
@@ -862,13 +1366,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
862
1366
|
newEmail: this.$data.newEmailAddress,
|
|
863
1367
|
},
|
|
864
1368
|
},
|
|
865
|
-
this
|
|
1369
|
+
this,
|
|
866
1370
|
));
|
|
867
1371
|
}
|
|
868
1372
|
|
|
869
1373
|
if (this.$data.cancelEmailSecret != null) {
|
|
870
1374
|
const link = `${verifyUrl}?action=cancelchange&id=${encodeURIComponent(
|
|
871
|
-
this.guid
|
|
1375
|
+
this.guid,
|
|
872
1376
|
)}&secret=${encodeURIComponent(this.$data.cancelEmailSecret)}`;
|
|
873
1377
|
success =
|
|
874
1378
|
success &&
|
|
@@ -888,7 +1392,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
888
1392
|
newEmail: this.$data.newEmailAddress ?? this.$data.email,
|
|
889
1393
|
},
|
|
890
1394
|
},
|
|
891
|
-
this
|
|
1395
|
+
this,
|
|
892
1396
|
));
|
|
893
1397
|
}
|
|
894
1398
|
|
|
@@ -901,13 +1405,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
901
1405
|
* @param group The group.
|
|
902
1406
|
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
903
1407
|
*/
|
|
904
|
-
public $addGroup(group:
|
|
905
|
-
|
|
906
|
-
|
|
1408
|
+
public async $addGroup(group: GroupClass & GroupData) {
|
|
1409
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1410
|
+
let groups = this.$data.groups;
|
|
1411
|
+
if (groups == null) {
|
|
1412
|
+
groups = [];
|
|
907
1413
|
}
|
|
908
|
-
if (!group.$inArray(
|
|
909
|
-
|
|
910
|
-
|
|
1414
|
+
if (!group.$inArray(groups)) {
|
|
1415
|
+
groups.push(group);
|
|
1416
|
+
this.$data.groups = groups;
|
|
1417
|
+
return groups;
|
|
911
1418
|
}
|
|
912
1419
|
return true;
|
|
913
1420
|
}
|
|
@@ -919,17 +1426,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
919
1426
|
* @returns True if the passwords match, otherwise false.
|
|
920
1427
|
*/
|
|
921
1428
|
public $checkPassword(password: string) {
|
|
922
|
-
const tilmeld = this
|
|
1429
|
+
const tilmeld = enforceTilmeld(this);
|
|
923
1430
|
switch (tilmeld.config.pwMethod) {
|
|
924
1431
|
case 'plain':
|
|
925
1432
|
return this.$data.password === password;
|
|
926
1433
|
case 'digest':
|
|
927
|
-
return
|
|
1434
|
+
return (
|
|
1435
|
+
this.$data.password ===
|
|
1436
|
+
crypto.createHash('sha256').update(password).digest('base64')
|
|
1437
|
+
);
|
|
928
1438
|
case 'salt':
|
|
929
1439
|
default:
|
|
930
1440
|
return (
|
|
931
1441
|
this.$data.password ===
|
|
932
|
-
|
|
1442
|
+
crypto
|
|
1443
|
+
.createHash('sha256')
|
|
1444
|
+
.update(password + this.$data.salt)
|
|
1445
|
+
.digest('base64')
|
|
933
1446
|
);
|
|
934
1447
|
}
|
|
935
1448
|
}
|
|
@@ -940,10 +1453,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
940
1453
|
* @param group The group.
|
|
941
1454
|
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
942
1455
|
*/
|
|
943
|
-
public $delGroup(group:
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1456
|
+
public async $delGroup(group: GroupClass & GroupData) {
|
|
1457
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1458
|
+
let groups = this.$data.groups || [];
|
|
1459
|
+
if (this.$data.groups != null && group.$inArray(groups)) {
|
|
1460
|
+
const newGroups: (GroupClass & GroupData)[] = [];
|
|
1461
|
+
for (let curGroup of groups) {
|
|
947
1462
|
if (!group.$is(curGroup)) {
|
|
948
1463
|
newGroups.push(curGroup);
|
|
949
1464
|
}
|
|
@@ -960,7 +1475,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
960
1475
|
* @param group The group, or the group's GUID.
|
|
961
1476
|
* @returns True or false.
|
|
962
1477
|
*/
|
|
963
|
-
public $inGroup(group: (
|
|
1478
|
+
public async $inGroup(group: (GroupClass & GroupData) | string) {
|
|
964
1479
|
if (typeof group === 'string') {
|
|
965
1480
|
if (this.$data.group?.guid === group) {
|
|
966
1481
|
return true;
|
|
@@ -983,16 +1498,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
983
1498
|
* @param group The group, or the group's GUID.
|
|
984
1499
|
* @returns True or false.
|
|
985
1500
|
*/
|
|
986
|
-
public $isDescendant(group: (
|
|
1501
|
+
public async $isDescendant(group: (GroupClass & GroupData) | string) {
|
|
987
1502
|
// Check to see if the user is in a descendant group of the given group.
|
|
1503
|
+
await this.$data.group?.$wake();
|
|
988
1504
|
if (
|
|
1505
|
+
this.$data.group != null &&
|
|
989
1506
|
this.$data.group?.cdate != null &&
|
|
990
|
-
this.$data.group
|
|
1507
|
+
(await this.$data.group?.$isDescendant(group))
|
|
991
1508
|
) {
|
|
992
1509
|
return true;
|
|
993
1510
|
}
|
|
1511
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
994
1512
|
for (let curGroup of this.$data.groups ?? []) {
|
|
995
|
-
if (curGroup.$isDescendant(group)) {
|
|
1513
|
+
if (await curGroup.$isDescendant(group)) {
|
|
996
1514
|
return true;
|
|
997
1515
|
}
|
|
998
1516
|
}
|
|
@@ -1008,15 +1526,31 @@ export default class User extends AbleObject<UserData> {
|
|
|
1008
1526
|
public async $changePassword(data: {
|
|
1009
1527
|
newPassword: string;
|
|
1010
1528
|
currentPassword: string;
|
|
1529
|
+
revokeCurrentTokens?: boolean;
|
|
1011
1530
|
}): Promise<{ result: boolean; message: string }> {
|
|
1531
|
+
this.$nymph.config.debugLog(
|
|
1532
|
+
'tilmeld',
|
|
1533
|
+
`Password change request for "${this.$data.username}".`,
|
|
1534
|
+
);
|
|
1012
1535
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1013
1536
|
return { result: false, message: 'Please specify a password.' };
|
|
1014
1537
|
}
|
|
1015
1538
|
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
1539
|
+
this.$nymph.config.debugLog(
|
|
1540
|
+
'tilmeld',
|
|
1541
|
+
`Incorrect password for change request for "${this.$data.username}".`,
|
|
1542
|
+
);
|
|
1016
1543
|
return { result: false, message: 'Incorrect password.' };
|
|
1017
1544
|
}
|
|
1018
1545
|
this.$data.passwordTemp = data.newPassword;
|
|
1546
|
+
if (data.revokeCurrentTokens) {
|
|
1547
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1548
|
+
}
|
|
1019
1549
|
if (await this.$save()) {
|
|
1550
|
+
if (data.revokeCurrentTokens) {
|
|
1551
|
+
const tilmeld = enforceTilmeld(this);
|
|
1552
|
+
await tilmeld.login(this, true);
|
|
1553
|
+
}
|
|
1020
1554
|
return { result: true, message: 'Your password has been changed.' };
|
|
1021
1555
|
} else {
|
|
1022
1556
|
return { result: false, message: "Couldn't save new password." };
|
|
@@ -1030,7 +1564,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1030
1564
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1031
1565
|
*/
|
|
1032
1566
|
public $password(password: string) {
|
|
1033
|
-
const tilmeld = this
|
|
1567
|
+
const tilmeld = enforceTilmeld(this);
|
|
1034
1568
|
switch (tilmeld.config.pwMethod) {
|
|
1035
1569
|
case 'plain':
|
|
1036
1570
|
delete this.$data.salt;
|
|
@@ -1038,34 +1572,275 @@ export default class User extends AbleObject<UserData> {
|
|
|
1038
1572
|
break;
|
|
1039
1573
|
case 'digest':
|
|
1040
1574
|
delete this.$data.salt;
|
|
1041
|
-
this.$data.password =
|
|
1575
|
+
this.$data.password = crypto
|
|
1576
|
+
.createHash('sha256')
|
|
1577
|
+
.update(password)
|
|
1578
|
+
.digest('base64');
|
|
1042
1579
|
break;
|
|
1043
1580
|
case 'salt':
|
|
1044
1581
|
default:
|
|
1045
1582
|
this.$data.salt = nanoid();
|
|
1046
|
-
this.$data.password =
|
|
1047
|
-
|
|
1048
|
-
|
|
1583
|
+
this.$data.password = crypto
|
|
1584
|
+
.createHash('sha256')
|
|
1585
|
+
.update(password + this.$data.salt)
|
|
1586
|
+
.digest('base64');
|
|
1049
1587
|
break;
|
|
1050
1588
|
}
|
|
1051
1589
|
return this.$data.password;
|
|
1052
1590
|
}
|
|
1053
1591
|
|
|
1592
|
+
/**
|
|
1593
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
1594
|
+
*
|
|
1595
|
+
* @param data The input data from the client.
|
|
1596
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1597
|
+
*/
|
|
1598
|
+
public async $revokeCurrentTokens(data: {
|
|
1599
|
+
password: string;
|
|
1600
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1601
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1602
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1606
|
+
|
|
1607
|
+
if (await this.$save()) {
|
|
1608
|
+
const tilmeld = enforceTilmeld(this);
|
|
1609
|
+
await tilmeld.login(this, true);
|
|
1610
|
+
return {
|
|
1611
|
+
result: true,
|
|
1612
|
+
message: 'You have logged out of all other sessions.',
|
|
1613
|
+
};
|
|
1614
|
+
} else {
|
|
1615
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
public async $hasTOTPSecret() {
|
|
1620
|
+
return this.$data.totpSecret != null;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Check the given code against the user's TOTP secret.
|
|
1625
|
+
*
|
|
1626
|
+
* @param code The code in question.
|
|
1627
|
+
* @returns True if the code is valid, otherwise false.
|
|
1628
|
+
*/
|
|
1629
|
+
public $checkTOTPCode(code: string) {
|
|
1630
|
+
if (this.$data.totpSecret == null) {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const tilmeld = enforceTilmeld(this);
|
|
1635
|
+
|
|
1636
|
+
const totp = new TOTP({
|
|
1637
|
+
issuer: tilmeld.config.appName,
|
|
1638
|
+
label: this.$data.username,
|
|
1639
|
+
algorithm: 'SHA1',
|
|
1640
|
+
digits: 6,
|
|
1641
|
+
period: 30,
|
|
1642
|
+
secret: this.$data.totpSecret,
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1650
|
+
*
|
|
1651
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1652
|
+
*/
|
|
1653
|
+
public async $getNewTOTPSecret() {
|
|
1654
|
+
const tilmeld = enforceTilmeld(this);
|
|
1655
|
+
|
|
1656
|
+
if (this.$data.totpSecret != null) {
|
|
1657
|
+
throw new BadDataError('You already have a 2FA secret.');
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
const secret = new Secret();
|
|
1661
|
+
|
|
1662
|
+
const totp = new TOTP({
|
|
1663
|
+
issuer: tilmeld.config.appName,
|
|
1664
|
+
label: this.$data.username,
|
|
1665
|
+
algorithm: 'SHA1',
|
|
1666
|
+
digits: 6,
|
|
1667
|
+
period: 30,
|
|
1668
|
+
secret,
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
const uri = totp.toString();
|
|
1672
|
+
|
|
1673
|
+
const qrcode = await toDataURL(uri);
|
|
1674
|
+
|
|
1675
|
+
return {
|
|
1676
|
+
uri,
|
|
1677
|
+
qrcode,
|
|
1678
|
+
secret: secret.base32,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1684
|
+
*
|
|
1685
|
+
* @param data The input data from the client.
|
|
1686
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1687
|
+
*/
|
|
1688
|
+
public async $saveTOTPSecret(data: {
|
|
1689
|
+
password: string;
|
|
1690
|
+
secret: string;
|
|
1691
|
+
code: string;
|
|
1692
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1693
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1694
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (this.$data.totpSecret != null) {
|
|
1698
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
if (
|
|
1702
|
+
data.secret == null ||
|
|
1703
|
+
typeof data.secret !== 'string' ||
|
|
1704
|
+
data.secret === ''
|
|
1705
|
+
) {
|
|
1706
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (
|
|
1710
|
+
data.code == null ||
|
|
1711
|
+
typeof data.code !== 'string' ||
|
|
1712
|
+
data.code.length !== 6
|
|
1713
|
+
) {
|
|
1714
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
const tilmeld = enforceTilmeld(this);
|
|
1718
|
+
|
|
1719
|
+
const totp = new TOTP({
|
|
1720
|
+
issuer: tilmeld.config.appName,
|
|
1721
|
+
label: this.$data.username,
|
|
1722
|
+
algorithm: 'SHA1',
|
|
1723
|
+
digits: 6,
|
|
1724
|
+
period: 30,
|
|
1725
|
+
secret: data.secret,
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1729
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
this.$data.totpSecret = data.secret;
|
|
1733
|
+
|
|
1734
|
+
if (await this.$save()) {
|
|
1735
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1736
|
+
} else {
|
|
1737
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1743
|
+
* account.
|
|
1744
|
+
*
|
|
1745
|
+
* @param data The input data from the client.
|
|
1746
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1747
|
+
*/
|
|
1748
|
+
public async $removeTOTPSecret(data?: {
|
|
1749
|
+
password: string;
|
|
1750
|
+
code: string;
|
|
1751
|
+
}): Promise<{ result: boolean; message: string }> {
|
|
1752
|
+
const tilmeld = enforceTilmeld(this);
|
|
1753
|
+
|
|
1754
|
+
if (data) {
|
|
1755
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1756
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
if (
|
|
1760
|
+
data.code == null ||
|
|
1761
|
+
typeof data.code !== 'string' ||
|
|
1762
|
+
data.code.length !== 6
|
|
1763
|
+
) {
|
|
1764
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
if (this.$data.totpSecret == null) {
|
|
1768
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const totp = new TOTP({
|
|
1772
|
+
issuer: tilmeld.config.appName,
|
|
1773
|
+
label: this.$data.username,
|
|
1774
|
+
algorithm: 'SHA1',
|
|
1775
|
+
digits: 6,
|
|
1776
|
+
period: 30,
|
|
1777
|
+
secret: this.$data.totpSecret,
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1781
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1782
|
+
}
|
|
1783
|
+
} else if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1784
|
+
return {
|
|
1785
|
+
result: false,
|
|
1786
|
+
message: 'You must provide your password and 2FA code.',
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
delete this.$data.totpSecret;
|
|
1791
|
+
|
|
1792
|
+
if (await this.$save()) {
|
|
1793
|
+
return {
|
|
1794
|
+
result: true,
|
|
1795
|
+
message: 'Two factor secret has been removed.',
|
|
1796
|
+
};
|
|
1797
|
+
} else {
|
|
1798
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1054
1802
|
/**
|
|
1055
1803
|
* Check that a username is valid.
|
|
1056
1804
|
*
|
|
1057
1805
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1058
1806
|
*/
|
|
1059
1807
|
public async $checkUsername(): Promise<{ result: boolean; message: string }> {
|
|
1060
|
-
const tilmeld = this
|
|
1808
|
+
const tilmeld = enforceTilmeld(this);
|
|
1809
|
+
|
|
1810
|
+
this.$nymph.config.debugInfo(
|
|
1811
|
+
'tilmeld',
|
|
1812
|
+
`Username check "${this.$data.username}".`,
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1061
1815
|
if (!tilmeld.config.emailUsernames) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1816
|
+
let [username, domain] = tilmeld.config.domainSupport
|
|
1817
|
+
? splitn(this.$data.username ?? '', '@', 2)
|
|
1818
|
+
: [this.$data.username, null];
|
|
1819
|
+
|
|
1820
|
+
// Lowercase the domain part.
|
|
1821
|
+
if (domain) {
|
|
1822
|
+
domain = domain.toLowerCase();
|
|
1823
|
+
this.$data.username = `${username}@${domain}`;
|
|
1064
1824
|
}
|
|
1825
|
+
|
|
1065
1826
|
if (
|
|
1066
|
-
|
|
1067
|
-
this.$data.username.length
|
|
1827
|
+
this.$data.username == null ||
|
|
1828
|
+
!this.$data.username.length ||
|
|
1829
|
+
username == null ||
|
|
1830
|
+
!username.length
|
|
1068
1831
|
) {
|
|
1832
|
+
return { result: false, message: 'Please specify a username.' };
|
|
1833
|
+
}
|
|
1834
|
+
if (username.length < tilmeld.config.minUsernameLength) {
|
|
1835
|
+
return {
|
|
1836
|
+
result: false,
|
|
1837
|
+
message:
|
|
1838
|
+
'Usernames must be at least ' +
|
|
1839
|
+
tilmeld.config.minUsernameLength +
|
|
1840
|
+
' characters.',
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
if (username.length > tilmeld.config.maxUsernameLength) {
|
|
1069
1844
|
return {
|
|
1070
1845
|
result: false,
|
|
1071
1846
|
message:
|
|
@@ -1076,22 +1851,39 @@ export default class User extends AbleObject<UserData> {
|
|
|
1076
1851
|
}
|
|
1077
1852
|
|
|
1078
1853
|
if (
|
|
1079
|
-
difference(
|
|
1080
|
-
|
|
1081
|
-
tilmeld.config.validChars.split('')
|
|
1082
|
-
).length
|
|
1854
|
+
difference(username.split(''), tilmeld.config.validChars.split(''))
|
|
1855
|
+
.length
|
|
1083
1856
|
) {
|
|
1084
1857
|
return {
|
|
1085
1858
|
result: false,
|
|
1086
1859
|
message: tilmeld.config.validCharsNotice,
|
|
1087
1860
|
};
|
|
1088
1861
|
}
|
|
1089
|
-
if (!tilmeld.config.validRegex.test(
|
|
1862
|
+
if (!tilmeld.config.validRegex.test(username)) {
|
|
1090
1863
|
return {
|
|
1091
1864
|
result: false,
|
|
1092
1865
|
message: tilmeld.config.validRegexNotice,
|
|
1093
1866
|
};
|
|
1094
1867
|
}
|
|
1868
|
+
if (domain != null) {
|
|
1869
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
1870
|
+
return {
|
|
1871
|
+
result: false,
|
|
1872
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
if (
|
|
1876
|
+
!this.$skipAcWhenSaving &&
|
|
1877
|
+
!this.$is(tilmeld.currentUser) &&
|
|
1878
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
1879
|
+
) {
|
|
1880
|
+
return {
|
|
1881
|
+
result: false,
|
|
1882
|
+
message:
|
|
1883
|
+
"You don't have the authority to manage users on this domain.",
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1095
1887
|
|
|
1096
1888
|
const selector: Selector = {
|
|
1097
1889
|
type: '&',
|
|
@@ -1105,12 +1897,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
1105
1897
|
}
|
|
1106
1898
|
const test = await this.$nymph.getEntity(
|
|
1107
1899
|
{ class: tilmeld.User, skipAc: true },
|
|
1108
|
-
selector
|
|
1900
|
+
selector,
|
|
1109
1901
|
);
|
|
1110
1902
|
if (test != null) {
|
|
1111
1903
|
return { result: false, message: 'That username is taken.' };
|
|
1112
1904
|
}
|
|
1113
1905
|
|
|
1906
|
+
for (let callback of (this.constructor as typeof User)
|
|
1907
|
+
.checkUsernameCallbacks) {
|
|
1908
|
+
if (callback) {
|
|
1909
|
+
const result = await callback(this, {
|
|
1910
|
+
username: this.$data.username,
|
|
1911
|
+
});
|
|
1912
|
+
if (!result.result) {
|
|
1913
|
+
return { message: 'Username is not available.', ...result };
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1114
1918
|
return {
|
|
1115
1919
|
result: true,
|
|
1116
1920
|
message:
|
|
@@ -1121,10 +1925,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1121
1925
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1122
1926
|
return { result: false, message: 'Please specify an email.' };
|
|
1123
1927
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1928
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1929
|
+
return {
|
|
1930
|
+
result: false,
|
|
1931
|
+
message:
|
|
1932
|
+
'Emails must be at least ' +
|
|
1933
|
+
tilmeld.config.minUsernameLength +
|
|
1934
|
+
' characters.',
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
1128
1938
|
return {
|
|
1129
1939
|
result: false,
|
|
1130
1940
|
message:
|
|
@@ -1144,7 +1954,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1144
1954
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1145
1955
|
*/
|
|
1146
1956
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
1147
|
-
const tilmeld = this
|
|
1957
|
+
const tilmeld = enforceTilmeld(this);
|
|
1958
|
+
|
|
1959
|
+
this.$nymph.config.debugInfo(
|
|
1960
|
+
'tilmeld',
|
|
1961
|
+
`Email check "${this.$data.email}".`,
|
|
1962
|
+
);
|
|
1963
|
+
|
|
1148
1964
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
1149
1965
|
if (tilmeld.config.verifyEmail) {
|
|
1150
1966
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -1167,7 +1983,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1167
1983
|
}
|
|
1168
1984
|
const test = await this.$nymph.getEntity(
|
|
1169
1985
|
{ class: tilmeld.User, skipAc: true },
|
|
1170
|
-
selector
|
|
1986
|
+
selector,
|
|
1171
1987
|
);
|
|
1172
1988
|
if (test != null) {
|
|
1173
1989
|
return {
|
|
@@ -1189,7 +2005,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1189
2005
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1190
2006
|
*/
|
|
1191
2007
|
public async $checkPhone(): Promise<{ result: boolean; message: string }> {
|
|
1192
|
-
const tilmeld = this
|
|
2008
|
+
const tilmeld = enforceTilmeld(this);
|
|
2009
|
+
|
|
2010
|
+
this.$nymph.config.debugInfo(
|
|
2011
|
+
'tilmeld',
|
|
2012
|
+
`Phone check "${this.$data.phone}".`,
|
|
2013
|
+
);
|
|
2014
|
+
|
|
1193
2015
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
1194
2016
|
return { result: false, message: 'Please specify a phone number.' };
|
|
1195
2017
|
}
|
|
@@ -1211,7 +2033,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1211
2033
|
}
|
|
1212
2034
|
const test = await this.$nymph.getEntity(
|
|
1213
2035
|
{ class: tilmeld.User, skipAc: true },
|
|
1214
|
-
selector
|
|
2036
|
+
selector,
|
|
1215
2037
|
);
|
|
1216
2038
|
if (test != null) {
|
|
1217
2039
|
return { result: false, message: 'Phone number is in use.' };
|
|
@@ -1228,7 +2050,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1228
2050
|
password: string;
|
|
1229
2051
|
additionalData?: { [k: string]: any };
|
|
1230
2052
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1231
|
-
let tilmeld = this
|
|
2053
|
+
let tilmeld = enforceTilmeld(this);
|
|
1232
2054
|
if (!tilmeld.config.allowRegistration) {
|
|
1233
2055
|
return {
|
|
1234
2056
|
result: false,
|
|
@@ -1244,21 +2066,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
1244
2066
|
};
|
|
1245
2067
|
}
|
|
1246
2068
|
|
|
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
2069
|
if (!('password' in data) || !data.password.length) {
|
|
1263
2070
|
return {
|
|
1264
2071
|
result: false,
|
|
@@ -1272,7 +2079,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1272
2079
|
}
|
|
1273
2080
|
|
|
1274
2081
|
this.$password(data.password);
|
|
1275
|
-
if (tilmeld.config.regFields.
|
|
2082
|
+
if (tilmeld.config.regFields.includes('name')) {
|
|
1276
2083
|
this.$data.name =
|
|
1277
2084
|
this.$data.nameFirst +
|
|
1278
2085
|
(this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
|
|
@@ -1289,75 +2096,34 @@ export default class User extends AbleObject<UserData> {
|
|
|
1289
2096
|
const transaction = 'tilmeld-register-' + nanoid();
|
|
1290
2097
|
const nymph = this.$nymph;
|
|
1291
2098
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1292
|
-
this.$
|
|
1293
|
-
tilmeld = this
|
|
2099
|
+
this.$setNymph(tnymph);
|
|
2100
|
+
tilmeld = enforceTilmeld(this);
|
|
2101
|
+
|
|
2102
|
+
let message = '';
|
|
2103
|
+
let loggedin = false;
|
|
1294
2104
|
|
|
1295
2105
|
try {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
generatedPrimaryGroup.groupname = this.$data.username;
|
|
1302
|
-
generatedPrimaryGroup.avatar = this.$data.avatar;
|
|
1303
|
-
generatedPrimaryGroup.name = this.$data.name;
|
|
1304
|
-
generatedPrimaryGroup.email = this.$data.email;
|
|
1305
|
-
const parent = await tnymph.getEntity(
|
|
1306
|
-
{ class: tilmeld.Group },
|
|
1307
|
-
{
|
|
1308
|
-
type: '&',
|
|
1309
|
-
equal: ['defaultPrimary', true],
|
|
1310
|
-
}
|
|
1311
|
-
);
|
|
1312
|
-
if (parent != null) {
|
|
1313
|
-
generatedPrimaryGroup.parent = parent;
|
|
1314
|
-
}
|
|
1315
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
1316
|
-
await tnymph.rollback(transaction);
|
|
1317
|
-
this.$nymph = nymph;
|
|
1318
|
-
return {
|
|
1319
|
-
result: false,
|
|
1320
|
-
loggedin: false,
|
|
1321
|
-
message: 'Error creating primary group for user.',
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
this.$data.group = generatedPrimaryGroup;
|
|
1325
|
-
} else {
|
|
1326
|
-
// Add the default primary.
|
|
1327
|
-
const group = await tnymph.getEntity(
|
|
1328
|
-
{ class: tilmeld.Group },
|
|
1329
|
-
{
|
|
1330
|
-
type: '&',
|
|
1331
|
-
equal: ['defaultPrimary', true],
|
|
2106
|
+
try {
|
|
2107
|
+
for (let callback of (this.constructor as typeof User)
|
|
2108
|
+
.beforeRegisterCallbacks) {
|
|
2109
|
+
if (callback) {
|
|
2110
|
+
await callback(this, data);
|
|
1332
2111
|
}
|
|
1333
|
-
);
|
|
1334
|
-
if (group != null) {
|
|
1335
|
-
this.$data.group = group;
|
|
1336
2112
|
}
|
|
2113
|
+
} catch (e: any) {
|
|
2114
|
+
await tnymph.rollback(transaction);
|
|
2115
|
+
this.$setNymph(nymph);
|
|
2116
|
+
return {
|
|
2117
|
+
result: false,
|
|
2118
|
+
loggedin: false,
|
|
2119
|
+
message: e.message,
|
|
2120
|
+
};
|
|
1337
2121
|
}
|
|
1338
2122
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
{ class: tilmeld.Group },
|
|
1344
|
-
{
|
|
1345
|
-
type: '&',
|
|
1346
|
-
equal: ['unverifiedSecondary', true],
|
|
1347
|
-
}
|
|
1348
|
-
);
|
|
1349
|
-
} else {
|
|
1350
|
-
// Add the default secondaries.
|
|
1351
|
-
this.$data.groups = await tnymph.getEntities(
|
|
1352
|
-
{ class: tilmeld.Group },
|
|
1353
|
-
{
|
|
1354
|
-
type: '&',
|
|
1355
|
-
equal: ['defaultSecondary', true],
|
|
1356
|
-
}
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
if (tilmeld.config.verifyEmail) {
|
|
2123
|
+
if (
|
|
2124
|
+
tilmeld.config.userFields.includes('email') &&
|
|
2125
|
+
tilmeld.config.verifyEmail
|
|
2126
|
+
) {
|
|
1361
2127
|
// The user will be enabled after verifying their e-mail address.
|
|
1362
2128
|
if (!tilmeld.config.unverifiedAccess) {
|
|
1363
2129
|
this.$data.enabled = false;
|
|
@@ -1366,7 +2132,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1366
2132
|
this.$data.enabled = true;
|
|
1367
2133
|
}
|
|
1368
2134
|
|
|
1369
|
-
// If
|
|
2135
|
+
// If createAdmin is true and there are no other users, grant
|
|
1370
2136
|
// "system/admin".
|
|
1371
2137
|
let madeAdmin = false;
|
|
1372
2138
|
if (tilmeld.config.createAdmin) {
|
|
@@ -1377,6 +2143,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
1377
2143
|
return: 'guid',
|
|
1378
2144
|
});
|
|
1379
2145
|
// Make sure it's not just null, cause that means an error.
|
|
2146
|
+
if (otherUsers == null) {
|
|
2147
|
+
await tnymph.rollback(transaction);
|
|
2148
|
+
this.$setNymph(nymph);
|
|
2149
|
+
return {
|
|
2150
|
+
result: false,
|
|
2151
|
+
loggedin: false,
|
|
2152
|
+
message: 'An error occurred.',
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
1380
2155
|
if (!otherUsers.length) {
|
|
1381
2156
|
this.$grant('system/admin');
|
|
1382
2157
|
this.$data.enabled = true;
|
|
@@ -1384,7 +2159,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
1384
2159
|
}
|
|
1385
2160
|
}
|
|
1386
2161
|
|
|
2162
|
+
this.$nymph.config.debugLog(
|
|
2163
|
+
'tilmeld',
|
|
2164
|
+
`Registering new user "${this.$data.username}".`,
|
|
2165
|
+
);
|
|
1387
2166
|
if (await this.$saveSkipAC()) {
|
|
2167
|
+
this.$nymph.config.debugLog(
|
|
2168
|
+
'tilmeld',
|
|
2169
|
+
`New user registered "${this.$data.username}".`,
|
|
2170
|
+
);
|
|
1388
2171
|
// Send the new user registered email.
|
|
1389
2172
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
1390
2173
|
await tilmeld.config.sendEmail(
|
|
@@ -1403,33 +2186,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1403
2186
|
userPhone: this.$data.phone,
|
|
1404
2187
|
},
|
|
1405
2188
|
},
|
|
1406
|
-
this
|
|
2189
|
+
this,
|
|
1407
2190
|
);
|
|
1408
2191
|
}
|
|
1409
2192
|
|
|
1410
|
-
let message = '';
|
|
1411
|
-
let loggedin = false;
|
|
1412
|
-
|
|
1413
|
-
// Save the primary group.
|
|
1414
|
-
if (generatedPrimaryGroup != null) {
|
|
1415
|
-
generatedPrimaryGroup.user = this;
|
|
1416
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
1417
|
-
await tnymph.rollback(transaction);
|
|
1418
|
-
this.$nymph = nymph;
|
|
1419
|
-
return {
|
|
1420
|
-
result: false,
|
|
1421
|
-
loggedin: false,
|
|
1422
|
-
message:
|
|
1423
|
-
'Your primary group could not be assigned. Please try again later.',
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
2193
|
// Finish up.
|
|
1429
2194
|
if (
|
|
1430
2195
|
tilmeld.config.verifyEmail &&
|
|
1431
2196
|
!tilmeld.config.unverifiedAccess &&
|
|
1432
|
-
!madeAdmin
|
|
2197
|
+
!madeAdmin &&
|
|
2198
|
+
tilmeld.config.userFields.includes('email')
|
|
1433
2199
|
) {
|
|
1434
2200
|
message +=
|
|
1435
2201
|
`Almost there. An email has been sent to ${this.$data.email} ` +
|
|
@@ -1437,9 +2203,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1437
2203
|
} else if (
|
|
1438
2204
|
tilmeld.config.verifyEmail &&
|
|
1439
2205
|
tilmeld.config.unverifiedAccess &&
|
|
1440
|
-
!madeAdmin
|
|
2206
|
+
!madeAdmin &&
|
|
2207
|
+
tilmeld.config.userFields.includes('email')
|
|
1441
2208
|
) {
|
|
1442
|
-
if (!tilmeld.login(this, true)) {
|
|
2209
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1443
2210
|
throw new Error('An error occurred trying to log you in.');
|
|
1444
2211
|
}
|
|
1445
2212
|
message +=
|
|
@@ -1448,7 +2215,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1448
2215
|
'registration.';
|
|
1449
2216
|
loggedin = true;
|
|
1450
2217
|
} else {
|
|
1451
|
-
if (!tilmeld.login(this, true)) {
|
|
2218
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1452
2219
|
throw new Error('An error occurred trying to log you in.');
|
|
1453
2220
|
}
|
|
1454
2221
|
message += "You're now registered and logged in!";
|
|
@@ -1464,23 +2231,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1464
2231
|
});
|
|
1465
2232
|
}
|
|
1466
2233
|
}
|
|
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
2234
|
} else {
|
|
2235
|
+
this.$nymph.config.debugError(
|
|
2236
|
+
'tilmeld',
|
|
2237
|
+
`Error registering new user "${this.$data.username}".`,
|
|
2238
|
+
);
|
|
1482
2239
|
await tnymph.rollback(transaction);
|
|
1483
|
-
this.$nymph
|
|
2240
|
+
this.$setNymph(nymph);
|
|
1484
2241
|
return {
|
|
1485
2242
|
result: false,
|
|
1486
2243
|
loggedin: false,
|
|
@@ -1488,28 +2245,100 @@ export default class User extends AbleObject<UserData> {
|
|
|
1488
2245
|
};
|
|
1489
2246
|
}
|
|
1490
2247
|
} catch (e: any) {
|
|
2248
|
+
this.$nymph.config.debugError(
|
|
2249
|
+
'tilmeld',
|
|
2250
|
+
`Error registering new user "${this.$data.username}": ${e}`,
|
|
2251
|
+
);
|
|
1491
2252
|
await tnymph.rollback(transaction);
|
|
1492
|
-
this.$nymph
|
|
2253
|
+
this.$setNymph(nymph);
|
|
2254
|
+
throw e;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
try {
|
|
2258
|
+
await tnymph.commit(transaction);
|
|
2259
|
+
this.$setNymph(nymph);
|
|
2260
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
2261
|
+
} catch (e: any) {
|
|
1493
2262
|
throw e;
|
|
1494
2263
|
}
|
|
2264
|
+
|
|
2265
|
+
return {
|
|
2266
|
+
result: true,
|
|
2267
|
+
loggedin,
|
|
2268
|
+
message,
|
|
2269
|
+
};
|
|
1495
2270
|
}
|
|
1496
2271
|
|
|
1497
2272
|
public async $save() {
|
|
1498
|
-
let tilmeld = this
|
|
2273
|
+
let tilmeld = enforceTilmeld(this);
|
|
1499
2274
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1500
2275
|
return false;
|
|
1501
2276
|
}
|
|
1502
2277
|
|
|
1503
2278
|
if (
|
|
2279
|
+
!this.$skipAcWhenSaving &&
|
|
1504
2280
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1505
2281
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1506
|
-
this.$
|
|
2282
|
+
this.$data.abilities?.includes('system/admin')
|
|
1507
2283
|
) {
|
|
1508
|
-
throw new
|
|
1509
|
-
"You don't have the authority to modify system admins."
|
|
2284
|
+
throw new AccessControlError(
|
|
2285
|
+
"You don't have the authority to modify system admins.",
|
|
1510
2286
|
);
|
|
1511
2287
|
}
|
|
1512
2288
|
|
|
2289
|
+
// Lowercase the domain part.
|
|
2290
|
+
if (tilmeld.config.domainSupport) {
|
|
2291
|
+
const [username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
2292
|
+
if (domain) {
|
|
2293
|
+
this.$data.username = `${username}@${domain.toLowerCase()}`;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
if (tilmeld.config.domainSupport) {
|
|
2298
|
+
const [_username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
2299
|
+
const [_originalUsername, originalDomain] = splitn(
|
|
2300
|
+
this.$originalUsername ?? '',
|
|
2301
|
+
'@',
|
|
2302
|
+
2,
|
|
2303
|
+
);
|
|
2304
|
+
|
|
2305
|
+
if (
|
|
2306
|
+
this.guid != null &&
|
|
2307
|
+
((originalDomain == null && domain != null) ||
|
|
2308
|
+
(domain == null && originalDomain != null))
|
|
2309
|
+
) {
|
|
2310
|
+
throw new AccessControlError(
|
|
2311
|
+
"Users can't switch to or from having a domain once created.",
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
if (
|
|
2316
|
+
!this.$skipAcWhenSaving &&
|
|
2317
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
2318
|
+
!tilmeld.gatekeeper('system/admin')
|
|
2319
|
+
) {
|
|
2320
|
+
if (
|
|
2321
|
+
domain != null &&
|
|
2322
|
+
!this.$is(tilmeld.currentUser) &&
|
|
2323
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)
|
|
2324
|
+
) {
|
|
2325
|
+
throw new AccessControlError(
|
|
2326
|
+
"You don't have the authority to modify users on this domain.",
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
if (
|
|
2330
|
+
originalDomain != null &&
|
|
2331
|
+
originalDomain !== domain &&
|
|
2332
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
2333
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))
|
|
2334
|
+
) {
|
|
2335
|
+
throw new AccessControlError(
|
|
2336
|
+
"You don't have the authority to modify users on this domain.",
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
1513
2342
|
let sendVerification = false;
|
|
1514
2343
|
|
|
1515
2344
|
// Formatting.
|
|
@@ -1528,49 +2357,88 @@ export default class User extends AbleObject<UserData> {
|
|
|
1528
2357
|
this.$data.enabled = !!this.$data.enabled;
|
|
1529
2358
|
|
|
1530
2359
|
// Clear empty values.
|
|
1531
|
-
if (
|
|
2360
|
+
if (
|
|
2361
|
+
this.$data.nameFirst === '' ||
|
|
2362
|
+
!tilmeld.config.userFields.includes('name')
|
|
2363
|
+
) {
|
|
1532
2364
|
delete this.$data.nameFirst;
|
|
1533
2365
|
}
|
|
1534
|
-
if (
|
|
2366
|
+
if (
|
|
2367
|
+
this.$data.nameMiddle === '' ||
|
|
2368
|
+
!tilmeld.config.userFields.includes('name')
|
|
2369
|
+
) {
|
|
1535
2370
|
delete this.$data.nameMiddle;
|
|
1536
2371
|
}
|
|
1537
|
-
if (
|
|
2372
|
+
if (
|
|
2373
|
+
this.$data.nameLast === '' ||
|
|
2374
|
+
!tilmeld.config.userFields.includes('name')
|
|
2375
|
+
) {
|
|
1538
2376
|
delete this.$data.nameLast;
|
|
1539
2377
|
}
|
|
1540
|
-
if (this.$data.name === '') {
|
|
2378
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
1541
2379
|
delete this.$data.name;
|
|
1542
2380
|
}
|
|
1543
|
-
if (
|
|
2381
|
+
if (
|
|
2382
|
+
this.$data.email === '' ||
|
|
2383
|
+
!tilmeld.config.userFields.includes('email')
|
|
2384
|
+
) {
|
|
1544
2385
|
delete this.$data.email;
|
|
1545
2386
|
}
|
|
1546
2387
|
if (this.$data.avatar === '') {
|
|
1547
2388
|
delete this.$data.avatar;
|
|
1548
2389
|
}
|
|
1549
|
-
if (
|
|
2390
|
+
if (
|
|
2391
|
+
this.$data.phone === '' ||
|
|
2392
|
+
!tilmeld.config.userFields.includes('phone')
|
|
2393
|
+
) {
|
|
1550
2394
|
delete this.$data.phone;
|
|
1551
2395
|
}
|
|
1552
|
-
if (
|
|
2396
|
+
if (
|
|
2397
|
+
this.$data.secret === '' ||
|
|
2398
|
+
!tilmeld.config.userFields.includes('email')
|
|
2399
|
+
) {
|
|
1553
2400
|
delete this.$data.secret;
|
|
1554
2401
|
}
|
|
1555
|
-
if (
|
|
2402
|
+
if (
|
|
2403
|
+
this.$data.emailChangeDate === 0 ||
|
|
2404
|
+
!tilmeld.config.userFields.includes('email')
|
|
2405
|
+
) {
|
|
1556
2406
|
delete this.$data.emailChangeDate;
|
|
1557
2407
|
}
|
|
1558
|
-
if (
|
|
2408
|
+
if (
|
|
2409
|
+
this.$data.newEmailSecret === '' ||
|
|
2410
|
+
!tilmeld.config.userFields.includes('email')
|
|
2411
|
+
) {
|
|
1559
2412
|
delete this.$data.newEmailSecret;
|
|
1560
2413
|
}
|
|
1561
|
-
if (
|
|
2414
|
+
if (
|
|
2415
|
+
this.$data.newEmailAddress === '' ||
|
|
2416
|
+
!tilmeld.config.userFields.includes('email')
|
|
2417
|
+
) {
|
|
1562
2418
|
delete this.$data.newEmailAddress;
|
|
1563
2419
|
}
|
|
1564
|
-
if (
|
|
2420
|
+
if (
|
|
2421
|
+
this.$data.cancelEmailSecret === '' ||
|
|
2422
|
+
!tilmeld.config.userFields.includes('email')
|
|
2423
|
+
) {
|
|
1565
2424
|
delete this.$data.cancelEmailSecret;
|
|
1566
2425
|
}
|
|
1567
|
-
if (
|
|
2426
|
+
if (
|
|
2427
|
+
this.$data.cancelEmailAddress === '' ||
|
|
2428
|
+
!tilmeld.config.userFields.includes('email')
|
|
2429
|
+
) {
|
|
1568
2430
|
delete this.$data.cancelEmailAddress;
|
|
1569
2431
|
}
|
|
1570
|
-
if (
|
|
2432
|
+
if (
|
|
2433
|
+
this.$data.recoverSecret === '' ||
|
|
2434
|
+
!tilmeld.config.userFields.includes('email')
|
|
2435
|
+
) {
|
|
1571
2436
|
delete this.$data.recoverSecret;
|
|
1572
2437
|
}
|
|
1573
|
-
if (
|
|
2438
|
+
if (
|
|
2439
|
+
this.$data.recoverSecretDate === 0 ||
|
|
2440
|
+
!tilmeld.config.userFields.includes('email')
|
|
2441
|
+
) {
|
|
1574
2442
|
delete this.$data.recoverSecretDate;
|
|
1575
2443
|
}
|
|
1576
2444
|
if (this.$data.password === '') {
|
|
@@ -1588,7 +2456,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1588
2456
|
if (!unCheck.result) {
|
|
1589
2457
|
throw new BadUsernameError(unCheck.message);
|
|
1590
2458
|
}
|
|
1591
|
-
if (
|
|
2459
|
+
if (
|
|
2460
|
+
!tilmeld.config.emailUsernames &&
|
|
2461
|
+
tilmeld.config.userFields.includes('email')
|
|
2462
|
+
) {
|
|
1592
2463
|
const emCheck = await this.$checkEmail();
|
|
1593
2464
|
if (!emCheck.result) {
|
|
1594
2465
|
throw new BadEmailError(emCheck.message);
|
|
@@ -1596,7 +2467,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1596
2467
|
}
|
|
1597
2468
|
|
|
1598
2469
|
// Email changes.
|
|
1599
|
-
if (
|
|
2470
|
+
if (
|
|
2471
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
2472
|
+
tilmeld.config.userFields.includes('email')
|
|
2473
|
+
) {
|
|
1600
2474
|
// The user isn't an admin, so email address changes should contain some
|
|
1601
2475
|
// security measures.
|
|
1602
2476
|
if (tilmeld.config.verifyEmail) {
|
|
@@ -1604,7 +2478,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1604
2478
|
if (this.guid == null) {
|
|
1605
2479
|
// If this is the first user, they'll be made an admin, so they don't
|
|
1606
2480
|
// need to verify.
|
|
1607
|
-
if (
|
|
2481
|
+
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1608
2482
|
this.$data.secret = nanoid();
|
|
1609
2483
|
sendVerification = true;
|
|
1610
2484
|
}
|
|
@@ -1624,10 +2498,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1624
2498
|
new Date(
|
|
1625
2499
|
strtotime(
|
|
1626
2500
|
'+' + tilmeld.config.emailRateLimit,
|
|
1627
|
-
Math.floor(this.$data.emailChangeDate / 1000)
|
|
1628
|
-
) * 1000
|
|
2501
|
+
Math.floor(this.$data.emailChangeDate / 1000),
|
|
2502
|
+
) * 1000,
|
|
1629
2503
|
).toString() +
|
|
1630
|
-
' to change your email address again.'
|
|
2504
|
+
' to change your email address again.',
|
|
1631
2505
|
);
|
|
1632
2506
|
} else {
|
|
1633
2507
|
if (
|
|
@@ -1682,51 +2556,157 @@ export default class User extends AbleObject<UserData> {
|
|
|
1682
2556
|
throw new BadDataError('A password is required.');
|
|
1683
2557
|
}
|
|
1684
2558
|
|
|
1685
|
-
try {
|
|
1686
|
-
tilmeld.config.validatorUser(this);
|
|
1687
|
-
} catch (e: any) {
|
|
1688
|
-
throw new BadDataError(e?.message);
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
2559
|
// Start transaction.
|
|
1692
|
-
const transaction = 'tilmeld-save-' + nanoid();
|
|
2560
|
+
const transaction = 'tilmeld-save-user-' + (this.guid || nanoid());
|
|
1693
2561
|
const nymph = this.$nymph;
|
|
1694
2562
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1695
|
-
this.$
|
|
1696
|
-
tilmeld = this
|
|
2563
|
+
this.$setNymph(tnymph);
|
|
2564
|
+
tilmeld = enforceTilmeld(this);
|
|
2565
|
+
const Group = tnymph.getEntityClass(GroupClass);
|
|
1697
2566
|
|
|
1698
|
-
|
|
1699
|
-
this.$data.group
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
2567
|
+
try {
|
|
2568
|
+
let group = this.$data.group;
|
|
2569
|
+
if (group != null) {
|
|
2570
|
+
// Make sure to get fresh group data.
|
|
2571
|
+
if (group.$asleep()) {
|
|
2572
|
+
await group.$wake();
|
|
2573
|
+
} else {
|
|
2574
|
+
if (!(await group.$refresh())) {
|
|
2575
|
+
throw new Error("Couldn't retrieve primary group info.");
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
if (group == null && this.guid == null) {
|
|
2580
|
+
if (tilmeld.config.generatePrimary) {
|
|
2581
|
+
// Generate a new primary group for the user.
|
|
2582
|
+
group = await Group.factory();
|
|
2583
|
+
group.user = this;
|
|
2584
|
+
const parent = await tnymph.getEntity(
|
|
2585
|
+
{ class: Group },
|
|
2586
|
+
{
|
|
2587
|
+
type: '&',
|
|
2588
|
+
equal: ['defaultPrimary', true],
|
|
2589
|
+
},
|
|
2590
|
+
);
|
|
2591
|
+
if (parent != null) {
|
|
2592
|
+
group.parent = parent;
|
|
2593
|
+
}
|
|
2594
|
+
} else {
|
|
2595
|
+
// Add the default primary.
|
|
2596
|
+
const group = await tnymph.getEntity(
|
|
2597
|
+
{ class: Group },
|
|
2598
|
+
{
|
|
2599
|
+
type: '&',
|
|
2600
|
+
equal: ['defaultPrimary', true],
|
|
2601
|
+
},
|
|
2602
|
+
);
|
|
2603
|
+
if (group != null) {
|
|
2604
|
+
this.$data.group = group;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1704
2609
|
// Update the user's generated primary group.
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2610
|
+
let changed = false;
|
|
2611
|
+
if (group.groupname !== this.$data.username) {
|
|
2612
|
+
group.groupname = this.$data.username;
|
|
2613
|
+
changed = true;
|
|
2614
|
+
}
|
|
2615
|
+
if (group.avatar !== this.$data.avatar) {
|
|
2616
|
+
group.avatar = this.$data.avatar;
|
|
2617
|
+
changed = true;
|
|
2618
|
+
}
|
|
2619
|
+
if (
|
|
2620
|
+
tilmeld.config.userFields.includes('email') &&
|
|
2621
|
+
group.email !== this.$data.email
|
|
2622
|
+
) {
|
|
2623
|
+
group.email = this.$data.email;
|
|
2624
|
+
changed = true;
|
|
2625
|
+
}
|
|
2626
|
+
if (
|
|
2627
|
+
tilmeld.config.userFields.includes('name') &&
|
|
2628
|
+
group.name !== this.$data.name
|
|
2629
|
+
) {
|
|
2630
|
+
group.name = this.$data.name;
|
|
2631
|
+
changed = true;
|
|
2632
|
+
}
|
|
2633
|
+
if (
|
|
2634
|
+
tilmeld.config.userFields.includes('phone') &&
|
|
2635
|
+
group.phone !== this.$data.phone
|
|
2636
|
+
) {
|
|
2637
|
+
group.phone = this.$data.phone;
|
|
2638
|
+
changed = true;
|
|
2639
|
+
}
|
|
2640
|
+
if (changed || group.guid == null) {
|
|
2641
|
+
if (!(await group.$saveSkipAC())) {
|
|
2642
|
+
throw Error('Error updating primary group for user.');
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
this.$data.group = group;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
if (this.$data.groups == null || this.$data.groups.length === 0) {
|
|
2649
|
+
// Add secondary groups.
|
|
2650
|
+
if (
|
|
2651
|
+
tilmeld.config.userFields.includes('email') &&
|
|
2652
|
+
tilmeld.config.verifyEmail &&
|
|
2653
|
+
tilmeld.config.unverifiedAccess &&
|
|
2654
|
+
this.$data.secret != null
|
|
2655
|
+
) {
|
|
2656
|
+
// Add the default secondaries for unverified users.
|
|
2657
|
+
this.$data.groups = await tnymph.getEntities(
|
|
2658
|
+
{ class: Group },
|
|
2659
|
+
{
|
|
2660
|
+
type: '&',
|
|
2661
|
+
equal: ['unverifiedSecondary', true],
|
|
2662
|
+
},
|
|
2663
|
+
);
|
|
2664
|
+
} else {
|
|
2665
|
+
// Add the default secondaries.
|
|
2666
|
+
this.$data.groups = await tnymph.getEntities(
|
|
2667
|
+
{ class: Group },
|
|
2668
|
+
{
|
|
2669
|
+
type: '&',
|
|
2670
|
+
equal: ['defaultSecondary', true],
|
|
2671
|
+
},
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
try {
|
|
2677
|
+
tilmeld.config.validatorUser(tilmeld, this);
|
|
1711
2678
|
} catch (e: any) {
|
|
1712
|
-
|
|
1713
|
-
this.$nymph = nymph;
|
|
1714
|
-
throw e;
|
|
2679
|
+
throw new BadDataError(e?.message);
|
|
1715
2680
|
}
|
|
2681
|
+
} catch (e: any) {
|
|
2682
|
+
await tnymph.rollback(transaction);
|
|
2683
|
+
this.$setNymph(nymph);
|
|
2684
|
+
throw e;
|
|
1716
2685
|
}
|
|
1717
2686
|
|
|
1718
|
-
let ret
|
|
2687
|
+
let ret = false;
|
|
1719
2688
|
let preGuid = this.guid;
|
|
1720
2689
|
let preCdate = this.cdate;
|
|
1721
2690
|
let preMdate = this.mdate;
|
|
1722
2691
|
|
|
1723
2692
|
try {
|
|
2693
|
+
for (let callback of (this.constructor as typeof User)
|
|
2694
|
+
.beforeSaveCallbacks) {
|
|
2695
|
+
if (callback) {
|
|
2696
|
+
await callback(this);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
1724
2700
|
ret = await super.$save();
|
|
1725
2701
|
} catch (e: any) {
|
|
1726
2702
|
await tnymph.rollback(transaction);
|
|
1727
|
-
this
|
|
2703
|
+
this.guid = preGuid;
|
|
2704
|
+
this.cdate = preCdate;
|
|
2705
|
+
this.mdate = preMdate;
|
|
2706
|
+
this.$setNymph(nymph);
|
|
1728
2707
|
throw e;
|
|
1729
2708
|
}
|
|
2709
|
+
|
|
1730
2710
|
if (ret) {
|
|
1731
2711
|
if (sendVerification) {
|
|
1732
2712
|
// The email has changed, so send a new verification email.
|
|
@@ -1735,23 +2715,48 @@ export default class User extends AbleObject<UserData> {
|
|
|
1735
2715
|
this.guid = preGuid;
|
|
1736
2716
|
this.cdate = preCdate;
|
|
1737
2717
|
this.mdate = preMdate;
|
|
1738
|
-
this.$nymph
|
|
2718
|
+
this.$setNymph(nymph);
|
|
1739
2719
|
throw new Error("Couldn't send verification email.");
|
|
1740
2720
|
}
|
|
1741
2721
|
}
|
|
1742
2722
|
|
|
1743
2723
|
this.$descendantGroups = undefined;
|
|
1744
2724
|
this.$gatekeeperCache = undefined;
|
|
1745
|
-
await tnymph.commit(transaction);
|
|
1746
2725
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2726
|
+
try {
|
|
2727
|
+
for (let callback of (this.constructor as typeof User)
|
|
2728
|
+
.afterSaveCallbacks) {
|
|
2729
|
+
if (callback) {
|
|
2730
|
+
await callback(this);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
} catch (e: any) {
|
|
2734
|
+
await tnymph.rollback(transaction);
|
|
2735
|
+
this.guid = preGuid;
|
|
2736
|
+
this.cdate = preCdate;
|
|
2737
|
+
this.mdate = preMdate;
|
|
2738
|
+
this.$setNymph(nymph);
|
|
2739
|
+
throw e;
|
|
1750
2740
|
}
|
|
2741
|
+
|
|
2742
|
+
ret = await tnymph.commit(transaction);
|
|
1751
2743
|
} else {
|
|
1752
2744
|
await tnymph.rollback(transaction);
|
|
1753
2745
|
}
|
|
1754
|
-
this.$nymph
|
|
2746
|
+
this.$setNymph(nymph);
|
|
2747
|
+
|
|
2748
|
+
if (ret) {
|
|
2749
|
+
this.$originalEmail = this.$data.email;
|
|
2750
|
+
this.$originalUsername = this.$data.username;
|
|
2751
|
+
|
|
2752
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
2753
|
+
|
|
2754
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
2755
|
+
// Update the user in the session cache.
|
|
2756
|
+
await tilmeld.fillSession(this);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
1755
2760
|
return ret;
|
|
1756
2761
|
}
|
|
1757
2762
|
|
|
@@ -1772,53 +2777,167 @@ export default class User extends AbleObject<UserData> {
|
|
|
1772
2777
|
}
|
|
1773
2778
|
|
|
1774
2779
|
public async $delete() {
|
|
1775
|
-
const tilmeld = this
|
|
1776
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1777
|
-
throw new
|
|
2780
|
+
const tilmeld = enforceTilmeld(this);
|
|
2781
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
2782
|
+
throw new AccessControlError(
|
|
2783
|
+
"You don't have the authority to delete users.",
|
|
2784
|
+
);
|
|
1778
2785
|
}
|
|
1779
2786
|
if (
|
|
2787
|
+
!this.$skipAcWhenDeleting &&
|
|
1780
2788
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1781
|
-
this.$
|
|
2789
|
+
this.$data.abilities?.includes('system/admin')
|
|
1782
2790
|
) {
|
|
1783
|
-
throw new
|
|
1784
|
-
"You don't have the authority to delete system admins."
|
|
2791
|
+
throw new AccessControlError(
|
|
2792
|
+
"You don't have the authority to delete system admins.",
|
|
1785
2793
|
);
|
|
1786
2794
|
}
|
|
1787
|
-
if (
|
|
1788
|
-
|
|
2795
|
+
if (this.$data.username !== this.$originalUsername) {
|
|
2796
|
+
throw new AccessControlError(
|
|
2797
|
+
"You can't delete a user while switching their username.",
|
|
2798
|
+
);
|
|
1789
2799
|
}
|
|
1790
|
-
|
|
2800
|
+
|
|
2801
|
+
const transaction = 'tilmeld-delete-user-' + this.guid;
|
|
2802
|
+
const nymph = this.$nymph;
|
|
2803
|
+
const tnymph = await nymph.startTransaction(transaction);
|
|
2804
|
+
this.$setNymph(tnymph);
|
|
2805
|
+
|
|
2806
|
+
let success = false;
|
|
2807
|
+
|
|
2808
|
+
try {
|
|
2809
|
+
if (!(await this.$refresh())) {
|
|
2810
|
+
throw new BadDataError('User could not be refreshed.');
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
if (this.$data.group != null) {
|
|
2814
|
+
if (this.$data.group.$asleep()) {
|
|
2815
|
+
await this.$data.group.$wake();
|
|
2816
|
+
} else {
|
|
2817
|
+
if (!(await this.$data.group.$refresh())) {
|
|
2818
|
+
throw new BadDataError('Group could not be refreshed.');
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
for (let callback of (this.constructor as typeof User)
|
|
2824
|
+
.beforeDeleteCallbacks) {
|
|
2825
|
+
if (callback) {
|
|
2826
|
+
await callback(this);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
2831
|
+
await this.$logout();
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
if (this.$data.group != null && this.$is(this.$data.group.user)) {
|
|
2835
|
+
// Read the skip ac value here, because super.$delete changes it.
|
|
2836
|
+
const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
|
|
2837
|
+
|
|
2838
|
+
// Delete the user.
|
|
2839
|
+
success = await super.$delete();
|
|
2840
|
+
if (success) {
|
|
2841
|
+
// Delete the generated group.
|
|
2842
|
+
success = $skipAcWhenDeleting
|
|
2843
|
+
? await this.$data.group.$deleteSkipAC()
|
|
2844
|
+
: await this.$data.group.$delete();
|
|
2845
|
+
}
|
|
2846
|
+
} else {
|
|
2847
|
+
success = await super.$delete();
|
|
2848
|
+
}
|
|
2849
|
+
} catch (e: any) {
|
|
2850
|
+
await tnymph.rollback(transaction);
|
|
2851
|
+
this.$setNymph(nymph);
|
|
2852
|
+
throw e;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
if (success) {
|
|
2856
|
+
try {
|
|
2857
|
+
for (let callback of (this.constructor as typeof User)
|
|
2858
|
+
.afterDeleteCallbacks) {
|
|
2859
|
+
if (callback) {
|
|
2860
|
+
await callback(this);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
} catch (e: any) {
|
|
2864
|
+
await tnymph.rollback(transaction);
|
|
2865
|
+
this.$setNymph(nymph);
|
|
2866
|
+
throw e;
|
|
2867
|
+
}
|
|
2868
|
+
success = await tnymph.commit(transaction);
|
|
2869
|
+
} else {
|
|
2870
|
+
await tnymph.rollback(transaction);
|
|
2871
|
+
}
|
|
2872
|
+
this.$setNymph(nymph);
|
|
2873
|
+
return success;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
/*
|
|
2877
|
+
* This should *never* be accessible on the client.
|
|
2878
|
+
*/
|
|
2879
|
+
async $deleteSkipAC() {
|
|
2880
|
+
this.$skipAcWhenDeleting = true;
|
|
2881
|
+
return await this.$delete();
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
$tilmeldDeleteSkipAC() {
|
|
2885
|
+
if (this.$skipAcWhenDeleting) {
|
|
2886
|
+
this.$skipAcWhenDeleting = false;
|
|
2887
|
+
return true;
|
|
2888
|
+
}
|
|
2889
|
+
return false;
|
|
1791
2890
|
}
|
|
1792
2891
|
|
|
1793
2892
|
public static on<T extends EventType>(
|
|
1794
2893
|
event: T,
|
|
1795
|
-
callback: T extends '
|
|
1796
|
-
?
|
|
1797
|
-
: T extends '
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2894
|
+
callback: T extends 'checkUsername'
|
|
2895
|
+
? TilmeldCheckUsernameCallback
|
|
2896
|
+
: T extends 'beforeRegister'
|
|
2897
|
+
? TilmeldBeforeRegisterCallback
|
|
2898
|
+
: T extends 'afterRegister'
|
|
2899
|
+
? TilmeldAfterRegisterCallback
|
|
2900
|
+
: T extends 'beforeSave'
|
|
2901
|
+
? TilmeldBeforeSaveCallback
|
|
2902
|
+
: T extends 'afterSave'
|
|
2903
|
+
? TilmeldAfterSaveCallback
|
|
2904
|
+
: T extends 'beforeLogin'
|
|
2905
|
+
? TilmeldBeforeLoginCallback
|
|
2906
|
+
: T extends 'afterLogin'
|
|
2907
|
+
? TilmeldAfterLoginCallback
|
|
2908
|
+
: T extends 'beforeLogout'
|
|
2909
|
+
? TilmeldBeforeLogoutCallback
|
|
2910
|
+
: T extends 'afterLogout'
|
|
2911
|
+
? TilmeldAfterLogoutCallback
|
|
2912
|
+
: T extends 'beforeDelete'
|
|
2913
|
+
? TilmeldBeforeDeleteCallback
|
|
2914
|
+
: T extends 'afterDelete'
|
|
2915
|
+
? TilmeldAfterDeleteCallback
|
|
2916
|
+
: never,
|
|
1808
2917
|
) {
|
|
1809
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
1810
|
-
? '
|
|
1811
|
-
: T extends '
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
2918
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2919
|
+
? 'checkUsernameCallbacks'
|
|
2920
|
+
: T extends 'beforeRegister'
|
|
2921
|
+
? 'beforeRegisterCallbacks'
|
|
2922
|
+
: T extends 'afterRegister'
|
|
2923
|
+
? 'afterRegisterCallbacks'
|
|
2924
|
+
: T extends 'beforeSave'
|
|
2925
|
+
? 'beforeSaveCallbacks'
|
|
2926
|
+
: T extends 'afterSave'
|
|
2927
|
+
? 'afterSaveCallbacks'
|
|
2928
|
+
: T extends 'beforeLogin'
|
|
2929
|
+
? 'beforeLoginCallbacks'
|
|
2930
|
+
: T extends 'afterLogin'
|
|
2931
|
+
? 'afterLoginCallbacks'
|
|
2932
|
+
: T extends 'beforeLogout'
|
|
2933
|
+
? 'beforeLogoutCallbacks'
|
|
2934
|
+
: T extends 'afterLogout'
|
|
2935
|
+
? 'afterLogoutCallbacks'
|
|
2936
|
+
: T extends 'beforeDelete'
|
|
2937
|
+
? 'beforeDeleteCallbacks'
|
|
2938
|
+
: T extends 'afterDelete'
|
|
2939
|
+
? 'afterDeleteCallbacks'
|
|
2940
|
+
: never;
|
|
1822
2941
|
if (!(prop in this)) {
|
|
1823
2942
|
throw new Error('Invalid event type.');
|
|
1824
2943
|
}
|
|
@@ -1829,33 +2948,53 @@ export default class User extends AbleObject<UserData> {
|
|
|
1829
2948
|
|
|
1830
2949
|
public static off<T extends EventType>(
|
|
1831
2950
|
event: T,
|
|
1832
|
-
callback: T extends '
|
|
1833
|
-
?
|
|
1834
|
-
: T extends '
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2951
|
+
callback: T extends 'checkUsername'
|
|
2952
|
+
? TilmeldCheckUsernameCallback
|
|
2953
|
+
: T extends 'beforeRegister'
|
|
2954
|
+
? TilmeldBeforeRegisterCallback
|
|
2955
|
+
: T extends 'afterRegister'
|
|
2956
|
+
? TilmeldAfterRegisterCallback
|
|
2957
|
+
: T extends 'beforeSave'
|
|
2958
|
+
? TilmeldBeforeSaveCallback
|
|
2959
|
+
: T extends 'afterSave'
|
|
2960
|
+
? TilmeldAfterSaveCallback
|
|
2961
|
+
: T extends 'beforeLogin'
|
|
2962
|
+
? TilmeldBeforeLoginCallback
|
|
2963
|
+
: T extends 'afterLogin'
|
|
2964
|
+
? TilmeldAfterLoginCallback
|
|
2965
|
+
: T extends 'beforeLogout'
|
|
2966
|
+
? TilmeldBeforeLogoutCallback
|
|
2967
|
+
: T extends 'afterLogout'
|
|
2968
|
+
? TilmeldAfterLogoutCallback
|
|
2969
|
+
: T extends 'beforeDelete'
|
|
2970
|
+
? TilmeldBeforeDeleteCallback
|
|
2971
|
+
: T extends 'afterDelete'
|
|
2972
|
+
? TilmeldAfterDeleteCallback
|
|
2973
|
+
: never,
|
|
1845
2974
|
) {
|
|
1846
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
1847
|
-
? '
|
|
1848
|
-
: T extends '
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2975
|
+
const prop = (event + 'Callbacks') as T extends 'checkUsername'
|
|
2976
|
+
? 'checkUsernameCallbacks'
|
|
2977
|
+
: T extends 'beforeRegister'
|
|
2978
|
+
? 'beforeRegisterCallbacks'
|
|
2979
|
+
: T extends 'afterRegister'
|
|
2980
|
+
? 'afterRegisterCallbacks'
|
|
2981
|
+
: T extends 'beforeSave'
|
|
2982
|
+
? 'beforeSaveCallbacks'
|
|
2983
|
+
: T extends 'afterSave'
|
|
2984
|
+
? 'afterSaveCallbacks'
|
|
2985
|
+
: T extends 'beforeLogin'
|
|
2986
|
+
? 'beforeLoginCallbacks'
|
|
2987
|
+
: T extends 'afterLogin'
|
|
2988
|
+
? 'afterLoginCallbacks'
|
|
2989
|
+
: T extends 'beforeLogout'
|
|
2990
|
+
? 'beforeLogoutCallbacks'
|
|
2991
|
+
: T extends 'afterLogout'
|
|
2992
|
+
? 'afterLogoutCallbacks'
|
|
2993
|
+
: T extends 'beforeDelete'
|
|
2994
|
+
? 'beforeDeleteCallbacks'
|
|
2995
|
+
: T extends 'afterDelete'
|
|
2996
|
+
? 'afterDeleteCallbacks'
|
|
2997
|
+
: never;
|
|
1859
2998
|
if (!(prop in this)) {
|
|
1860
2999
|
return false;
|
|
1861
3000
|
}
|