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