@nymphjs/tilmeld 1.0.0-beta.1 → 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 +505 -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 +657 -259
- 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 +405 -32
- package/dist/User.js +1361 -372
- 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 +449 -260
- package/src/Tilmeld.types.ts +2 -2
- package/src/User.test.ts +160 -7
- package/src/User.ts +1615 -468
- 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 } }
|
|
40
|
-
) => Promise<void
|
|
57
|
+
data: { password: string; additionalData?: { [k: string]: any } },
|
|
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 }
|
|
44
|
-
) => Promise<void
|
|
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,
|
|
74
|
+
) => Promise<void>;
|
|
75
|
+
/**
|
|
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>;
|
|
45
83
|
/**
|
|
46
|
-
*
|
|
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,15 +89,33 @@ export type TilmeldBeforeLoginCallback = (
|
|
|
51
89
|
username: string;
|
|
52
90
|
password: string;
|
|
53
91
|
additionalData?: { [k: string]: any };
|
|
54
|
-
}
|
|
55
|
-
) => void
|
|
92
|
+
},
|
|
93
|
+
) => Promise<void>;
|
|
94
|
+
export type TilmeldAfterLoginCallback = (
|
|
95
|
+
user: User & UserData,
|
|
96
|
+
) => Promise<void>;
|
|
97
|
+
export type TilmeldBeforeLogoutCallback = (
|
|
98
|
+
user: User & UserData,
|
|
99
|
+
) => Promise<void>;
|
|
100
|
+
export type TilmeldAfterLogoutCallback = (
|
|
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>;
|
|
56
112
|
/**
|
|
57
113
|
* This is run before the transaction is committed, and you can perform
|
|
58
114
|
* additional functions on the transaction, which is available in `user.$nymph`.
|
|
59
115
|
*/
|
|
60
|
-
export type
|
|
61
|
-
|
|
62
|
-
|
|
116
|
+
export type TilmeldAfterDeleteCallback = (
|
|
117
|
+
user: User & UserData,
|
|
118
|
+
) => Promise<void>;
|
|
63
119
|
|
|
64
120
|
export type UserData = {
|
|
65
121
|
/**
|
|
@@ -102,11 +158,11 @@ export type UserData = {
|
|
|
102
158
|
/**
|
|
103
159
|
* The user's primary group.
|
|
104
160
|
*/
|
|
105
|
-
group?:
|
|
161
|
+
group?: GroupClass & GroupData;
|
|
106
162
|
/**
|
|
107
163
|
* The user's secondary groups.
|
|
108
164
|
*/
|
|
109
|
-
groups?: (
|
|
165
|
+
groups?: (GroupClass & GroupData)[];
|
|
110
166
|
/**
|
|
111
167
|
* Whether the user should inherit the abilities of his groups.
|
|
112
168
|
*/
|
|
@@ -129,7 +185,8 @@ export type UserData = {
|
|
|
129
185
|
*/
|
|
130
186
|
newEmailSecret?: string;
|
|
131
187
|
/**
|
|
132
|
-
*
|
|
188
|
+
* If the user has changed their email address, this is the new one, awaiting
|
|
189
|
+
* verification.
|
|
133
190
|
*/
|
|
134
191
|
newEmailAddress?: string;
|
|
135
192
|
/**
|
|
@@ -161,6 +218,15 @@ export type UserData = {
|
|
|
161
218
|
* database.
|
|
162
219
|
*/
|
|
163
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;
|
|
164
230
|
};
|
|
165
231
|
|
|
166
232
|
/**
|
|
@@ -176,15 +242,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
176
242
|
/**
|
|
177
243
|
* The instance of Tilmeld to use for queries.
|
|
178
244
|
*/
|
|
179
|
-
static tilmeld: Tilmeld;
|
|
180
245
|
static ETYPE = 'tilmeld_user';
|
|
181
246
|
static class = 'User';
|
|
247
|
+
private static checkUsernameCallbacks: TilmeldCheckUsernameCallback[] = [];
|
|
182
248
|
private static beforeRegisterCallbacks: TilmeldBeforeRegisterCallback[] = [];
|
|
183
249
|
private static afterRegisterCallbacks: TilmeldAfterRegisterCallback[] = [];
|
|
250
|
+
private static beforeSaveCallbacks: TilmeldBeforeSaveCallback[] = [];
|
|
251
|
+
private static afterSaveCallbacks: TilmeldAfterSaveCallback[] = [];
|
|
184
252
|
private static beforeLoginCallbacks: TilmeldBeforeLoginCallback[] = [];
|
|
185
253
|
private static afterLoginCallbacks: TilmeldAfterLoginCallback[] = [];
|
|
186
254
|
private static beforeLogoutCallbacks: TilmeldBeforeLogoutCallback[] = [];
|
|
187
255
|
private static afterLogoutCallbacks: TilmeldAfterLogoutCallback[] = [];
|
|
256
|
+
private static beforeDeleteCallbacks: TilmeldBeforeDeleteCallback[] = [];
|
|
257
|
+
private static afterDeleteCallbacks: TilmeldAfterDeleteCallback[] = [];
|
|
188
258
|
|
|
189
259
|
private static DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
190
260
|
'$checkUsername',
|
|
@@ -194,23 +264,33 @@ export default class User extends AbleObject<UserData> {
|
|
|
194
264
|
'$register',
|
|
195
265
|
];
|
|
196
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',
|
|
197
274
|
'email',
|
|
198
|
-
'
|
|
275
|
+
// 'avatar',
|
|
199
276
|
'phone',
|
|
200
277
|
'group',
|
|
201
278
|
'groups',
|
|
202
|
-
'abilities',
|
|
203
279
|
'inheritAbilities',
|
|
204
|
-
'
|
|
205
|
-
'recoverSecretDate',
|
|
206
|
-
'password',
|
|
207
|
-
'salt',
|
|
280
|
+
'enabled',
|
|
208
281
|
'secret',
|
|
209
|
-
'
|
|
282
|
+
'emailChangeDate',
|
|
210
283
|
'newEmailSecret',
|
|
211
|
-
'
|
|
284
|
+
'newEmailAddress',
|
|
212
285
|
'cancelEmailSecret',
|
|
213
|
-
'
|
|
286
|
+
'cancelEmailAddress',
|
|
287
|
+
'recoverSecret',
|
|
288
|
+
'recoverSecretDate',
|
|
289
|
+
'salt',
|
|
290
|
+
'password',
|
|
291
|
+
'passwordTemp',
|
|
292
|
+
'revokeTokenDate',
|
|
293
|
+
'totpSecret',
|
|
214
294
|
];
|
|
215
295
|
private static DEFAULT_ALLOWLIST_DATA: string[] = [];
|
|
216
296
|
|
|
@@ -221,6 +301,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
221
301
|
'sendRecovery',
|
|
222
302
|
'recover',
|
|
223
303
|
'getClientConfig',
|
|
304
|
+
'getDomainUsers',
|
|
224
305
|
];
|
|
225
306
|
protected $privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
226
307
|
public static searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -235,13 +316,17 @@ export default class User extends AbleObject<UserData> {
|
|
|
235
316
|
*/
|
|
236
317
|
private $gatekeeperCache?: { [k: string]: true };
|
|
237
318
|
/**
|
|
238
|
-
* This
|
|
319
|
+
* This should only be used by the backend.
|
|
239
320
|
*/
|
|
240
321
|
private $skipAcWhenSaving = false;
|
|
322
|
+
/**
|
|
323
|
+
* This should only be used by the backend.
|
|
324
|
+
*/
|
|
325
|
+
private $skipAcWhenDeleting = false;
|
|
241
326
|
/**
|
|
242
327
|
* The user's group descendants.
|
|
243
328
|
*/
|
|
244
|
-
private $descendantGroups?: (
|
|
329
|
+
private $descendantGroups?: (GroupClass & GroupData)[];
|
|
245
330
|
/**
|
|
246
331
|
* Used to save the current email address to send verification if it changes
|
|
247
332
|
* from the frontend.
|
|
@@ -250,10 +335,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
250
335
|
* verification, don't set this.
|
|
251
336
|
*/
|
|
252
337
|
public $originalEmail?: string;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Used to save the current username for domain admin permissions.
|
|
340
|
+
*/
|
|
341
|
+
public $originalUsername?: string;
|
|
257
342
|
|
|
258
343
|
static async factoryUsername(username?: string): Promise<User & UserData> {
|
|
259
344
|
const entity = new this();
|
|
@@ -265,7 +350,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
265
350
|
{
|
|
266
351
|
type: '&',
|
|
267
352
|
ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
268
|
-
}
|
|
353
|
+
},
|
|
269
354
|
);
|
|
270
355
|
if (entity != null) {
|
|
271
356
|
return entity;
|
|
@@ -274,34 +359,18 @@ export default class User extends AbleObject<UserData> {
|
|
|
274
359
|
return entity;
|
|
275
360
|
}
|
|
276
361
|
|
|
277
|
-
static factorySync(guid?: string): User & UserData {
|
|
278
|
-
return super.factorySync(guid) as User & UserData;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
constructor(guid?: string) {
|
|
282
|
-
super(guid);
|
|
283
|
-
|
|
284
|
-
if (this.guid == null) {
|
|
285
|
-
// Defaults.
|
|
286
|
-
this.$data.enabled = true;
|
|
287
|
-
this.$data.abilities = [];
|
|
288
|
-
this.$data.groups = [];
|
|
289
|
-
this.$data.inheritAbilities = true;
|
|
290
|
-
this.$updateDataProtection();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
362
|
public static current(returnObjectIfNotExist: true): User & UserData;
|
|
295
363
|
public static current(
|
|
296
|
-
returnObjectIfNotExist?: false
|
|
364
|
+
returnObjectIfNotExist?: false,
|
|
297
365
|
): (User & UserData) | null;
|
|
298
366
|
public static current(
|
|
299
|
-
returnObjectIfNotExist?: boolean
|
|
367
|
+
returnObjectIfNotExist?: boolean,
|
|
300
368
|
): (User & UserData) | null {
|
|
301
|
-
|
|
369
|
+
const tilmeld = enforceTilmeld(this);
|
|
370
|
+
if (tilmeld.currentUser == null) {
|
|
302
371
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
303
372
|
}
|
|
304
|
-
return
|
|
373
|
+
return tilmeld.currentUser;
|
|
305
374
|
}
|
|
306
375
|
|
|
307
376
|
/**
|
|
@@ -314,7 +383,8 @@ export default class User extends AbleObject<UserData> {
|
|
|
314
383
|
recoveryType: 'username' | 'password';
|
|
315
384
|
account: string;
|
|
316
385
|
}): Promise<{ result: boolean; message: string }> {
|
|
317
|
-
|
|
386
|
+
const tilmeld = enforceTilmeld(this);
|
|
387
|
+
if (!tilmeld.config.pwRecovery) {
|
|
318
388
|
return {
|
|
319
389
|
result: false,
|
|
320
390
|
message: 'Account recovery is not allowed.',
|
|
@@ -324,18 +394,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
324
394
|
let user: User & UserData;
|
|
325
395
|
const options: EmailOptions = {};
|
|
326
396
|
|
|
327
|
-
if (
|
|
328
|
-
!this.tilmeld.config.emailUsernames &&
|
|
329
|
-
data.recoveryType === 'username'
|
|
330
|
-
) {
|
|
397
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
331
398
|
// Create a username recovery email.
|
|
332
399
|
|
|
333
400
|
const getUser = await this.nymph.getEntity(
|
|
334
|
-
{ class:
|
|
401
|
+
{ class: tilmeld.User, skipAc: true },
|
|
335
402
|
{
|
|
336
403
|
type: '&',
|
|
337
404
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
338
|
-
}
|
|
405
|
+
},
|
|
339
406
|
);
|
|
340
407
|
|
|
341
408
|
if (getUser == null) {
|
|
@@ -358,7 +425,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
358
425
|
} else if (data.recoveryType === 'password') {
|
|
359
426
|
// Create a password recovery email.
|
|
360
427
|
|
|
361
|
-
const getUser = await
|
|
428
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
362
429
|
|
|
363
430
|
if (getUser.guid == null) {
|
|
364
431
|
return {
|
|
@@ -385,14 +452,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
385
452
|
};
|
|
386
453
|
options.locals = {
|
|
387
454
|
recoverCode: getUser.recoverSecret,
|
|
388
|
-
timeLimit:
|
|
455
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
389
456
|
};
|
|
390
457
|
} else {
|
|
391
458
|
return { result: false, message: 'Invalid recovery type.' };
|
|
392
459
|
}
|
|
393
460
|
|
|
461
|
+
this.nymph.config.debugLog(
|
|
462
|
+
'tilmeld',
|
|
463
|
+
`User account recovery (${options.template}) request for "${user.username}".`,
|
|
464
|
+
);
|
|
465
|
+
|
|
394
466
|
// Send the email.
|
|
395
|
-
if (await
|
|
467
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
396
468
|
return {
|
|
397
469
|
result: true,
|
|
398
470
|
message:
|
|
@@ -415,22 +487,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
415
487
|
secret: string;
|
|
416
488
|
password: string;
|
|
417
489
|
}): Promise<{ result: boolean; message: string }> {
|
|
418
|
-
|
|
490
|
+
const tilmeld = enforceTilmeld(this);
|
|
491
|
+
if (!tilmeld.config.pwRecovery) {
|
|
419
492
|
return {
|
|
420
493
|
result: false,
|
|
421
494
|
message: 'Account recovery is not allowed.',
|
|
422
495
|
};
|
|
423
496
|
}
|
|
424
497
|
|
|
425
|
-
const user = await
|
|
498
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
426
499
|
|
|
427
500
|
if (
|
|
428
501
|
user.guid == null ||
|
|
429
502
|
user.recoverSecret == null ||
|
|
430
503
|
data.secret !== user.recoverSecret ||
|
|
431
504
|
strtotime(
|
|
432
|
-
'+' +
|
|
433
|
-
Math.floor((user.recoverSecretDate ?? 0) / 1000)
|
|
505
|
+
'+' + tilmeld.config.pwRecoveryTimeLimit,
|
|
506
|
+
Math.floor((user.recoverSecretDate ?? 0) / 1000),
|
|
434
507
|
) *
|
|
435
508
|
1000 <
|
|
436
509
|
Date.now()
|
|
@@ -445,6 +518,11 @@ export default class User extends AbleObject<UserData> {
|
|
|
445
518
|
return { result: false, message: 'Password cannot be empty.' };
|
|
446
519
|
}
|
|
447
520
|
|
|
521
|
+
this.nymph.config.debugLog(
|
|
522
|
+
'tilmeld',
|
|
523
|
+
`User account recovery for "${user.username}".`,
|
|
524
|
+
);
|
|
525
|
+
|
|
448
526
|
user.$password(data.password);
|
|
449
527
|
delete user.recoverSecret;
|
|
450
528
|
delete user.recoverSecretDate;
|
|
@@ -460,29 +538,33 @@ export default class User extends AbleObject<UserData> {
|
|
|
460
538
|
}
|
|
461
539
|
|
|
462
540
|
public static getClientConfig() {
|
|
541
|
+
const tilmeld = enforceTilmeld(this);
|
|
463
542
|
return {
|
|
464
|
-
regFields:
|
|
465
|
-
userFields:
|
|
466
|
-
emailUsernames:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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,
|
|
472
552
|
};
|
|
473
553
|
}
|
|
474
554
|
|
|
475
555
|
public static async loginUser(data: {
|
|
476
556
|
username: string;
|
|
477
557
|
password: string;
|
|
558
|
+
code?: string;
|
|
478
559
|
additionalData?: { [k: string]: any };
|
|
479
560
|
}) {
|
|
561
|
+
const tilmeld = enforceTilmeld(this);
|
|
480
562
|
if (!('username' in data) || !data.username.length) {
|
|
481
563
|
return { result: false, message: 'Incorrect login/password.' };
|
|
482
564
|
}
|
|
483
|
-
const user = await
|
|
565
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
484
566
|
const result: { result: boolean; message: string; user?: User & UserData } =
|
|
485
|
-
user.$login(data);
|
|
567
|
+
await user.$login(data);
|
|
486
568
|
if (result.result) {
|
|
487
569
|
user.$updateDataProtection();
|
|
488
570
|
result.user = user;
|
|
@@ -490,30 +572,234 @@ export default class User extends AbleObject<UserData> {
|
|
|
490
572
|
return result;
|
|
491
573
|
}
|
|
492
574
|
|
|
493
|
-
|
|
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
|
+
|
|
680
|
+
public async $login(data: {
|
|
494
681
|
username: string;
|
|
495
682
|
password: string;
|
|
683
|
+
code?: string;
|
|
496
684
|
additionalData?: { [k: string]: any };
|
|
497
685
|
}) {
|
|
498
|
-
const tilmeld = this
|
|
686
|
+
const tilmeld = enforceTilmeld(this);
|
|
499
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);
|
|
500
693
|
return { result: false, message: 'Incorrect login/password.' };
|
|
501
694
|
}
|
|
502
695
|
if (!this.$data.enabled) {
|
|
503
|
-
return { result: false, message: '
|
|
696
|
+
return { result: false, message: 'Your account is disabled.' };
|
|
504
697
|
}
|
|
505
|
-
if (this.$gatekeeper()) {
|
|
698
|
+
if (await this.$gatekeeper()) {
|
|
506
699
|
return { result: true, message: 'You are already logged in.' };
|
|
507
700
|
}
|
|
508
|
-
|
|
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
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
for (let callback of (this.constructor as typeof User)
|
|
723
|
+
.beforeLoginCallbacks) {
|
|
724
|
+
if (callback) {
|
|
725
|
+
await callback(this, data);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
} catch (e: any) {
|
|
729
|
+
return {
|
|
730
|
+
result: false,
|
|
731
|
+
message: e.message,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
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) {
|
|
509
759
|
return { result: false, message: 'Incorrect login/password.' };
|
|
510
760
|
}
|
|
511
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
|
+
|
|
512
794
|
try {
|
|
513
795
|
for (let callback of (this.constructor as typeof User)
|
|
514
796
|
.beforeLoginCallbacks) {
|
|
515
797
|
if (callback) {
|
|
516
|
-
callback(this,
|
|
798
|
+
await callback(this, {
|
|
799
|
+
username: this.$data.username || '',
|
|
800
|
+
password: '',
|
|
801
|
+
...(data || {}),
|
|
802
|
+
});
|
|
517
803
|
}
|
|
518
804
|
}
|
|
519
805
|
} catch (e: any) {
|
|
@@ -523,15 +809,20 @@ export default class User extends AbleObject<UserData> {
|
|
|
523
809
|
};
|
|
524
810
|
}
|
|
525
811
|
|
|
812
|
+
this.$nymph.config.debugLog(
|
|
813
|
+
'tilmeld',
|
|
814
|
+
`User login switch for "${user.username}" to "${this.$data.username}".`,
|
|
815
|
+
);
|
|
816
|
+
|
|
526
817
|
// Authentication was successful, attempt to login.
|
|
527
|
-
if (!tilmeld.
|
|
818
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
528
819
|
return { result: false, message: 'Incorrect login/password.' };
|
|
529
820
|
}
|
|
530
821
|
|
|
531
822
|
for (let callback of (this.constructor as typeof User)
|
|
532
823
|
.afterLoginCallbacks) {
|
|
533
824
|
if (callback) {
|
|
534
|
-
callback(this);
|
|
825
|
+
await callback(this);
|
|
535
826
|
}
|
|
536
827
|
}
|
|
537
828
|
|
|
@@ -543,14 +834,14 @@ export default class User extends AbleObject<UserData> {
|
|
|
543
834
|
* Log a user out of the system.
|
|
544
835
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
545
836
|
*/
|
|
546
|
-
public $logout() {
|
|
547
|
-
const tilmeld = this
|
|
837
|
+
public async $logout() {
|
|
838
|
+
const tilmeld = enforceTilmeld(this);
|
|
548
839
|
|
|
549
840
|
try {
|
|
550
841
|
for (let callback of (this.constructor as typeof User)
|
|
551
842
|
.beforeLogoutCallbacks) {
|
|
552
843
|
if (callback) {
|
|
553
|
-
callback(this);
|
|
844
|
+
await callback(this);
|
|
554
845
|
}
|
|
555
846
|
}
|
|
556
847
|
} catch (e: any) {
|
|
@@ -560,12 +851,17 @@ export default class User extends AbleObject<UserData> {
|
|
|
560
851
|
};
|
|
561
852
|
}
|
|
562
853
|
|
|
563
|
-
|
|
854
|
+
this.$nymph.config.debugLog(
|
|
855
|
+
'tilmeld',
|
|
856
|
+
`User logout for "${this.$data.username}".`,
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
await tilmeld.logout();
|
|
564
860
|
|
|
565
861
|
for (let callback of (this.constructor as typeof User)
|
|
566
862
|
.afterLogoutCallbacks) {
|
|
567
863
|
if (callback) {
|
|
568
|
-
callback(this);
|
|
864
|
+
await callback(this);
|
|
569
865
|
}
|
|
570
866
|
}
|
|
571
867
|
|
|
@@ -581,87 +877,176 @@ export default class User extends AbleObject<UserData> {
|
|
|
581
877
|
}
|
|
582
878
|
return (
|
|
583
879
|
'https://secure.gravatar.com/avatar/' +
|
|
584
|
-
|
|
880
|
+
crypto
|
|
881
|
+
.createHash('sha256')
|
|
882
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
883
|
+
.digest('hex')
|
|
884
|
+
.toLowerCase() +
|
|
585
885
|
'?d=identicon&s=40'
|
|
586
886
|
);
|
|
587
887
|
}
|
|
588
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
|
+
|
|
589
905
|
/**
|
|
590
906
|
* Get the user's group descendants.
|
|
591
907
|
*/
|
|
592
|
-
public async $getDescendantGroups(): Promise<(
|
|
908
|
+
public async $getDescendantGroups(): Promise<(GroupClass & GroupData)[]> {
|
|
593
909
|
if (this.$descendantGroups == null) {
|
|
594
910
|
this.$descendantGroups = [];
|
|
595
911
|
if (this.$data.group != null) {
|
|
912
|
+
await this.$data.group.$wake();
|
|
596
913
|
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
597
914
|
}
|
|
915
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
598
916
|
for (let curGroup of this.$data.groups ?? []) {
|
|
599
917
|
this.$descendantGroups = this.$descendantGroups?.concat(
|
|
600
|
-
await curGroup.$getDescendants()
|
|
918
|
+
await curGroup.$getDescendants(),
|
|
601
919
|
);
|
|
602
920
|
}
|
|
603
921
|
}
|
|
604
922
|
return this.$descendantGroups;
|
|
605
923
|
}
|
|
606
924
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (
|
|
612
|
-
this.$
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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();
|
|
620
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);
|
|
621
967
|
}
|
|
622
|
-
return this.$descendantGroups;
|
|
623
968
|
}
|
|
624
969
|
|
|
625
|
-
public $
|
|
626
|
-
|
|
627
|
-
|
|
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);
|
|
628
982
|
|
|
629
983
|
if (
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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'))
|
|
634
995
|
) {
|
|
635
996
|
throw new BadDataError(
|
|
636
|
-
"You don't have the authority to
|
|
997
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
637
998
|
);
|
|
638
999
|
}
|
|
639
1000
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
+
}
|
|
647
1011
|
|
|
648
1012
|
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
tilmeld.
|
|
652
|
-
|
|
1013
|
+
incoming.includes('tilmeld/switch') !==
|
|
1014
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
1015
|
+
(!tilmeld.currentUser ||
|
|
1016
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
653
1017
|
) {
|
|
654
1018
|
throw new BadDataError(
|
|
655
|
-
"You don't have the authority to
|
|
1019
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
656
1020
|
);
|
|
657
1021
|
}
|
|
658
1022
|
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
}
|
|
661
1038
|
}
|
|
662
1039
|
|
|
663
|
-
public $putData(
|
|
664
|
-
|
|
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
|
+
}
|
|
665
1050
|
this.$updateDataProtection();
|
|
666
1051
|
}
|
|
667
1052
|
|
|
@@ -671,8 +1056,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
671
1056
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
672
1057
|
*/
|
|
673
1058
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
674
|
-
const tilmeld = this
|
|
1059
|
+
const tilmeld = enforceTilmeld(this);
|
|
675
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
|
+
}
|
|
676
1068
|
|
|
677
1069
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
678
1070
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -690,59 +1082,171 @@ export default class User extends AbleObject<UserData> {
|
|
|
690
1082
|
? this.$data.abilities
|
|
691
1083
|
: user?.abilities) ?? [];
|
|
692
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`);
|
|
693
1091
|
|
|
694
1092
|
if (isCurrentUser) {
|
|
695
1093
|
// Users can check to see what abilities they have.
|
|
696
1094
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
697
1095
|
this.$clientEnabledMethods.push('$changePassword');
|
|
1096
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
698
1097
|
this.$clientEnabledMethods.push('$logout');
|
|
699
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');
|
|
700
1114
|
}
|
|
701
1115
|
|
|
702
1116
|
if (
|
|
703
1117
|
user != null &&
|
|
704
|
-
(abilities.
|
|
705
|
-
abilities.
|
|
1118
|
+
(abilities.includes('tilmeld/admin') ||
|
|
1119
|
+
abilities.includes('system/admin'))
|
|
706
1120
|
) {
|
|
707
1121
|
// Users who can edit other users can see most of their data.
|
|
708
|
-
this.$privateData = ['password', 'salt'];
|
|
1122
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
1123
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
1124
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
709
1125
|
this.$allowlistData = undefined;
|
|
710
|
-
} else if (
|
|
711
|
-
//
|
|
1126
|
+
} else if (isDomainUser) {
|
|
1127
|
+
// Domain admins can see their users' data, and edit some of it.
|
|
712
1128
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
713
1129
|
this.$allowlistData.push('username');
|
|
714
1130
|
}
|
|
715
1131
|
this.$allowlistData.push('avatar');
|
|
716
|
-
if (tilmeld.config.userFields.
|
|
1132
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
717
1133
|
this.$allowlistData.push('nameFirst');
|
|
718
1134
|
this.$allowlistData.push('nameMiddle');
|
|
719
1135
|
this.$allowlistData.push('nameLast');
|
|
720
1136
|
this.$allowlistData.push('name');
|
|
721
1137
|
}
|
|
722
|
-
if (tilmeld.config.userFields.
|
|
1138
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
723
1139
|
this.$allowlistData.push('email');
|
|
724
1140
|
}
|
|
725
|
-
if (tilmeld.config.userFields.
|
|
1141
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
726
1142
|
this.$allowlistData.push('phone');
|
|
727
1143
|
}
|
|
1144
|
+
this.$allowlistData.push('enabled');
|
|
1145
|
+
this.$allowlistData.push('passwordTemp');
|
|
728
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',
|
|
729
1161
|
'secret',
|
|
1162
|
+
'emailChangeDate',
|
|
730
1163
|
'newEmailSecret',
|
|
731
|
-
'
|
|
1164
|
+
// 'newEmailAddress',
|
|
732
1165
|
'cancelEmailSecret',
|
|
733
|
-
'
|
|
1166
|
+
'cancelEmailAddress',
|
|
734
1167
|
'recoverSecret',
|
|
735
1168
|
'recoverSecretDate',
|
|
736
|
-
'password',
|
|
737
1169
|
'salt',
|
|
1170
|
+
'password',
|
|
1171
|
+
// 'passwordTemp',
|
|
1172
|
+
// 'revokeTokenDate',
|
|
1173
|
+
'totpSecret',
|
|
738
1174
|
];
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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',
|
|
1216
|
+
'salt',
|
|
1217
|
+
'password',
|
|
1218
|
+
'passwordTemp',
|
|
1219
|
+
// 'revokeTokenDate',
|
|
1220
|
+
'totpSecret',
|
|
1221
|
+
];
|
|
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
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Check to see if a user has an ability.
|
|
1248
|
+
*
|
|
1249
|
+
* This function will check both user and group abilities, if the user is
|
|
746
1250
|
* marked to inherit the abilities of its group.
|
|
747
1251
|
*
|
|
748
1252
|
* If `ability` is undefined, it will check to see if the user is currently
|
|
@@ -753,35 +1257,43 @@ export default class User extends AbleObject<UserData> {
|
|
|
753
1257
|
* @param ability The ability.
|
|
754
1258
|
* @returns True or false.
|
|
755
1259
|
*/
|
|
756
|
-
public $gatekeeper(ability?: string): boolean {
|
|
757
|
-
|
|
1260
|
+
public async $gatekeeper(ability?: string): Promise<boolean> {
|
|
1261
|
+
await this.$wake();
|
|
1262
|
+
const tilmeld = enforceTilmeld(this);
|
|
758
1263
|
if (ability == null) {
|
|
759
1264
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
760
1265
|
}
|
|
761
1266
|
// Check the cache to see if we've already checked this user.
|
|
762
1267
|
if (this.$gatekeeperCache == null) {
|
|
763
|
-
|
|
764
|
-
if (this.$data.inheritAbilities) {
|
|
765
|
-
for (let curGroup of this.$data.groups ?? []) {
|
|
766
|
-
if (curGroup.enabled) {
|
|
767
|
-
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
if (
|
|
771
|
-
this.$data.group != null &&
|
|
772
|
-
this.$data.group.cdate != null &&
|
|
773
|
-
this.$data.group.enabled
|
|
774
|
-
) {
|
|
775
|
-
abilities = abilities.concat(this.$data.group.abilities ?? []);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
this.$gatekeeperCache = Object.fromEntries(
|
|
779
|
-
abilities.map((ability) => [ability, true])
|
|
780
|
-
);
|
|
1268
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
781
1269
|
}
|
|
782
1270
|
return (
|
|
783
1271
|
(ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
784
|
-
!!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]),
|
|
785
1297
|
);
|
|
786
1298
|
}
|
|
787
1299
|
|
|
@@ -798,7 +1310,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
798
1310
|
* @returns True on success, false on failure.
|
|
799
1311
|
*/
|
|
800
1312
|
public async $sendEmailVerification() {
|
|
801
|
-
const tilmeld = this
|
|
1313
|
+
const tilmeld = enforceTilmeld(this);
|
|
802
1314
|
if (this.guid == null) {
|
|
803
1315
|
return false;
|
|
804
1316
|
}
|
|
@@ -810,7 +1322,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
810
1322
|
|
|
811
1323
|
if (this.$data.secret != null) {
|
|
812
1324
|
const link = `${verifyUrl}?action=verify&id=${encodeURIComponent(
|
|
813
|
-
this.guid
|
|
1325
|
+
this.guid,
|
|
814
1326
|
)}&secret=${encodeURIComponent(this.$data.secret)}`;
|
|
815
1327
|
success =
|
|
816
1328
|
success &&
|
|
@@ -828,13 +1340,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
828
1340
|
verifyLink: link,
|
|
829
1341
|
},
|
|
830
1342
|
},
|
|
831
|
-
this
|
|
1343
|
+
this,
|
|
832
1344
|
));
|
|
833
1345
|
}
|
|
834
1346
|
|
|
835
1347
|
if (this.$data.newEmailSecret != null) {
|
|
836
1348
|
const link = `${verifyUrl}?action=verifychange&id=${encodeURIComponent(
|
|
837
|
-
this.guid
|
|
1349
|
+
this.guid,
|
|
838
1350
|
)}&secret=${encodeURIComponent(this.$data.newEmailSecret)}`;
|
|
839
1351
|
success =
|
|
840
1352
|
success &&
|
|
@@ -854,13 +1366,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
854
1366
|
newEmail: this.$data.newEmailAddress,
|
|
855
1367
|
},
|
|
856
1368
|
},
|
|
857
|
-
this
|
|
1369
|
+
this,
|
|
858
1370
|
));
|
|
859
1371
|
}
|
|
860
1372
|
|
|
861
1373
|
if (this.$data.cancelEmailSecret != null) {
|
|
862
1374
|
const link = `${verifyUrl}?action=cancelchange&id=${encodeURIComponent(
|
|
863
|
-
this.guid
|
|
1375
|
+
this.guid,
|
|
864
1376
|
)}&secret=${encodeURIComponent(this.$data.cancelEmailSecret)}`;
|
|
865
1377
|
success =
|
|
866
1378
|
success &&
|
|
@@ -880,7 +1392,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
880
1392
|
newEmail: this.$data.newEmailAddress ?? this.$data.email,
|
|
881
1393
|
},
|
|
882
1394
|
},
|
|
883
|
-
this
|
|
1395
|
+
this,
|
|
884
1396
|
));
|
|
885
1397
|
}
|
|
886
1398
|
|
|
@@ -893,13 +1405,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
893
1405
|
* @param group The group.
|
|
894
1406
|
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
895
1407
|
*/
|
|
896
|
-
public $addGroup(group:
|
|
897
|
-
|
|
898
|
-
|
|
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 = [];
|
|
899
1413
|
}
|
|
900
|
-
if (!group.$inArray(
|
|
901
|
-
|
|
902
|
-
|
|
1414
|
+
if (!group.$inArray(groups)) {
|
|
1415
|
+
groups.push(group);
|
|
1416
|
+
this.$data.groups = groups;
|
|
1417
|
+
return groups;
|
|
903
1418
|
}
|
|
904
1419
|
return true;
|
|
905
1420
|
}
|
|
@@ -911,17 +1426,23 @@ export default class User extends AbleObject<UserData> {
|
|
|
911
1426
|
* @returns True if the passwords match, otherwise false.
|
|
912
1427
|
*/
|
|
913
1428
|
public $checkPassword(password: string) {
|
|
914
|
-
const tilmeld = this
|
|
1429
|
+
const tilmeld = enforceTilmeld(this);
|
|
915
1430
|
switch (tilmeld.config.pwMethod) {
|
|
916
1431
|
case 'plain':
|
|
917
1432
|
return this.$data.password === password;
|
|
918
1433
|
case 'digest':
|
|
919
|
-
return
|
|
1434
|
+
return (
|
|
1435
|
+
this.$data.password ===
|
|
1436
|
+
crypto.createHash('sha256').update(password).digest('base64')
|
|
1437
|
+
);
|
|
920
1438
|
case 'salt':
|
|
921
1439
|
default:
|
|
922
1440
|
return (
|
|
923
1441
|
this.$data.password ===
|
|
924
|
-
|
|
1442
|
+
crypto
|
|
1443
|
+
.createHash('sha256')
|
|
1444
|
+
.update(password + this.$data.salt)
|
|
1445
|
+
.digest('base64')
|
|
925
1446
|
);
|
|
926
1447
|
}
|
|
927
1448
|
}
|
|
@@ -932,10 +1453,12 @@ export default class User extends AbleObject<UserData> {
|
|
|
932
1453
|
* @param group The group.
|
|
933
1454
|
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
934
1455
|
*/
|
|
935
|
-
public $delGroup(group:
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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) {
|
|
939
1462
|
if (!group.$is(curGroup)) {
|
|
940
1463
|
newGroups.push(curGroup);
|
|
941
1464
|
}
|
|
@@ -952,7 +1475,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
952
1475
|
* @param group The group, or the group's GUID.
|
|
953
1476
|
* @returns True or false.
|
|
954
1477
|
*/
|
|
955
|
-
public $inGroup(group: (
|
|
1478
|
+
public async $inGroup(group: (GroupClass & GroupData) | string) {
|
|
956
1479
|
if (typeof group === 'string') {
|
|
957
1480
|
if (this.$data.group?.guid === group) {
|
|
958
1481
|
return true;
|
|
@@ -975,16 +1498,19 @@ export default class User extends AbleObject<UserData> {
|
|
|
975
1498
|
* @param group The group, or the group's GUID.
|
|
976
1499
|
* @returns True or false.
|
|
977
1500
|
*/
|
|
978
|
-
public $isDescendant(group: (
|
|
1501
|
+
public async $isDescendant(group: (GroupClass & GroupData) | string) {
|
|
979
1502
|
// Check to see if the user is in a descendant group of the given group.
|
|
1503
|
+
await this.$data.group?.$wake();
|
|
980
1504
|
if (
|
|
1505
|
+
this.$data.group != null &&
|
|
981
1506
|
this.$data.group?.cdate != null &&
|
|
982
|
-
this.$data.group
|
|
1507
|
+
(await this.$data.group?.$isDescendant(group))
|
|
983
1508
|
) {
|
|
984
1509
|
return true;
|
|
985
1510
|
}
|
|
1511
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
986
1512
|
for (let curGroup of this.$data.groups ?? []) {
|
|
987
|
-
if (curGroup.$isDescendant(group)) {
|
|
1513
|
+
if (await curGroup.$isDescendant(group)) {
|
|
988
1514
|
return true;
|
|
989
1515
|
}
|
|
990
1516
|
}
|
|
@@ -1000,15 +1526,31 @@ export default class User extends AbleObject<UserData> {
|
|
|
1000
1526
|
public async $changePassword(data: {
|
|
1001
1527
|
newPassword: string;
|
|
1002
1528
|
currentPassword: string;
|
|
1529
|
+
revokeCurrentTokens?: boolean;
|
|
1003
1530
|
}): Promise<{ result: boolean; message: string }> {
|
|
1531
|
+
this.$nymph.config.debugLog(
|
|
1532
|
+
'tilmeld',
|
|
1533
|
+
`Password change request for "${this.$data.username}".`,
|
|
1534
|
+
);
|
|
1004
1535
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
1005
1536
|
return { result: false, message: 'Please specify a password.' };
|
|
1006
1537
|
}
|
|
1007
1538
|
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
1539
|
+
this.$nymph.config.debugLog(
|
|
1540
|
+
'tilmeld',
|
|
1541
|
+
`Incorrect password for change request for "${this.$data.username}".`,
|
|
1542
|
+
);
|
|
1008
1543
|
return { result: false, message: 'Incorrect password.' };
|
|
1009
1544
|
}
|
|
1010
1545
|
this.$data.passwordTemp = data.newPassword;
|
|
1546
|
+
if (data.revokeCurrentTokens) {
|
|
1547
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1548
|
+
}
|
|
1011
1549
|
if (await this.$save()) {
|
|
1550
|
+
if (data.revokeCurrentTokens) {
|
|
1551
|
+
const tilmeld = enforceTilmeld(this);
|
|
1552
|
+
await tilmeld.login(this, true);
|
|
1553
|
+
}
|
|
1012
1554
|
return { result: true, message: 'Your password has been changed.' };
|
|
1013
1555
|
} else {
|
|
1014
1556
|
return { result: false, message: "Couldn't save new password." };
|
|
@@ -1022,7 +1564,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1022
1564
|
* @returns The resulting password or hash which is stored in the entity.
|
|
1023
1565
|
*/
|
|
1024
1566
|
public $password(password: string) {
|
|
1025
|
-
const tilmeld = this
|
|
1567
|
+
const tilmeld = enforceTilmeld(this);
|
|
1026
1568
|
switch (tilmeld.config.pwMethod) {
|
|
1027
1569
|
case 'plain':
|
|
1028
1570
|
delete this.$data.salt;
|
|
@@ -1030,34 +1572,275 @@ export default class User extends AbleObject<UserData> {
|
|
|
1030
1572
|
break;
|
|
1031
1573
|
case 'digest':
|
|
1032
1574
|
delete this.$data.salt;
|
|
1033
|
-
this.$data.password =
|
|
1575
|
+
this.$data.password = crypto
|
|
1576
|
+
.createHash('sha256')
|
|
1577
|
+
.update(password)
|
|
1578
|
+
.digest('base64');
|
|
1034
1579
|
break;
|
|
1035
1580
|
case 'salt':
|
|
1036
1581
|
default:
|
|
1037
1582
|
this.$data.salt = nanoid();
|
|
1038
|
-
this.$data.password =
|
|
1039
|
-
|
|
1040
|
-
|
|
1583
|
+
this.$data.password = crypto
|
|
1584
|
+
.createHash('sha256')
|
|
1585
|
+
.update(password + this.$data.salt)
|
|
1586
|
+
.digest('base64');
|
|
1041
1587
|
break;
|
|
1042
1588
|
}
|
|
1043
1589
|
return this.$data.password;
|
|
1044
1590
|
}
|
|
1045
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
|
+
|
|
1046
1802
|
/**
|
|
1047
1803
|
* Check that a username is valid.
|
|
1048
1804
|
*
|
|
1049
1805
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1050
1806
|
*/
|
|
1051
1807
|
public async $checkUsername(): Promise<{ result: boolean; message: string }> {
|
|
1052
|
-
const tilmeld = this
|
|
1808
|
+
const tilmeld = enforceTilmeld(this);
|
|
1809
|
+
|
|
1810
|
+
this.$nymph.config.debugInfo(
|
|
1811
|
+
'tilmeld',
|
|
1812
|
+
`Username check "${this.$data.username}".`,
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1053
1815
|
if (!tilmeld.config.emailUsernames) {
|
|
1054
|
-
|
|
1055
|
-
|
|
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}`;
|
|
1056
1824
|
}
|
|
1825
|
+
|
|
1057
1826
|
if (
|
|
1058
|
-
|
|
1059
|
-
this.$data.username.length
|
|
1827
|
+
this.$data.username == null ||
|
|
1828
|
+
!this.$data.username.length ||
|
|
1829
|
+
username == null ||
|
|
1830
|
+
!username.length
|
|
1060
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) {
|
|
1061
1844
|
return {
|
|
1062
1845
|
result: false,
|
|
1063
1846
|
message:
|
|
@@ -1068,22 +1851,39 @@ export default class User extends AbleObject<UserData> {
|
|
|
1068
1851
|
}
|
|
1069
1852
|
|
|
1070
1853
|
if (
|
|
1071
|
-
difference(
|
|
1072
|
-
|
|
1073
|
-
tilmeld.config.validChars.split('')
|
|
1074
|
-
).length
|
|
1854
|
+
difference(username.split(''), tilmeld.config.validChars.split(''))
|
|
1855
|
+
.length
|
|
1075
1856
|
) {
|
|
1076
1857
|
return {
|
|
1077
1858
|
result: false,
|
|
1078
1859
|
message: tilmeld.config.validCharsNotice,
|
|
1079
1860
|
};
|
|
1080
1861
|
}
|
|
1081
|
-
if (!tilmeld.config.validRegex.test(
|
|
1862
|
+
if (!tilmeld.config.validRegex.test(username)) {
|
|
1082
1863
|
return {
|
|
1083
1864
|
result: false,
|
|
1084
1865
|
message: tilmeld.config.validRegexNotice,
|
|
1085
1866
|
};
|
|
1086
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
|
+
}
|
|
1087
1887
|
|
|
1088
1888
|
const selector: Selector = {
|
|
1089
1889
|
type: '&',
|
|
@@ -1097,12 +1897,24 @@ export default class User extends AbleObject<UserData> {
|
|
|
1097
1897
|
}
|
|
1098
1898
|
const test = await this.$nymph.getEntity(
|
|
1099
1899
|
{ class: tilmeld.User, skipAc: true },
|
|
1100
|
-
selector
|
|
1900
|
+
selector,
|
|
1101
1901
|
);
|
|
1102
1902
|
if (test != null) {
|
|
1103
1903
|
return { result: false, message: 'That username is taken.' };
|
|
1104
1904
|
}
|
|
1105
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
|
+
|
|
1106
1918
|
return {
|
|
1107
1919
|
result: true,
|
|
1108
1920
|
message:
|
|
@@ -1113,10 +1925,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1113
1925
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
1114
1926
|
return { result: false, message: 'Please specify an email.' };
|
|
1115
1927
|
}
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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) {
|
|
1120
1938
|
return {
|
|
1121
1939
|
result: false,
|
|
1122
1940
|
message:
|
|
@@ -1136,7 +1954,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1136
1954
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1137
1955
|
*/
|
|
1138
1956
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
1139
|
-
const tilmeld = this
|
|
1957
|
+
const tilmeld = enforceTilmeld(this);
|
|
1958
|
+
|
|
1959
|
+
this.$nymph.config.debugInfo(
|
|
1960
|
+
'tilmeld',
|
|
1961
|
+
`Email check "${this.$data.email}".`,
|
|
1962
|
+
);
|
|
1963
|
+
|
|
1140
1964
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
1141
1965
|
if (tilmeld.config.verifyEmail) {
|
|
1142
1966
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -1159,7 +1983,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1159
1983
|
}
|
|
1160
1984
|
const test = await this.$nymph.getEntity(
|
|
1161
1985
|
{ class: tilmeld.User, skipAc: true },
|
|
1162
|
-
selector
|
|
1986
|
+
selector,
|
|
1163
1987
|
);
|
|
1164
1988
|
if (test != null) {
|
|
1165
1989
|
return {
|
|
@@ -1181,7 +2005,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1181
2005
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1182
2006
|
*/
|
|
1183
2007
|
public async $checkPhone(): Promise<{ result: boolean; message: string }> {
|
|
1184
|
-
const tilmeld = this
|
|
2008
|
+
const tilmeld = enforceTilmeld(this);
|
|
2009
|
+
|
|
2010
|
+
this.$nymph.config.debugInfo(
|
|
2011
|
+
'tilmeld',
|
|
2012
|
+
`Phone check "${this.$data.phone}".`,
|
|
2013
|
+
);
|
|
2014
|
+
|
|
1185
2015
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
1186
2016
|
return { result: false, message: 'Please specify a phone number.' };
|
|
1187
2017
|
}
|
|
@@ -1203,7 +2033,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1203
2033
|
}
|
|
1204
2034
|
const test = await this.$nymph.getEntity(
|
|
1205
2035
|
{ class: tilmeld.User, skipAc: true },
|
|
1206
|
-
selector
|
|
2036
|
+
selector,
|
|
1207
2037
|
);
|
|
1208
2038
|
if (test != null) {
|
|
1209
2039
|
return { result: false, message: 'Phone number is in use.' };
|
|
@@ -1220,7 +2050,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1220
2050
|
password: string;
|
|
1221
2051
|
additionalData?: { [k: string]: any };
|
|
1222
2052
|
}): Promise<{ result: boolean; loggedin: boolean; message: string }> {
|
|
1223
|
-
let tilmeld = this
|
|
2053
|
+
let tilmeld = enforceTilmeld(this);
|
|
1224
2054
|
if (!tilmeld.config.allowRegistration) {
|
|
1225
2055
|
return {
|
|
1226
2056
|
result: false,
|
|
@@ -1236,21 +2066,6 @@ export default class User extends AbleObject<UserData> {
|
|
|
1236
2066
|
};
|
|
1237
2067
|
}
|
|
1238
2068
|
|
|
1239
|
-
try {
|
|
1240
|
-
for (let callback of (this.constructor as typeof User)
|
|
1241
|
-
.beforeRegisterCallbacks) {
|
|
1242
|
-
if (callback) {
|
|
1243
|
-
await callback(this, data);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
} catch (e: any) {
|
|
1247
|
-
return {
|
|
1248
|
-
result: false,
|
|
1249
|
-
loggedin: false,
|
|
1250
|
-
message: e.message,
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
2069
|
if (!('password' in data) || !data.password.length) {
|
|
1255
2070
|
return {
|
|
1256
2071
|
result: false,
|
|
@@ -1264,7 +2079,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1264
2079
|
}
|
|
1265
2080
|
|
|
1266
2081
|
this.$password(data.password);
|
|
1267
|
-
if (tilmeld.config.regFields.
|
|
2082
|
+
if (tilmeld.config.regFields.includes('name')) {
|
|
1268
2083
|
this.$data.name =
|
|
1269
2084
|
this.$data.nameFirst +
|
|
1270
2085
|
(this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
|
|
@@ -1281,75 +2096,34 @@ export default class User extends AbleObject<UserData> {
|
|
|
1281
2096
|
const transaction = 'tilmeld-register-' + nanoid();
|
|
1282
2097
|
const nymph = this.$nymph;
|
|
1283
2098
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1284
|
-
this.$
|
|
1285
|
-
tilmeld = this
|
|
2099
|
+
this.$setNymph(tnymph);
|
|
2100
|
+
tilmeld = enforceTilmeld(this);
|
|
2101
|
+
|
|
2102
|
+
let message = '';
|
|
2103
|
+
let loggedin = false;
|
|
1286
2104
|
|
|
1287
2105
|
try {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
generatedPrimaryGroup.groupname = this.$data.username;
|
|
1294
|
-
generatedPrimaryGroup.avatar = this.$data.avatar;
|
|
1295
|
-
generatedPrimaryGroup.name = this.$data.name;
|
|
1296
|
-
generatedPrimaryGroup.email = this.$data.email;
|
|
1297
|
-
const parent = await tnymph.getEntity(
|
|
1298
|
-
{ class: tilmeld.Group },
|
|
1299
|
-
{
|
|
1300
|
-
type: '&',
|
|
1301
|
-
equal: ['defaultPrimary', true],
|
|
1302
|
-
}
|
|
1303
|
-
);
|
|
1304
|
-
if (parent != null) {
|
|
1305
|
-
generatedPrimaryGroup.parent = parent;
|
|
1306
|
-
}
|
|
1307
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
1308
|
-
await tnymph.rollback(transaction);
|
|
1309
|
-
this.$nymph = nymph;
|
|
1310
|
-
return {
|
|
1311
|
-
result: false,
|
|
1312
|
-
loggedin: false,
|
|
1313
|
-
message: 'Error creating primary group for user.',
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
this.$data.group = generatedPrimaryGroup;
|
|
1317
|
-
} else {
|
|
1318
|
-
// Add the default primary.
|
|
1319
|
-
const group = await tnymph.getEntity(
|
|
1320
|
-
{ class: tilmeld.Group },
|
|
1321
|
-
{
|
|
1322
|
-
type: '&',
|
|
1323
|
-
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);
|
|
1324
2111
|
}
|
|
1325
|
-
);
|
|
1326
|
-
if (group != null) {
|
|
1327
|
-
this.$data.group = group;
|
|
1328
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
|
+
};
|
|
1329
2121
|
}
|
|
1330
2122
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
{ class: tilmeld.Group },
|
|
1336
|
-
{
|
|
1337
|
-
type: '&',
|
|
1338
|
-
equal: ['unverifiedSecondary', true],
|
|
1339
|
-
}
|
|
1340
|
-
);
|
|
1341
|
-
} else {
|
|
1342
|
-
// Add the default secondaries.
|
|
1343
|
-
this.$data.groups = await tnymph.getEntities(
|
|
1344
|
-
{ class: tilmeld.Group },
|
|
1345
|
-
{
|
|
1346
|
-
type: '&',
|
|
1347
|
-
equal: ['defaultSecondary', true],
|
|
1348
|
-
}
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
if (tilmeld.config.verifyEmail) {
|
|
2123
|
+
if (
|
|
2124
|
+
tilmeld.config.userFields.includes('email') &&
|
|
2125
|
+
tilmeld.config.verifyEmail
|
|
2126
|
+
) {
|
|
1353
2127
|
// The user will be enabled after verifying their e-mail address.
|
|
1354
2128
|
if (!tilmeld.config.unverifiedAccess) {
|
|
1355
2129
|
this.$data.enabled = false;
|
|
@@ -1358,7 +2132,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1358
2132
|
this.$data.enabled = true;
|
|
1359
2133
|
}
|
|
1360
2134
|
|
|
1361
|
-
// If
|
|
2135
|
+
// If createAdmin is true and there are no other users, grant
|
|
1362
2136
|
// "system/admin".
|
|
1363
2137
|
let madeAdmin = false;
|
|
1364
2138
|
if (tilmeld.config.createAdmin) {
|
|
@@ -1369,6 +2143,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
1369
2143
|
return: 'guid',
|
|
1370
2144
|
});
|
|
1371
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
|
+
}
|
|
1372
2155
|
if (!otherUsers.length) {
|
|
1373
2156
|
this.$grant('system/admin');
|
|
1374
2157
|
this.$data.enabled = true;
|
|
@@ -1376,7 +2159,15 @@ export default class User extends AbleObject<UserData> {
|
|
|
1376
2159
|
}
|
|
1377
2160
|
}
|
|
1378
2161
|
|
|
2162
|
+
this.$nymph.config.debugLog(
|
|
2163
|
+
'tilmeld',
|
|
2164
|
+
`Registering new user "${this.$data.username}".`,
|
|
2165
|
+
);
|
|
1379
2166
|
if (await this.$saveSkipAC()) {
|
|
2167
|
+
this.$nymph.config.debugLog(
|
|
2168
|
+
'tilmeld',
|
|
2169
|
+
`New user registered "${this.$data.username}".`,
|
|
2170
|
+
);
|
|
1380
2171
|
// Send the new user registered email.
|
|
1381
2172
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
1382
2173
|
await tilmeld.config.sendEmail(
|
|
@@ -1395,33 +2186,16 @@ export default class User extends AbleObject<UserData> {
|
|
|
1395
2186
|
userPhone: this.$data.phone,
|
|
1396
2187
|
},
|
|
1397
2188
|
},
|
|
1398
|
-
this
|
|
2189
|
+
this,
|
|
1399
2190
|
);
|
|
1400
2191
|
}
|
|
1401
2192
|
|
|
1402
|
-
let message = '';
|
|
1403
|
-
let loggedin = false;
|
|
1404
|
-
|
|
1405
|
-
// Save the primary group.
|
|
1406
|
-
if (generatedPrimaryGroup != null) {
|
|
1407
|
-
generatedPrimaryGroup.user = this;
|
|
1408
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
1409
|
-
await tnymph.rollback(transaction);
|
|
1410
|
-
this.$nymph = nymph;
|
|
1411
|
-
return {
|
|
1412
|
-
result: false,
|
|
1413
|
-
loggedin: false,
|
|
1414
|
-
message:
|
|
1415
|
-
'Your primary group could not be assigned. Please try again later.',
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
2193
|
// Finish up.
|
|
1421
2194
|
if (
|
|
1422
2195
|
tilmeld.config.verifyEmail &&
|
|
1423
2196
|
!tilmeld.config.unverifiedAccess &&
|
|
1424
|
-
!madeAdmin
|
|
2197
|
+
!madeAdmin &&
|
|
2198
|
+
tilmeld.config.userFields.includes('email')
|
|
1425
2199
|
) {
|
|
1426
2200
|
message +=
|
|
1427
2201
|
`Almost there. An email has been sent to ${this.$data.email} ` +
|
|
@@ -1429,9 +2203,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1429
2203
|
} else if (
|
|
1430
2204
|
tilmeld.config.verifyEmail &&
|
|
1431
2205
|
tilmeld.config.unverifiedAccess &&
|
|
1432
|
-
!madeAdmin
|
|
2206
|
+
!madeAdmin &&
|
|
2207
|
+
tilmeld.config.userFields.includes('email')
|
|
1433
2208
|
) {
|
|
1434
|
-
if (!tilmeld.login(this, true)) {
|
|
2209
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1435
2210
|
throw new Error('An error occurred trying to log you in.');
|
|
1436
2211
|
}
|
|
1437
2212
|
message +=
|
|
@@ -1440,7 +2215,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1440
2215
|
'registration.';
|
|
1441
2216
|
loggedin = true;
|
|
1442
2217
|
} else {
|
|
1443
|
-
if (!tilmeld.login(this, true)) {
|
|
2218
|
+
if (!(await tilmeld.login(this, true))) {
|
|
1444
2219
|
throw new Error('An error occurred trying to log you in.');
|
|
1445
2220
|
}
|
|
1446
2221
|
message += "You're now registered and logged in!";
|
|
@@ -1456,23 +2231,13 @@ export default class User extends AbleObject<UserData> {
|
|
|
1456
2231
|
});
|
|
1457
2232
|
}
|
|
1458
2233
|
}
|
|
1459
|
-
|
|
1460
|
-
try {
|
|
1461
|
-
await tnymph.commit(transaction);
|
|
1462
|
-
this.$nymph = nymph;
|
|
1463
|
-
this.$nymph.tilmeld?.fillSession(this);
|
|
1464
|
-
} catch (e: any) {
|
|
1465
|
-
throw e;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
return {
|
|
1469
|
-
result: true,
|
|
1470
|
-
loggedin,
|
|
1471
|
-
message,
|
|
1472
|
-
};
|
|
1473
2234
|
} else {
|
|
2235
|
+
this.$nymph.config.debugError(
|
|
2236
|
+
'tilmeld',
|
|
2237
|
+
`Error registering new user "${this.$data.username}".`,
|
|
2238
|
+
);
|
|
1474
2239
|
await tnymph.rollback(transaction);
|
|
1475
|
-
this.$nymph
|
|
2240
|
+
this.$setNymph(nymph);
|
|
1476
2241
|
return {
|
|
1477
2242
|
result: false,
|
|
1478
2243
|
loggedin: false,
|
|
@@ -1480,28 +2245,100 @@ export default class User extends AbleObject<UserData> {
|
|
|
1480
2245
|
};
|
|
1481
2246
|
}
|
|
1482
2247
|
} catch (e: any) {
|
|
2248
|
+
this.$nymph.config.debugError(
|
|
2249
|
+
'tilmeld',
|
|
2250
|
+
`Error registering new user "${this.$data.username}": ${e}`,
|
|
2251
|
+
);
|
|
1483
2252
|
await tnymph.rollback(transaction);
|
|
1484
|
-
this.$nymph
|
|
2253
|
+
this.$setNymph(nymph);
|
|
1485
2254
|
throw e;
|
|
1486
2255
|
}
|
|
2256
|
+
|
|
2257
|
+
try {
|
|
2258
|
+
await tnymph.commit(transaction);
|
|
2259
|
+
this.$setNymph(nymph);
|
|
2260
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
2261
|
+
} catch (e: any) {
|
|
2262
|
+
throw e;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
return {
|
|
2266
|
+
result: true,
|
|
2267
|
+
loggedin,
|
|
2268
|
+
message,
|
|
2269
|
+
};
|
|
1487
2270
|
}
|
|
1488
2271
|
|
|
1489
2272
|
public async $save() {
|
|
1490
|
-
let tilmeld = this
|
|
2273
|
+
let tilmeld = enforceTilmeld(this);
|
|
1491
2274
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
1492
2275
|
return false;
|
|
1493
2276
|
}
|
|
1494
2277
|
|
|
1495
2278
|
if (
|
|
2279
|
+
!this.$skipAcWhenSaving &&
|
|
1496
2280
|
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1497
2281
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1498
|
-
this.$
|
|
2282
|
+
this.$data.abilities?.includes('system/admin')
|
|
1499
2283
|
) {
|
|
1500
|
-
throw new
|
|
1501
|
-
"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.",
|
|
1502
2286
|
);
|
|
1503
2287
|
}
|
|
1504
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
|
+
|
|
1505
2342
|
let sendVerification = false;
|
|
1506
2343
|
|
|
1507
2344
|
// Formatting.
|
|
@@ -1520,49 +2357,88 @@ export default class User extends AbleObject<UserData> {
|
|
|
1520
2357
|
this.$data.enabled = !!this.$data.enabled;
|
|
1521
2358
|
|
|
1522
2359
|
// Clear empty values.
|
|
1523
|
-
if (
|
|
2360
|
+
if (
|
|
2361
|
+
this.$data.nameFirst === '' ||
|
|
2362
|
+
!tilmeld.config.userFields.includes('name')
|
|
2363
|
+
) {
|
|
1524
2364
|
delete this.$data.nameFirst;
|
|
1525
2365
|
}
|
|
1526
|
-
if (
|
|
2366
|
+
if (
|
|
2367
|
+
this.$data.nameMiddle === '' ||
|
|
2368
|
+
!tilmeld.config.userFields.includes('name')
|
|
2369
|
+
) {
|
|
1527
2370
|
delete this.$data.nameMiddle;
|
|
1528
2371
|
}
|
|
1529
|
-
if (
|
|
2372
|
+
if (
|
|
2373
|
+
this.$data.nameLast === '' ||
|
|
2374
|
+
!tilmeld.config.userFields.includes('name')
|
|
2375
|
+
) {
|
|
1530
2376
|
delete this.$data.nameLast;
|
|
1531
2377
|
}
|
|
1532
|
-
if (this.$data.name === '') {
|
|
2378
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
1533
2379
|
delete this.$data.name;
|
|
1534
2380
|
}
|
|
1535
|
-
if (
|
|
2381
|
+
if (
|
|
2382
|
+
this.$data.email === '' ||
|
|
2383
|
+
!tilmeld.config.userFields.includes('email')
|
|
2384
|
+
) {
|
|
1536
2385
|
delete this.$data.email;
|
|
1537
2386
|
}
|
|
1538
2387
|
if (this.$data.avatar === '') {
|
|
1539
2388
|
delete this.$data.avatar;
|
|
1540
2389
|
}
|
|
1541
|
-
if (
|
|
2390
|
+
if (
|
|
2391
|
+
this.$data.phone === '' ||
|
|
2392
|
+
!tilmeld.config.userFields.includes('phone')
|
|
2393
|
+
) {
|
|
1542
2394
|
delete this.$data.phone;
|
|
1543
2395
|
}
|
|
1544
|
-
if (
|
|
2396
|
+
if (
|
|
2397
|
+
this.$data.secret === '' ||
|
|
2398
|
+
!tilmeld.config.userFields.includes('email')
|
|
2399
|
+
) {
|
|
1545
2400
|
delete this.$data.secret;
|
|
1546
2401
|
}
|
|
1547
|
-
if (
|
|
2402
|
+
if (
|
|
2403
|
+
this.$data.emailChangeDate === 0 ||
|
|
2404
|
+
!tilmeld.config.userFields.includes('email')
|
|
2405
|
+
) {
|
|
1548
2406
|
delete this.$data.emailChangeDate;
|
|
1549
2407
|
}
|
|
1550
|
-
if (
|
|
2408
|
+
if (
|
|
2409
|
+
this.$data.newEmailSecret === '' ||
|
|
2410
|
+
!tilmeld.config.userFields.includes('email')
|
|
2411
|
+
) {
|
|
1551
2412
|
delete this.$data.newEmailSecret;
|
|
1552
2413
|
}
|
|
1553
|
-
if (
|
|
2414
|
+
if (
|
|
2415
|
+
this.$data.newEmailAddress === '' ||
|
|
2416
|
+
!tilmeld.config.userFields.includes('email')
|
|
2417
|
+
) {
|
|
1554
2418
|
delete this.$data.newEmailAddress;
|
|
1555
2419
|
}
|
|
1556
|
-
if (
|
|
2420
|
+
if (
|
|
2421
|
+
this.$data.cancelEmailSecret === '' ||
|
|
2422
|
+
!tilmeld.config.userFields.includes('email')
|
|
2423
|
+
) {
|
|
1557
2424
|
delete this.$data.cancelEmailSecret;
|
|
1558
2425
|
}
|
|
1559
|
-
if (
|
|
2426
|
+
if (
|
|
2427
|
+
this.$data.cancelEmailAddress === '' ||
|
|
2428
|
+
!tilmeld.config.userFields.includes('email')
|
|
2429
|
+
) {
|
|
1560
2430
|
delete this.$data.cancelEmailAddress;
|
|
1561
2431
|
}
|
|
1562
|
-
if (
|
|
2432
|
+
if (
|
|
2433
|
+
this.$data.recoverSecret === '' ||
|
|
2434
|
+
!tilmeld.config.userFields.includes('email')
|
|
2435
|
+
) {
|
|
1563
2436
|
delete this.$data.recoverSecret;
|
|
1564
2437
|
}
|
|
1565
|
-
if (
|
|
2438
|
+
if (
|
|
2439
|
+
this.$data.recoverSecretDate === 0 ||
|
|
2440
|
+
!tilmeld.config.userFields.includes('email')
|
|
2441
|
+
) {
|
|
1566
2442
|
delete this.$data.recoverSecretDate;
|
|
1567
2443
|
}
|
|
1568
2444
|
if (this.$data.password === '') {
|
|
@@ -1580,7 +2456,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1580
2456
|
if (!unCheck.result) {
|
|
1581
2457
|
throw new BadUsernameError(unCheck.message);
|
|
1582
2458
|
}
|
|
1583
|
-
if (
|
|
2459
|
+
if (
|
|
2460
|
+
!tilmeld.config.emailUsernames &&
|
|
2461
|
+
tilmeld.config.userFields.includes('email')
|
|
2462
|
+
) {
|
|
1584
2463
|
const emCheck = await this.$checkEmail();
|
|
1585
2464
|
if (!emCheck.result) {
|
|
1586
2465
|
throw new BadEmailError(emCheck.message);
|
|
@@ -1588,7 +2467,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1588
2467
|
}
|
|
1589
2468
|
|
|
1590
2469
|
// Email changes.
|
|
1591
|
-
if (
|
|
2470
|
+
if (
|
|
2471
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
2472
|
+
tilmeld.config.userFields.includes('email')
|
|
2473
|
+
) {
|
|
1592
2474
|
// The user isn't an admin, so email address changes should contain some
|
|
1593
2475
|
// security measures.
|
|
1594
2476
|
if (tilmeld.config.verifyEmail) {
|
|
@@ -1596,7 +2478,7 @@ export default class User extends AbleObject<UserData> {
|
|
|
1596
2478
|
if (this.guid == null) {
|
|
1597
2479
|
// If this is the first user, they'll be made an admin, so they don't
|
|
1598
2480
|
// need to verify.
|
|
1599
|
-
if (
|
|
2481
|
+
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1600
2482
|
this.$data.secret = nanoid();
|
|
1601
2483
|
sendVerification = true;
|
|
1602
2484
|
}
|
|
@@ -1616,10 +2498,10 @@ export default class User extends AbleObject<UserData> {
|
|
|
1616
2498
|
new Date(
|
|
1617
2499
|
strtotime(
|
|
1618
2500
|
'+' + tilmeld.config.emailRateLimit,
|
|
1619
|
-
Math.floor(this.$data.emailChangeDate / 1000)
|
|
1620
|
-
) * 1000
|
|
2501
|
+
Math.floor(this.$data.emailChangeDate / 1000),
|
|
2502
|
+
) * 1000,
|
|
1621
2503
|
).toString() +
|
|
1622
|
-
' to change your email address again.'
|
|
2504
|
+
' to change your email address again.',
|
|
1623
2505
|
);
|
|
1624
2506
|
} else {
|
|
1625
2507
|
if (
|
|
@@ -1674,51 +2556,157 @@ export default class User extends AbleObject<UserData> {
|
|
|
1674
2556
|
throw new BadDataError('A password is required.');
|
|
1675
2557
|
}
|
|
1676
2558
|
|
|
1677
|
-
try {
|
|
1678
|
-
tilmeld.config.validatorUser(this);
|
|
1679
|
-
} catch (e: any) {
|
|
1680
|
-
throw new BadDataError(e?.message);
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
2559
|
// Start transaction.
|
|
1684
|
-
const transaction = 'tilmeld-save-' + nanoid();
|
|
2560
|
+
const transaction = 'tilmeld-save-user-' + (this.guid || nanoid());
|
|
1685
2561
|
const nymph = this.$nymph;
|
|
1686
2562
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1687
|
-
this.$
|
|
1688
|
-
tilmeld = this
|
|
2563
|
+
this.$setNymph(tnymph);
|
|
2564
|
+
tilmeld = enforceTilmeld(this);
|
|
2565
|
+
const Group = tnymph.getEntityClass(GroupClass);
|
|
1689
2566
|
|
|
1690
|
-
|
|
1691
|
-
this.$data.group
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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)) {
|
|
1696
2609
|
// Update the user's generated primary group.
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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);
|
|
1703
2678
|
} catch (e: any) {
|
|
1704
|
-
|
|
1705
|
-
this.$nymph = nymph;
|
|
1706
|
-
throw e;
|
|
2679
|
+
throw new BadDataError(e?.message);
|
|
1707
2680
|
}
|
|
2681
|
+
} catch (e: any) {
|
|
2682
|
+
await tnymph.rollback(transaction);
|
|
2683
|
+
this.$setNymph(nymph);
|
|
2684
|
+
throw e;
|
|
1708
2685
|
}
|
|
1709
2686
|
|
|
1710
|
-
let ret
|
|
2687
|
+
let ret = false;
|
|
1711
2688
|
let preGuid = this.guid;
|
|
1712
2689
|
let preCdate = this.cdate;
|
|
1713
2690
|
let preMdate = this.mdate;
|
|
1714
2691
|
|
|
1715
2692
|
try {
|
|
2693
|
+
for (let callback of (this.constructor as typeof User)
|
|
2694
|
+
.beforeSaveCallbacks) {
|
|
2695
|
+
if (callback) {
|
|
2696
|
+
await callback(this);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
1716
2700
|
ret = await super.$save();
|
|
1717
2701
|
} catch (e: any) {
|
|
1718
2702
|
await tnymph.rollback(transaction);
|
|
1719
|
-
this
|
|
2703
|
+
this.guid = preGuid;
|
|
2704
|
+
this.cdate = preCdate;
|
|
2705
|
+
this.mdate = preMdate;
|
|
2706
|
+
this.$setNymph(nymph);
|
|
1720
2707
|
throw e;
|
|
1721
2708
|
}
|
|
2709
|
+
|
|
1722
2710
|
if (ret) {
|
|
1723
2711
|
if (sendVerification) {
|
|
1724
2712
|
// The email has changed, so send a new verification email.
|
|
@@ -1727,23 +2715,48 @@ export default class User extends AbleObject<UserData> {
|
|
|
1727
2715
|
this.guid = preGuid;
|
|
1728
2716
|
this.cdate = preCdate;
|
|
1729
2717
|
this.mdate = preMdate;
|
|
1730
|
-
this.$nymph
|
|
2718
|
+
this.$setNymph(nymph);
|
|
1731
2719
|
throw new Error("Couldn't send verification email.");
|
|
1732
2720
|
}
|
|
1733
2721
|
}
|
|
1734
2722
|
|
|
1735
2723
|
this.$descendantGroups = undefined;
|
|
1736
2724
|
this.$gatekeeperCache = undefined;
|
|
1737
|
-
await tnymph.commit(transaction);
|
|
1738
2725
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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;
|
|
1742
2740
|
}
|
|
2741
|
+
|
|
2742
|
+
ret = await tnymph.commit(transaction);
|
|
1743
2743
|
} else {
|
|
1744
2744
|
await tnymph.rollback(transaction);
|
|
1745
2745
|
}
|
|
1746
|
-
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
|
+
|
|
1747
2760
|
return ret;
|
|
1748
2761
|
}
|
|
1749
2762
|
|
|
@@ -1764,53 +2777,167 @@ export default class User extends AbleObject<UserData> {
|
|
|
1764
2777
|
}
|
|
1765
2778
|
|
|
1766
2779
|
public async $delete() {
|
|
1767
|
-
const tilmeld = this
|
|
1768
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1769
|
-
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
|
+
);
|
|
1770
2785
|
}
|
|
1771
2786
|
if (
|
|
2787
|
+
!this.$skipAcWhenDeleting &&
|
|
1772
2788
|
!tilmeld.gatekeeper('system/admin') &&
|
|
1773
|
-
this.$
|
|
2789
|
+
this.$data.abilities?.includes('system/admin')
|
|
1774
2790
|
) {
|
|
1775
|
-
throw new
|
|
1776
|
-
"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.",
|
|
1777
2793
|
);
|
|
1778
2794
|
}
|
|
1779
|
-
if (
|
|
1780
|
-
|
|
2795
|
+
if (this.$data.username !== this.$originalUsername) {
|
|
2796
|
+
throw new AccessControlError(
|
|
2797
|
+
"You can't delete a user while switching their username.",
|
|
2798
|
+
);
|
|
1781
2799
|
}
|
|
1782
|
-
|
|
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;
|
|
1783
2890
|
}
|
|
1784
2891
|
|
|
1785
2892
|
public static on<T extends EventType>(
|
|
1786
2893
|
event: T,
|
|
1787
|
-
callback: T extends '
|
|
1788
|
-
?
|
|
1789
|
-
: T extends '
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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,
|
|
1800
2917
|
) {
|
|
1801
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
1802
|
-
? '
|
|
1803
|
-
: T extends '
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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;
|
|
1814
2941
|
if (!(prop in this)) {
|
|
1815
2942
|
throw new Error('Invalid event type.');
|
|
1816
2943
|
}
|
|
@@ -1821,33 +2948,53 @@ export default class User extends AbleObject<UserData> {
|
|
|
1821
2948
|
|
|
1822
2949
|
public static off<T extends EventType>(
|
|
1823
2950
|
event: T,
|
|
1824
|
-
callback: T extends '
|
|
1825
|
-
?
|
|
1826
|
-
: T extends '
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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,
|
|
1837
2974
|
) {
|
|
1838
|
-
const prop = (event + 'Callbacks') as T extends '
|
|
1839
|
-
? '
|
|
1840
|
-
: T extends '
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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;
|
|
1851
2998
|
if (!(prop in this)) {
|
|
1852
2999
|
return false;
|
|
1853
3000
|
}
|