@nymphjs/tilmeld 1.0.0-beta.9 → 1.0.0-beta.91
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 +414 -0
- package/README.md +15 -2
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +18 -6
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +181 -15
- package/dist/Group.js +551 -192
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +83 -13
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +241 -12
- package/dist/Tilmeld.js +636 -254
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +2 -2
- package/dist/Tilmeld.types.js +1 -2
- package/dist/User.d.ts +395 -22
- package/dist/User.js +1316 -365
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +132 -13
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +287 -7
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +188 -141
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +18 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -6
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -6
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -6
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -6
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
- package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
- package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
- package/dist/errors/index.d.ts +6 -6
- package/dist/errors/index.js +6 -22
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +16 -35
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.js +13 -18
- package/dist/tilmeldJoiProps.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +34 -31
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +104 -6
- package/src/Group.ts +661 -226
- package/src/Tilmeld.ts +420 -254
- package/src/Tilmeld.types.ts +2 -2
- package/src/User.test.ts +163 -6
- package/src/User.ts +1557 -454
- package/src/conf/d.ts +63 -17
- package/src/conf/defaults.ts +162 -109
- package/src/conf/index.ts +2 -2
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/errors/index.ts +6 -6
- package/src/index.ts +12 -10
- package/src/tilmeldJoiProps.ts +11 -11
- package/src/type/locutus.d.ts +3 -0
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
package/dist/User.js
CHANGED
|
@@ -1,20 +1,121 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { humanSecret, nanoid } from '@nymphjs/guid';
|
|
3
|
+
import strtotime from 'locutus/php/datetime/strtotime.js';
|
|
4
|
+
import { difference, xor } from 'lodash-es';
|
|
5
|
+
import { TOTP, Secret } from 'otpauth';
|
|
6
|
+
import { toDataURL } from 'qrcode';
|
|
7
|
+
import { splitn } from '@sciactive/splitn';
|
|
8
|
+
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
9
|
+
import AbleObject from './AbleObject.js';
|
|
10
|
+
import GroupClass from './Group.js';
|
|
11
|
+
import { AccessControlError, BadDataError, BadEmailError, BadUsernameError, EmailChangeRateLimitExceededError, } from './errors/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* A user data model.
|
|
14
|
+
*
|
|
15
|
+
* Written by Hunter Perrin for SciActive.
|
|
16
|
+
*
|
|
17
|
+
* @author Hunter Perrin <hperrin@gmail.com>
|
|
18
|
+
* @copyright SciActive Inc
|
|
19
|
+
* @see http://nymph.io/
|
|
20
|
+
*/
|
|
21
|
+
export default class User extends AbleObject {
|
|
22
|
+
/**
|
|
23
|
+
* The instance of Tilmeld to use for queries.
|
|
24
|
+
*/
|
|
25
|
+
static ETYPE = 'tilmeld_user';
|
|
26
|
+
static class = 'User';
|
|
27
|
+
static checkUsernameCallbacks = [];
|
|
28
|
+
static beforeRegisterCallbacks = [];
|
|
29
|
+
static afterRegisterCallbacks = [];
|
|
30
|
+
static beforeSaveCallbacks = [];
|
|
31
|
+
static afterSaveCallbacks = [];
|
|
32
|
+
static beforeLoginCallbacks = [];
|
|
33
|
+
static afterLoginCallbacks = [];
|
|
34
|
+
static beforeLogoutCallbacks = [];
|
|
35
|
+
static afterLogoutCallbacks = [];
|
|
36
|
+
static beforeDeleteCallbacks = [];
|
|
37
|
+
static afterDeleteCallbacks = [];
|
|
38
|
+
static DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
39
|
+
'$checkUsername',
|
|
40
|
+
'$checkEmail',
|
|
41
|
+
'$checkPhone',
|
|
42
|
+
'$getAvatar',
|
|
43
|
+
'$register',
|
|
44
|
+
];
|
|
45
|
+
static DEFAULT_PRIVATE_DATA = [
|
|
46
|
+
// The things that are commented are there to show what is _not_ private.
|
|
47
|
+
'abilities',
|
|
48
|
+
// 'username',
|
|
49
|
+
// 'nameFirst',
|
|
50
|
+
// 'nameMiddle',
|
|
51
|
+
// 'nameLast',
|
|
52
|
+
// 'name',
|
|
53
|
+
'email',
|
|
54
|
+
// 'avatar',
|
|
55
|
+
'phone',
|
|
56
|
+
'group',
|
|
57
|
+
'groups',
|
|
58
|
+
'inheritAbilities',
|
|
59
|
+
'enabled',
|
|
60
|
+
'secret',
|
|
61
|
+
'emailChangeDate',
|
|
62
|
+
'newEmailSecret',
|
|
63
|
+
'newEmailAddress',
|
|
64
|
+
'cancelEmailSecret',
|
|
65
|
+
'cancelEmailAddress',
|
|
66
|
+
'recoverSecret',
|
|
67
|
+
'recoverSecretDate',
|
|
68
|
+
'salt',
|
|
69
|
+
'password',
|
|
70
|
+
'passwordTemp',
|
|
71
|
+
'revokeTokenDate',
|
|
72
|
+
'totpSecret',
|
|
73
|
+
];
|
|
74
|
+
static DEFAULT_ALLOWLIST_DATA = [];
|
|
75
|
+
$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
76
|
+
static clientEnabledStaticMethods = [
|
|
77
|
+
'current',
|
|
78
|
+
'loginUser',
|
|
79
|
+
'sendRecovery',
|
|
80
|
+
'recover',
|
|
81
|
+
'getClientConfig',
|
|
82
|
+
'getDomainUsers',
|
|
83
|
+
];
|
|
84
|
+
$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
85
|
+
static searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
86
|
+
$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
87
|
+
$allowlistTags = [];
|
|
88
|
+
/**
|
|
89
|
+
* Gatekeeper ability cache.
|
|
90
|
+
*
|
|
91
|
+
* Gatekeeper will cache the user's abilities that it calculates, so it can
|
|
92
|
+
* check faster if that user has been checked before.
|
|
93
|
+
*/
|
|
94
|
+
$gatekeeperCache;
|
|
95
|
+
/**
|
|
96
|
+
* This should only be used by the backend.
|
|
97
|
+
*/
|
|
98
|
+
$skipAcWhenSaving = false;
|
|
99
|
+
/**
|
|
100
|
+
* This should only be used by the backend.
|
|
101
|
+
*/
|
|
102
|
+
$skipAcWhenDeleting = false;
|
|
103
|
+
/**
|
|
104
|
+
* The user's group descendants.
|
|
105
|
+
*/
|
|
106
|
+
$descendantGroups;
|
|
107
|
+
/**
|
|
108
|
+
* Used to save the current email address to send verification if it changes
|
|
109
|
+
* from the frontend.
|
|
110
|
+
*
|
|
111
|
+
* If you are changing a user's email address and want to bypass email
|
|
112
|
+
* verification, don't set this.
|
|
113
|
+
*/
|
|
114
|
+
$originalEmail;
|
|
115
|
+
/**
|
|
116
|
+
* Used to save the current username for domain admin permissions.
|
|
117
|
+
*/
|
|
118
|
+
$originalUsername;
|
|
18
119
|
static async factoryUsername(username) {
|
|
19
120
|
const entity = new this();
|
|
20
121
|
if (username != null) {
|
|
@@ -30,32 +131,22 @@ class User extends AbleObject_1.default {
|
|
|
30
131
|
}
|
|
31
132
|
return entity;
|
|
32
133
|
}
|
|
33
|
-
static factorySync(guid) {
|
|
34
|
-
return super.factorySync(guid);
|
|
35
|
-
}
|
|
36
|
-
constructor(guid) {
|
|
37
|
-
super(guid);
|
|
38
|
-
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
39
|
-
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
40
|
-
this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
41
|
-
this.$allowlistTags = [];
|
|
42
|
-
this.$skipAcWhenSaving = false;
|
|
43
|
-
if (this.guid == null) {
|
|
44
|
-
this.$data.enabled = true;
|
|
45
|
-
this.$data.abilities = [];
|
|
46
|
-
this.$data.groups = [];
|
|
47
|
-
this.$data.inheritAbilities = true;
|
|
48
|
-
this.$updateDataProtection();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
134
|
static current(returnObjectIfNotExist) {
|
|
52
|
-
|
|
135
|
+
const tilmeld = enforceTilmeld(this);
|
|
136
|
+
if (tilmeld.currentUser == null) {
|
|
53
137
|
return returnObjectIfNotExist ? this.factorySync() : null;
|
|
54
138
|
}
|
|
55
|
-
return
|
|
139
|
+
return tilmeld.currentUser;
|
|
56
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Send an account recovery link.
|
|
143
|
+
*
|
|
144
|
+
* @param data The input data from the client.
|
|
145
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
146
|
+
*/
|
|
57
147
|
static async sendRecovery(data) {
|
|
58
|
-
|
|
148
|
+
const tilmeld = enforceTilmeld(this);
|
|
149
|
+
if (!tilmeld.config.pwRecovery) {
|
|
59
150
|
return {
|
|
60
151
|
result: false,
|
|
61
152
|
message: 'Account recovery is not allowed.',
|
|
@@ -63,9 +154,9 @@ class User extends AbleObject_1.default {
|
|
|
63
154
|
}
|
|
64
155
|
let user;
|
|
65
156
|
const options = {};
|
|
66
|
-
if (!
|
|
67
|
-
|
|
68
|
-
const getUser = await this.nymph.getEntity({ class:
|
|
157
|
+
if (!tilmeld.config.emailUsernames && data.recoveryType === 'username') {
|
|
158
|
+
// Create a username recovery email.
|
|
159
|
+
const getUser = await this.nymph.getEntity({ class: tilmeld.User, skipAc: true }, {
|
|
69
160
|
type: '&',
|
|
70
161
|
ilike: ['email', data.account.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
71
162
|
});
|
|
@@ -75,6 +166,7 @@ class User extends AbleObject_1.default {
|
|
|
75
166
|
message: 'Requested account is not accessible.',
|
|
76
167
|
};
|
|
77
168
|
}
|
|
169
|
+
// Send the recovery email.
|
|
78
170
|
user = getUser;
|
|
79
171
|
options.template = 'RecoverUsername';
|
|
80
172
|
options.message = {
|
|
@@ -86,18 +178,21 @@ class User extends AbleObject_1.default {
|
|
|
86
178
|
options.locals = {};
|
|
87
179
|
}
|
|
88
180
|
else if (data.recoveryType === 'password') {
|
|
89
|
-
|
|
181
|
+
// Create a password recovery email.
|
|
182
|
+
const getUser = await tilmeld.User.factoryUsername(data.account);
|
|
90
183
|
if (getUser.guid == null) {
|
|
91
184
|
return {
|
|
92
185
|
result: false,
|
|
93
186
|
message: 'Requested account is not accessible.',
|
|
94
187
|
};
|
|
95
188
|
}
|
|
96
|
-
|
|
189
|
+
// Create a unique secret.
|
|
190
|
+
getUser.recoverSecret = humanSecret();
|
|
97
191
|
getUser.recoverSecretDate = Date.now();
|
|
98
192
|
if (!(await getUser.$saveSkipAC())) {
|
|
99
193
|
return { result: false, message: "Couldn't save recovery secret." };
|
|
100
194
|
}
|
|
195
|
+
// Send the recovery email.
|
|
101
196
|
user = getUser;
|
|
102
197
|
options.template = 'RecoverPassword';
|
|
103
198
|
options.message = {
|
|
@@ -108,13 +203,15 @@ class User extends AbleObject_1.default {
|
|
|
108
203
|
};
|
|
109
204
|
options.locals = {
|
|
110
205
|
recoverCode: getUser.recoverSecret,
|
|
111
|
-
timeLimit:
|
|
206
|
+
timeLimit: tilmeld.config.pwRecoveryTimeLimit,
|
|
112
207
|
};
|
|
113
208
|
}
|
|
114
209
|
else {
|
|
115
210
|
return { result: false, message: 'Invalid recovery type.' };
|
|
116
211
|
}
|
|
117
|
-
|
|
212
|
+
this.nymph.config.debugLog('tilmeld', `User account recovery (${options.template}) request for "${user.username}".`);
|
|
213
|
+
// Send the email.
|
|
214
|
+
if (await tilmeld.config.sendEmail(tilmeld, options, user)) {
|
|
118
215
|
return {
|
|
119
216
|
result: true,
|
|
120
217
|
message: "We've sent an email to your registered address. Please " +
|
|
@@ -125,18 +222,25 @@ class User extends AbleObject_1.default {
|
|
|
125
222
|
return { result: false, message: "Couldn't send recovery email." };
|
|
126
223
|
}
|
|
127
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Recover account details.
|
|
227
|
+
*
|
|
228
|
+
* @param data The input data from the client.
|
|
229
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
230
|
+
*/
|
|
128
231
|
static async recover(data) {
|
|
129
|
-
|
|
232
|
+
const tilmeld = enforceTilmeld(this);
|
|
233
|
+
if (!tilmeld.config.pwRecovery) {
|
|
130
234
|
return {
|
|
131
235
|
result: false,
|
|
132
236
|
message: 'Account recovery is not allowed.',
|
|
133
237
|
};
|
|
134
238
|
}
|
|
135
|
-
const user = await
|
|
239
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
136
240
|
if (user.guid == null ||
|
|
137
241
|
user.recoverSecret == null ||
|
|
138
242
|
data.secret !== user.recoverSecret ||
|
|
139
|
-
(
|
|
243
|
+
strtotime('+' + tilmeld.config.pwRecoveryTimeLimit, Math.floor((user.recoverSecretDate ?? 0) / 1000)) *
|
|
140
244
|
1000 <
|
|
141
245
|
Date.now()) {
|
|
142
246
|
return {
|
|
@@ -147,6 +251,7 @@ class User extends AbleObject_1.default {
|
|
|
147
251
|
if (!('password' in data) || !data.password.length) {
|
|
148
252
|
return { result: false, message: 'Password cannot be empty.' };
|
|
149
253
|
}
|
|
254
|
+
this.nymph.config.debugLog('tilmeld', `User account recovery for "${user.username}".`);
|
|
150
255
|
user.$password(data.password);
|
|
151
256
|
delete user.recoverSecret;
|
|
152
257
|
delete user.recoverSecretDate;
|
|
@@ -161,22 +266,25 @@ class User extends AbleObject_1.default {
|
|
|
161
266
|
}
|
|
162
267
|
}
|
|
163
268
|
static getClientConfig() {
|
|
269
|
+
const tilmeld = enforceTilmeld(this);
|
|
164
270
|
return {
|
|
165
|
-
regFields:
|
|
166
|
-
userFields:
|
|
167
|
-
emailUsernames:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
271
|
+
regFields: tilmeld.config.regFields,
|
|
272
|
+
userFields: tilmeld.config.userFields,
|
|
273
|
+
emailUsernames: tilmeld.config.emailUsernames,
|
|
274
|
+
domainSupport: tilmeld.config.domainSupport,
|
|
275
|
+
allowRegistration: tilmeld.config.allowRegistration,
|
|
276
|
+
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
277
|
+
pwRecovery: tilmeld.config.pwRecovery,
|
|
278
|
+
verifyEmail: tilmeld.config.verifyEmail,
|
|
279
|
+
unverifiedAccess: tilmeld.config.unverifiedAccess,
|
|
173
280
|
};
|
|
174
281
|
}
|
|
175
282
|
static async loginUser(data) {
|
|
283
|
+
const tilmeld = enforceTilmeld(this);
|
|
176
284
|
if (!('username' in data) || !data.username.length) {
|
|
177
285
|
return { result: false, message: 'Incorrect login/password.' };
|
|
178
286
|
}
|
|
179
|
-
const user = await
|
|
287
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
180
288
|
const result = await user.$login(data);
|
|
181
289
|
if (result.result) {
|
|
182
290
|
user.$updateDataProtection();
|
|
@@ -184,20 +292,113 @@ class User extends AbleObject_1.default {
|
|
|
184
292
|
}
|
|
185
293
|
return result;
|
|
186
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Get the users in a domain. This is only accessible to the domain's admins.
|
|
297
|
+
*/
|
|
298
|
+
static async getDomainUsers(domain, options) {
|
|
299
|
+
const tilmeld = enforceTilmeld(this);
|
|
300
|
+
if (!tilmeld.config.domainSupport) {
|
|
301
|
+
throw new AccessControlError('Domain support is not enabled.');
|
|
302
|
+
}
|
|
303
|
+
if (!domain) {
|
|
304
|
+
throw new BadDataError('Domain is required.');
|
|
305
|
+
}
|
|
306
|
+
domain = domain.toLowerCase();
|
|
307
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
308
|
+
throw new BadDataError(tilmeld.config.validDomainRegexNotice);
|
|
309
|
+
}
|
|
310
|
+
if (!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
311
|
+
throw new AccessControlError('You do not have the authority to manage this domain.');
|
|
312
|
+
}
|
|
313
|
+
return await this.nymph.getEntities({
|
|
314
|
+
class: this.nymph.getEntityClass(User),
|
|
315
|
+
...(options?.limit != null
|
|
316
|
+
? {
|
|
317
|
+
limit: options.limit,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
...(options?.offset != null
|
|
321
|
+
? {
|
|
322
|
+
offset: options.offset,
|
|
323
|
+
}
|
|
324
|
+
: {}),
|
|
325
|
+
...(options?.sort != null
|
|
326
|
+
? {
|
|
327
|
+
sort: options.sort,
|
|
328
|
+
}
|
|
329
|
+
: {}),
|
|
330
|
+
...(options?.reverse != null
|
|
331
|
+
? {
|
|
332
|
+
reverse: options.reverse,
|
|
333
|
+
}
|
|
334
|
+
: {}),
|
|
335
|
+
}, {
|
|
336
|
+
type: '&',
|
|
337
|
+
like: ['username', `%@${domain.replace(/([\\%_])/g, (s) => `\\${s}`)}`],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
constructor() {
|
|
341
|
+
super();
|
|
342
|
+
// Defaults.
|
|
343
|
+
this.$data.enabled = true;
|
|
344
|
+
this.$data.abilities = [];
|
|
345
|
+
this.$data.groups = [];
|
|
346
|
+
this.$data.inheritAbilities = true;
|
|
347
|
+
this.$updateDataProtection();
|
|
348
|
+
}
|
|
349
|
+
$setNymph(nymph) {
|
|
350
|
+
this.$nymph = nymph;
|
|
351
|
+
if (!this.$asleep()) {
|
|
352
|
+
if (this.$data.group && this.$data.group.$nymph !== nymph) {
|
|
353
|
+
this.$data.group.$setNymph(nymph);
|
|
354
|
+
}
|
|
355
|
+
for (const group of this.$data.groups ?? []) {
|
|
356
|
+
if (group && group.$nymph !== nymph) {
|
|
357
|
+
group.$setNymph(nymph);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async $getUniques() {
|
|
363
|
+
const tilmeld = enforceTilmeld(this);
|
|
364
|
+
const uniques = [`u:${this.$data.username}`];
|
|
365
|
+
if (!tilmeld.config.emailUsernames &&
|
|
366
|
+
tilmeld.config.userFields.includes('email')) {
|
|
367
|
+
uniques.push(`e:${this.$data.email}`);
|
|
368
|
+
if (this.$data.newEmailAddress != null) {
|
|
369
|
+
uniques.push(`e:${this.$data.newEmailAddress}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return uniques;
|
|
373
|
+
}
|
|
187
374
|
async $login(data) {
|
|
188
|
-
const tilmeld = this
|
|
375
|
+
const tilmeld = enforceTilmeld(this);
|
|
189
376
|
if (this.guid == null) {
|
|
190
377
|
return { result: false, message: 'Incorrect login/password.' };
|
|
191
378
|
}
|
|
192
379
|
if (!this.$data.enabled) {
|
|
193
380
|
return { result: false, message: 'This user is disabled.' };
|
|
194
381
|
}
|
|
195
|
-
if (this.$gatekeeper()) {
|
|
382
|
+
if (await this.$gatekeeper()) {
|
|
196
383
|
return { result: true, message: 'You are already logged in.' };
|
|
197
384
|
}
|
|
198
385
|
if (!this.$checkPassword(data.password)) {
|
|
199
386
|
return { result: false, message: 'Incorrect login/password.' };
|
|
200
387
|
}
|
|
388
|
+
if (this.$data.totpSecret != null) {
|
|
389
|
+
if (data.code == null ||
|
|
390
|
+
typeof data.code !== 'string' ||
|
|
391
|
+
data.code.length !== 6) {
|
|
392
|
+
return {
|
|
393
|
+
result: false,
|
|
394
|
+
needTOTP: true,
|
|
395
|
+
message: 'You need to provide a 2FA code.',
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (!this.$checkTOTPCode(data.code)) {
|
|
399
|
+
return { result: false, message: 'Incorrect 2FA code.' };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
201
402
|
try {
|
|
202
403
|
for (let callback of this.constructor
|
|
203
404
|
.beforeLoginCallbacks) {
|
|
@@ -212,7 +413,9 @@ class User extends AbleObject_1.default {
|
|
|
212
413
|
message: e.message,
|
|
213
414
|
};
|
|
214
415
|
}
|
|
215
|
-
|
|
416
|
+
this.$nymph.config.debugLog('tilmeld', `User login for "${this.$data.username}".`);
|
|
417
|
+
// Authentication was successful, attempt to login.
|
|
418
|
+
if (!(await tilmeld.login(this, true))) {
|
|
216
419
|
return { result: false, message: 'Incorrect login/password.' };
|
|
217
420
|
}
|
|
218
421
|
for (let callback of this.constructor
|
|
@@ -221,10 +424,73 @@ class User extends AbleObject_1.default {
|
|
|
221
424
|
await callback(this);
|
|
222
425
|
}
|
|
223
426
|
}
|
|
427
|
+
// Login was successful.
|
|
224
428
|
return { result: true, message: 'You are logged in.' };
|
|
225
429
|
}
|
|
430
|
+
async $switchUser(data) {
|
|
431
|
+
const tilmeld = enforceTilmeld(this);
|
|
432
|
+
if (this.guid == null) {
|
|
433
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
434
|
+
}
|
|
435
|
+
let user = tilmeld.User.current();
|
|
436
|
+
if (!user || user.guid == null) {
|
|
437
|
+
return { result: false, message: "You're not logged in." };
|
|
438
|
+
}
|
|
439
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
440
|
+
if (isCurrentUser) {
|
|
441
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
442
|
+
}
|
|
443
|
+
const toAbilities = this.$data.abilities || [];
|
|
444
|
+
const fromAbilities = user.abilities || [];
|
|
445
|
+
if (!fromAbilities.includes('tilmeld/switch') &&
|
|
446
|
+
!fromAbilities.includes('system/admin')) {
|
|
447
|
+
return {
|
|
448
|
+
result: false,
|
|
449
|
+
message: "You don't have permission to switch users.",
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (toAbilities.includes('system/admin') ||
|
|
453
|
+
toAbilities.includes('tilmeld/admin')) {
|
|
454
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
for (let callback of this.constructor
|
|
458
|
+
.beforeLoginCallbacks) {
|
|
459
|
+
if (callback) {
|
|
460
|
+
await callback(this, {
|
|
461
|
+
username: this.$data.username || '',
|
|
462
|
+
password: '',
|
|
463
|
+
...(data || {}),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
return {
|
|
470
|
+
result: false,
|
|
471
|
+
message: e.message,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
this.$nymph.config.debugLog('tilmeld', `User login switch for "${user.username}" to "${this.$data.username}".`);
|
|
475
|
+
// Authentication was successful, attempt to login.
|
|
476
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
477
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
478
|
+
}
|
|
479
|
+
for (let callback of this.constructor
|
|
480
|
+
.afterLoginCallbacks) {
|
|
481
|
+
if (callback) {
|
|
482
|
+
await callback(this);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Login was successful.
|
|
486
|
+
return { result: true, message: 'You are logged in.' };
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Log a user out of the system.
|
|
490
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
491
|
+
*/
|
|
226
492
|
async $logout() {
|
|
227
|
-
const tilmeld = this
|
|
493
|
+
const tilmeld = enforceTilmeld(this);
|
|
228
494
|
try {
|
|
229
495
|
for (let callback of this.constructor
|
|
230
496
|
.beforeLogoutCallbacks) {
|
|
@@ -239,7 +505,8 @@ class User extends AbleObject_1.default {
|
|
|
239
505
|
message: e.message,
|
|
240
506
|
};
|
|
241
507
|
}
|
|
242
|
-
tilmeld.
|
|
508
|
+
this.$nymph.config.debugLog('tilmeld', `User logout for "${this.$data.username}".`);
|
|
509
|
+
await tilmeld.logout();
|
|
243
510
|
for (let callback of this.constructor
|
|
244
511
|
.afterLogoutCallbacks) {
|
|
245
512
|
if (callback) {
|
|
@@ -256,66 +523,142 @@ class User extends AbleObject_1.default {
|
|
|
256
523
|
return 'https://secure.gravatar.com/avatar/?d=mm&s=40';
|
|
257
524
|
}
|
|
258
525
|
return ('https://secure.gravatar.com/avatar/' +
|
|
259
|
-
|
|
526
|
+
crypto
|
|
527
|
+
.createHash('sha256')
|
|
528
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
529
|
+
.digest('hex')
|
|
530
|
+
.toLowerCase() +
|
|
260
531
|
'?d=identicon&s=40');
|
|
261
532
|
}
|
|
533
|
+
$getGid() {
|
|
534
|
+
if (this.$data.group) {
|
|
535
|
+
return this.$data.group.guid;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
$getGids() {
|
|
540
|
+
if (this.$data.groups) {
|
|
541
|
+
return this.$data.groups
|
|
542
|
+
.map((group) => group.guid)
|
|
543
|
+
.filter((guid) => guid != null);
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Get the user's group descendants.
|
|
549
|
+
*/
|
|
262
550
|
async $getDescendantGroups() {
|
|
263
551
|
if (this.$descendantGroups == null) {
|
|
264
552
|
this.$descendantGroups = [];
|
|
265
553
|
if (this.$data.group != null) {
|
|
554
|
+
await this.$data.group.$wake();
|
|
266
555
|
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
267
556
|
}
|
|
557
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
268
558
|
for (let curGroup of this.$data.groups ?? []) {
|
|
269
559
|
this.$descendantGroups = this.$descendantGroups?.concat(await curGroup.$getDescendants());
|
|
270
560
|
}
|
|
271
561
|
}
|
|
272
562
|
return this.$descendantGroups;
|
|
273
563
|
}
|
|
274
|
-
$getDescendantGroupsSync() {
|
|
275
|
-
if (this.$descendantGroups == null) {
|
|
276
|
-
this.$descendantGroups = [];
|
|
277
|
-
if (this.$data.group != null) {
|
|
278
|
-
this.$descendantGroups = this.$data.group.$getDescendantsSync();
|
|
279
|
-
}
|
|
280
|
-
for (let curGroup of this.$data.groups ?? []) {
|
|
281
|
-
this.$descendantGroups = this.$descendantGroups?.concat(curGroup.$getDescendantsSync());
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return this.$descendantGroups;
|
|
285
|
-
}
|
|
286
564
|
$jsonAcceptData(input, allowConflict = false) {
|
|
287
|
-
const tilmeld = this
|
|
288
|
-
this.$
|
|
289
|
-
if ('abilities' in input.data
|
|
290
|
-
input.data.abilities
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
565
|
+
const tilmeld = enforceTilmeld(this);
|
|
566
|
+
this.$check();
|
|
567
|
+
if ('abilities' in input.data) {
|
|
568
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
569
|
+
}
|
|
570
|
+
// If the user is a new domain user, the data protection needs to be updated
|
|
571
|
+
// before the data is accepted, or else some things won't be accepted
|
|
572
|
+
// correctly (enabled, passwordTemp).
|
|
573
|
+
let reloadDataProtection = false;
|
|
574
|
+
if (tilmeld.config.domainSupport) {
|
|
575
|
+
let [_username, domain] = splitn(input.data.username ?? '', '@', 2);
|
|
576
|
+
// Lowercase the domain part.
|
|
577
|
+
if (domain) {
|
|
578
|
+
domain = domain.toLowerCase();
|
|
579
|
+
}
|
|
580
|
+
const user = tilmeld.User.current();
|
|
581
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
582
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
583
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
584
|
+
const abilities = (isCurrentUser || user == null
|
|
585
|
+
? this.$data.abilities
|
|
586
|
+
: user?.abilities) ?? [];
|
|
587
|
+
const isNewUser = input.guid == null;
|
|
588
|
+
const isDomainUser = !isCurrentUser &&
|
|
589
|
+
user != null &&
|
|
590
|
+
domain &&
|
|
591
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
592
|
+
reloadDataProtection = !!(isNewUser && isDomainUser);
|
|
295
593
|
}
|
|
296
|
-
this.$originalEmail = this.$data.email;
|
|
297
594
|
super.$jsonAcceptData(input, allowConflict);
|
|
595
|
+
if (reloadDataProtection) {
|
|
596
|
+
// Load it again after data protection is run.
|
|
597
|
+
this.$updateDataProtection();
|
|
598
|
+
super.$jsonAcceptData(input, allowConflict);
|
|
599
|
+
}
|
|
298
600
|
}
|
|
299
601
|
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
patch.set.abilities.indexOf('system/admin') !== -1 &&
|
|
304
|
-
this.$data.abilities?.indexOf('system/admin') === -1 &&
|
|
305
|
-
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
306
|
-
!tilmeld.gatekeeper('system/admin')) {
|
|
307
|
-
throw new errors_1.BadDataError("You don't have the authority to make this user a system admin.");
|
|
602
|
+
this.$check();
|
|
603
|
+
if ('abilities' in patch.set) {
|
|
604
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
308
605
|
}
|
|
309
|
-
this.$originalEmail = this.$data.email;
|
|
310
606
|
super.$jsonAcceptPatch(patch, allowConflict);
|
|
311
607
|
}
|
|
312
|
-
$
|
|
313
|
-
|
|
608
|
+
$checkIncomingAbilities(incoming) {
|
|
609
|
+
const tilmeld = enforceTilmeld(this);
|
|
610
|
+
if (!Array.isArray(incoming) ||
|
|
611
|
+
incoming.find((ability) => typeof ability !== 'string')) {
|
|
612
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
613
|
+
}
|
|
614
|
+
if (incoming.includes('system/admin') !==
|
|
615
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
616
|
+
(!tilmeld.currentUser ||
|
|
617
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
618
|
+
throw new BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
619
|
+
}
|
|
620
|
+
if (incoming.includes('tilmeld/admin') !==
|
|
621
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
622
|
+
(!tilmeld.currentUser ||
|
|
623
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
624
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
625
|
+
}
|
|
626
|
+
if (incoming.includes('tilmeld/switch') !==
|
|
627
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
628
|
+
(!tilmeld.currentUser ||
|
|
629
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
630
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
631
|
+
}
|
|
632
|
+
if (xor(incoming.filter((ability) => ability.startsWith('tilmeld/')), this.$data.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ?? []).length &&
|
|
633
|
+
(!tilmeld.currentUser ||
|
|
634
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
635
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))) {
|
|
636
|
+
throw new BadDataError("You don't have the authority to grant or revoke Tilmeld abilities.");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
$putData(data, sdata, source) {
|
|
640
|
+
super.$putData(data, sdata, source);
|
|
641
|
+
if (source === 'server') {
|
|
642
|
+
this.$originalEmail = this.$data.email;
|
|
643
|
+
this.$originalUsername = this.$data.username;
|
|
644
|
+
}
|
|
314
645
|
this.$updateDataProtection();
|
|
315
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Update the data protection arrays for a user.
|
|
649
|
+
*
|
|
650
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
651
|
+
*/
|
|
316
652
|
$updateDataProtection(givenUser) {
|
|
317
|
-
const tilmeld = this
|
|
653
|
+
const tilmeld = enforceTilmeld(this);
|
|
318
654
|
let user = givenUser ?? tilmeld.User.current();
|
|
655
|
+
let [_username, domain] = tilmeld.config.domainSupport
|
|
656
|
+
? splitn(this.$data.username ?? '', '@', 2)
|
|
657
|
+
: [this.$data.username, null];
|
|
658
|
+
// Lowercase the domain part.
|
|
659
|
+
if (domain) {
|
|
660
|
+
domain = domain.toLowerCase();
|
|
661
|
+
}
|
|
319
662
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
320
663
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
321
664
|
this.$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
@@ -323,75 +666,211 @@ class User extends AbleObject_1.default {
|
|
|
323
666
|
this.$privateData.push('username');
|
|
324
667
|
}
|
|
325
668
|
const isCurrentUser = user != null && user.$is(this);
|
|
669
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
670
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
326
671
|
const abilities = (isCurrentUser || user == null
|
|
327
672
|
? this.$data.abilities
|
|
328
673
|
: user?.abilities) ?? [];
|
|
329
674
|
const isNewUser = this.guid == null;
|
|
675
|
+
const isDomainUser = tilmeld.config.domainSupport &&
|
|
676
|
+
!isCurrentUser &&
|
|
677
|
+
user != null &&
|
|
678
|
+
domain &&
|
|
679
|
+
abilities.includes(`tilmeld/domain/${domain}/admin`);
|
|
330
680
|
if (isCurrentUser) {
|
|
681
|
+
// Users can check to see what abilities they have.
|
|
331
682
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
332
683
|
this.$clientEnabledMethods.push('$changePassword');
|
|
684
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
333
685
|
this.$clientEnabledMethods.push('$logout');
|
|
334
686
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
687
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
688
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
689
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
690
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
691
|
+
}
|
|
692
|
+
if (isDomainUser) {
|
|
693
|
+
// Domain admins can check to see what abilities their users have.
|
|
694
|
+
this.$clientEnabledMethods.push('$gatekeeper');
|
|
695
|
+
this.$clientEnabledMethods.push('$changePassword');
|
|
696
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
697
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
698
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
699
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
700
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
335
701
|
}
|
|
336
702
|
if (user != null &&
|
|
337
|
-
(abilities.
|
|
338
|
-
abilities.
|
|
339
|
-
|
|
703
|
+
(abilities.includes('tilmeld/admin') ||
|
|
704
|
+
abilities.includes('system/admin'))) {
|
|
705
|
+
// Users who can edit other users can see most of their data.
|
|
706
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
707
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
708
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
340
709
|
this.$allowlistData = undefined;
|
|
341
710
|
}
|
|
342
|
-
else if (
|
|
711
|
+
else if (isDomainUser) {
|
|
712
|
+
// Domain admins can see their users' data, and edit some of it.
|
|
343
713
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
344
714
|
this.$allowlistData.push('username');
|
|
345
715
|
}
|
|
346
716
|
this.$allowlistData.push('avatar');
|
|
347
|
-
if (tilmeld.config.userFields.
|
|
717
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
348
718
|
this.$allowlistData.push('nameFirst');
|
|
349
719
|
this.$allowlistData.push('nameMiddle');
|
|
350
720
|
this.$allowlistData.push('nameLast');
|
|
351
721
|
this.$allowlistData.push('name');
|
|
352
722
|
}
|
|
353
|
-
if (tilmeld.config.userFields.
|
|
723
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
354
724
|
this.$allowlistData.push('email');
|
|
355
725
|
}
|
|
356
|
-
if (tilmeld.config.userFields.
|
|
726
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
357
727
|
this.$allowlistData.push('phone');
|
|
358
728
|
}
|
|
729
|
+
this.$allowlistData.push('enabled');
|
|
730
|
+
this.$allowlistData.push('passwordTemp');
|
|
359
731
|
this.$privateData = [
|
|
732
|
+
// The things commented are there to show what is _not_ private.
|
|
733
|
+
// 'abilities',
|
|
734
|
+
// 'username',
|
|
735
|
+
// 'nameFirst',
|
|
736
|
+
// 'nameMiddle',
|
|
737
|
+
// 'nameLast',
|
|
738
|
+
// 'name',
|
|
739
|
+
// 'email',
|
|
740
|
+
// 'avatar',
|
|
741
|
+
// 'phone',
|
|
742
|
+
// 'group',
|
|
743
|
+
// 'groups',
|
|
744
|
+
// 'inheritAbilities',
|
|
745
|
+
// 'enabled',
|
|
360
746
|
'secret',
|
|
747
|
+
'emailChangeDate',
|
|
361
748
|
'newEmailSecret',
|
|
362
|
-
'
|
|
749
|
+
// 'newEmailAddress',
|
|
363
750
|
'cancelEmailSecret',
|
|
364
|
-
'
|
|
751
|
+
'cancelEmailAddress',
|
|
365
752
|
'recoverSecret',
|
|
366
753
|
'recoverSecretDate',
|
|
754
|
+
'salt',
|
|
367
755
|
'password',
|
|
756
|
+
// 'passwordTemp',
|
|
757
|
+
// 'revokeTokenDate',
|
|
758
|
+
'totpSecret',
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
else if (isCurrentUser || isNewUser) {
|
|
762
|
+
// Users can see their own data, and edit some of it.
|
|
763
|
+
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
764
|
+
this.$allowlistData.push('username');
|
|
765
|
+
}
|
|
766
|
+
this.$allowlistData.push('avatar');
|
|
767
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
768
|
+
this.$allowlistData.push('nameFirst');
|
|
769
|
+
this.$allowlistData.push('nameMiddle');
|
|
770
|
+
this.$allowlistData.push('nameLast');
|
|
771
|
+
this.$allowlistData.push('name');
|
|
772
|
+
}
|
|
773
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
774
|
+
this.$allowlistData.push('email');
|
|
775
|
+
}
|
|
776
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
777
|
+
this.$allowlistData.push('phone');
|
|
778
|
+
}
|
|
779
|
+
this.$privateData = [
|
|
780
|
+
// The things commented are there to show what is _not_ private.
|
|
781
|
+
// 'abilities',
|
|
782
|
+
// 'username',
|
|
783
|
+
// 'nameFirst',
|
|
784
|
+
// 'nameMiddle',
|
|
785
|
+
// 'nameLast',
|
|
786
|
+
// 'name',
|
|
787
|
+
// 'email',
|
|
788
|
+
// 'avatar',
|
|
789
|
+
// 'phone',
|
|
790
|
+
// 'group',
|
|
791
|
+
// 'groups',
|
|
792
|
+
// 'inheritAbilities',
|
|
793
|
+
'enabled',
|
|
794
|
+
'secret',
|
|
795
|
+
'emailChangeDate',
|
|
796
|
+
'newEmailSecret',
|
|
797
|
+
// 'newEmailAddress',
|
|
798
|
+
'cancelEmailSecret',
|
|
799
|
+
'cancelEmailAddress',
|
|
800
|
+
'recoverSecret',
|
|
801
|
+
'recoverSecretDate',
|
|
368
802
|
'salt',
|
|
803
|
+
'password',
|
|
804
|
+
'passwordTemp',
|
|
805
|
+
// 'revokeTokenDate',
|
|
806
|
+
'totpSecret',
|
|
369
807
|
];
|
|
370
808
|
}
|
|
809
|
+
if (user != null &&
|
|
810
|
+
!isCurrentUser &&
|
|
811
|
+
(abilities.includes('tilmeld/switch') ||
|
|
812
|
+
abilities.includes('system/admin'))) {
|
|
813
|
+
// Users with this ability can switch to other users.
|
|
814
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
815
|
+
}
|
|
371
816
|
}
|
|
372
|
-
$
|
|
373
|
-
|
|
817
|
+
$updateGroupDataProtection() {
|
|
818
|
+
if (this.$data.group != null && !this.$data.group.$asleep()) {
|
|
819
|
+
this.$data.group.$updateDataProtection();
|
|
820
|
+
}
|
|
821
|
+
for (let group of this.$data.groups ?? []) {
|
|
822
|
+
if (!group.$asleep()) {
|
|
823
|
+
group.$updateDataProtection();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Check to see if a user has an ability.
|
|
829
|
+
*
|
|
830
|
+
* This function will check both user and group abilities, if the user is
|
|
831
|
+
* marked to inherit the abilities of its group.
|
|
832
|
+
*
|
|
833
|
+
* If `ability` is undefined, it will check to see if the user is currently
|
|
834
|
+
* logged in.
|
|
835
|
+
*
|
|
836
|
+
* If the user has the "system/admin" ability, this function will return true.
|
|
837
|
+
*
|
|
838
|
+
* @param ability The ability.
|
|
839
|
+
* @returns True or false.
|
|
840
|
+
*/
|
|
841
|
+
async $gatekeeper(ability) {
|
|
842
|
+
await this.$wake();
|
|
843
|
+
const tilmeld = enforceTilmeld(this);
|
|
374
844
|
if (ability == null) {
|
|
375
845
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
376
846
|
}
|
|
847
|
+
// Check the cache to see if we've already checked this user.
|
|
377
848
|
if (this.$gatekeeperCache == null) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
849
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
850
|
+
}
|
|
851
|
+
return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
852
|
+
!!this.$data.abilities?.includes('system/admin'));
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Build a gatekeeper cache object.
|
|
856
|
+
*/
|
|
857
|
+
async $getGatekeeperCache() {
|
|
858
|
+
await this.$wake();
|
|
859
|
+
let abilities = this.$data.abilities ?? [];
|
|
860
|
+
if (this.$data.inheritAbilities) {
|
|
861
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
862
|
+
for (let curGroup of this.$data.groups ?? []) {
|
|
863
|
+
if (curGroup.enabled) {
|
|
864
|
+
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
389
865
|
}
|
|
390
866
|
}
|
|
391
|
-
this.$
|
|
867
|
+
await this.$data.group?.$wake();
|
|
868
|
+
const group = this.$data.group;
|
|
869
|
+
if (group != null && group.cdate != null && group.enabled) {
|
|
870
|
+
abilities = abilities.concat(group.abilities ?? []);
|
|
871
|
+
}
|
|
392
872
|
}
|
|
393
|
-
return ((ability
|
|
394
|
-
!!this.$gatekeeperCache['system/admin']);
|
|
873
|
+
return Object.fromEntries(abilities.map((ability) => [ability, true]));
|
|
395
874
|
}
|
|
396
875
|
$clearCache() {
|
|
397
876
|
const ret = super.$clearCache();
|
|
@@ -399,8 +878,13 @@ class User extends AbleObject_1.default {
|
|
|
399
878
|
this.$gatekeeperCache = undefined;
|
|
400
879
|
return ret;
|
|
401
880
|
}
|
|
881
|
+
/**
|
|
882
|
+
* Send the user email verification/change/cancellation links.
|
|
883
|
+
*
|
|
884
|
+
* @returns True on success, false on failure.
|
|
885
|
+
*/
|
|
402
886
|
async $sendEmailVerification() {
|
|
403
|
-
const tilmeld = this
|
|
887
|
+
const tilmeld = enforceTilmeld(this);
|
|
404
888
|
if (this.guid == null) {
|
|
405
889
|
return false;
|
|
406
890
|
}
|
|
@@ -463,33 +947,60 @@ class User extends AbleObject_1.default {
|
|
|
463
947
|
}
|
|
464
948
|
return success;
|
|
465
949
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
950
|
+
/**
|
|
951
|
+
* Add the user to a (secondary) group.
|
|
952
|
+
*
|
|
953
|
+
* @param group The group.
|
|
954
|
+
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
955
|
+
*/
|
|
956
|
+
async $addGroup(group) {
|
|
957
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
958
|
+
let groups = this.$data.groups;
|
|
959
|
+
if (groups == null) {
|
|
960
|
+
groups = [];
|
|
961
|
+
}
|
|
962
|
+
if (!group.$inArray(groups)) {
|
|
963
|
+
groups.push(group);
|
|
964
|
+
this.$data.groups = groups;
|
|
965
|
+
return groups;
|
|
473
966
|
}
|
|
474
967
|
return true;
|
|
475
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Check the given password against the user's.
|
|
971
|
+
*
|
|
972
|
+
* @param password The password in question.
|
|
973
|
+
* @returns True if the passwords match, otherwise false.
|
|
974
|
+
*/
|
|
476
975
|
$checkPassword(password) {
|
|
477
|
-
const tilmeld = this
|
|
976
|
+
const tilmeld = enforceTilmeld(this);
|
|
478
977
|
switch (tilmeld.config.pwMethod) {
|
|
479
978
|
case 'plain':
|
|
480
979
|
return this.$data.password === password;
|
|
481
980
|
case 'digest':
|
|
482
|
-
return this.$data.password ===
|
|
981
|
+
return (this.$data.password ===
|
|
982
|
+
crypto.createHash('sha256').update(password).digest('base64'));
|
|
483
983
|
case 'salt':
|
|
484
984
|
default:
|
|
485
985
|
return (this.$data.password ===
|
|
486
|
-
|
|
986
|
+
crypto
|
|
987
|
+
.createHash('sha256')
|
|
988
|
+
.update(password + this.$data.salt)
|
|
989
|
+
.digest('base64'));
|
|
487
990
|
}
|
|
488
991
|
}
|
|
489
|
-
|
|
490
|
-
|
|
992
|
+
/**
|
|
993
|
+
* Remove the user from a (secondary) group.
|
|
994
|
+
*
|
|
995
|
+
* @param group The group.
|
|
996
|
+
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
997
|
+
*/
|
|
998
|
+
async $delGroup(group) {
|
|
999
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
1000
|
+
let groups = this.$data.groups || [];
|
|
1001
|
+
if (this.$data.groups != null && group.$inArray(groups)) {
|
|
491
1002
|
const newGroups = [];
|
|
492
|
-
for (let curGroup of
|
|
1003
|
+
for (let curGroup of groups) {
|
|
493
1004
|
if (!group.$is(curGroup)) {
|
|
494
1005
|
newGroups.push(curGroup);
|
|
495
1006
|
}
|
|
@@ -499,7 +1010,13 @@ class User extends AbleObject_1.default {
|
|
|
499
1010
|
}
|
|
500
1011
|
return true;
|
|
501
1012
|
}
|
|
502
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* Check whether the user is in a (primary or secondary) group.
|
|
1015
|
+
*
|
|
1016
|
+
* @param group The group, or the group's GUID.
|
|
1017
|
+
* @returns True or false.
|
|
1018
|
+
*/
|
|
1019
|
+
async $inGroup(group) {
|
|
503
1020
|
if (typeof group === 'string') {
|
|
504
1021
|
if (this.$data.group?.guid === group) {
|
|
505
1022
|
return true;
|
|
@@ -513,35 +1030,66 @@ class User extends AbleObject_1.default {
|
|
|
513
1030
|
}
|
|
514
1031
|
return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
|
|
515
1032
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1033
|
+
/**
|
|
1034
|
+
* Check whether the user is a descendant of a group.
|
|
1035
|
+
*
|
|
1036
|
+
* @param group The group, or the group's GUID.
|
|
1037
|
+
* @returns True or false.
|
|
1038
|
+
*/
|
|
1039
|
+
async $isDescendant(group) {
|
|
1040
|
+
// Check to see if the user is in a descendant group of the given group.
|
|
1041
|
+
await this.$data.group?.$wake();
|
|
1042
|
+
if (this.$data.group != null &&
|
|
1043
|
+
this.$data.group?.cdate != null &&
|
|
1044
|
+
(await this.$data.group?.$isDescendant(group))) {
|
|
519
1045
|
return true;
|
|
520
1046
|
}
|
|
1047
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
521
1048
|
for (let curGroup of this.$data.groups ?? []) {
|
|
522
|
-
if (curGroup.$isDescendant(group)) {
|
|
1049
|
+
if (await curGroup.$isDescendant(group)) {
|
|
523
1050
|
return true;
|
|
524
1051
|
}
|
|
525
1052
|
}
|
|
526
1053
|
return false;
|
|
527
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* A frontend accessible method to change the user's password.
|
|
1057
|
+
*
|
|
1058
|
+
* @param data The input data from the client.
|
|
1059
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1060
|
+
*/
|
|
528
1061
|
async $changePassword(data) {
|
|
1062
|
+
this.$nymph.config.debugLog('tilmeld', `Password change request for "${this.$data.username}".`);
|
|
529
1063
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
530
1064
|
return { result: false, message: 'Please specify a password.' };
|
|
531
1065
|
}
|
|
532
1066
|
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
1067
|
+
this.$nymph.config.debugLog('tilmeld', `Incorrect password for change request for "${this.$data.username}".`);
|
|
533
1068
|
return { result: false, message: 'Incorrect password.' };
|
|
534
1069
|
}
|
|
535
1070
|
this.$data.passwordTemp = data.newPassword;
|
|
1071
|
+
if (data.revokeCurrentTokens) {
|
|
1072
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1073
|
+
}
|
|
536
1074
|
if (await this.$save()) {
|
|
1075
|
+
if (data.revokeCurrentTokens) {
|
|
1076
|
+
const tilmeld = enforceTilmeld(this);
|
|
1077
|
+
await tilmeld.login(this, true);
|
|
1078
|
+
}
|
|
537
1079
|
return { result: true, message: 'Your password has been changed.' };
|
|
538
1080
|
}
|
|
539
1081
|
else {
|
|
540
1082
|
return { result: false, message: "Couldn't save new password." };
|
|
541
1083
|
}
|
|
542
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Change the user's password.
|
|
1087
|
+
*
|
|
1088
|
+
* @param password The new password.
|
|
1089
|
+
* @returns The resulting password or hash which is stored in the entity.
|
|
1090
|
+
*/
|
|
543
1091
|
$password(password) {
|
|
544
|
-
const tilmeld = this
|
|
1092
|
+
const tilmeld = enforceTilmeld(this);
|
|
545
1093
|
switch (tilmeld.config.pwMethod) {
|
|
546
1094
|
case 'plain':
|
|
547
1095
|
delete this.$data.salt;
|
|
@@ -549,24 +1097,221 @@ class User extends AbleObject_1.default {
|
|
|
549
1097
|
break;
|
|
550
1098
|
case 'digest':
|
|
551
1099
|
delete this.$data.salt;
|
|
552
|
-
this.$data.password =
|
|
1100
|
+
this.$data.password = crypto
|
|
1101
|
+
.createHash('sha256')
|
|
1102
|
+
.update(password)
|
|
1103
|
+
.digest('base64');
|
|
553
1104
|
break;
|
|
554
1105
|
case 'salt':
|
|
555
1106
|
default:
|
|
556
|
-
this.$data.salt =
|
|
557
|
-
this.$data.password =
|
|
1107
|
+
this.$data.salt = nanoid();
|
|
1108
|
+
this.$data.password = crypto
|
|
1109
|
+
.createHash('sha256')
|
|
1110
|
+
.update(password + this.$data.salt)
|
|
1111
|
+
.digest('base64');
|
|
558
1112
|
break;
|
|
559
1113
|
}
|
|
560
1114
|
return this.$data.password;
|
|
561
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
1118
|
+
*
|
|
1119
|
+
* @param data The input data from the client.
|
|
1120
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1121
|
+
*/
|
|
1122
|
+
async $revokeCurrentTokens(data) {
|
|
1123
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1124
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1125
|
+
}
|
|
1126
|
+
this.$data.revokeTokenDate = Date.now();
|
|
1127
|
+
if (await this.$save()) {
|
|
1128
|
+
const tilmeld = enforceTilmeld(this);
|
|
1129
|
+
await tilmeld.login(this, true);
|
|
1130
|
+
return {
|
|
1131
|
+
result: true,
|
|
1132
|
+
message: 'You have logged out of all other sessions.',
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
async $hasTOTPSecret() {
|
|
1140
|
+
return this.$data.totpSecret != null;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Check the given code against the user's TOTP secret.
|
|
1144
|
+
*
|
|
1145
|
+
* @param code The code in question.
|
|
1146
|
+
* @returns True if the code is valid, otherwise false.
|
|
1147
|
+
*/
|
|
1148
|
+
$checkTOTPCode(code) {
|
|
1149
|
+
if (this.$data.totpSecret == null) {
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
const tilmeld = enforceTilmeld(this);
|
|
1153
|
+
const totp = new TOTP({
|
|
1154
|
+
issuer: tilmeld.config.appName,
|
|
1155
|
+
label: this.$data.username,
|
|
1156
|
+
algorithm: 'SHA1',
|
|
1157
|
+
digits: 6,
|
|
1158
|
+
period: 30,
|
|
1159
|
+
secret: this.$data.totpSecret,
|
|
1160
|
+
});
|
|
1161
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1165
|
+
*
|
|
1166
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1167
|
+
*/
|
|
1168
|
+
async $getNewTOTPSecret() {
|
|
1169
|
+
const tilmeld = enforceTilmeld(this);
|
|
1170
|
+
if (this.$data.totpSecret != null) {
|
|
1171
|
+
throw new BadDataError('You already have a 2FA secret.');
|
|
1172
|
+
}
|
|
1173
|
+
const secret = new Secret();
|
|
1174
|
+
const totp = new TOTP({
|
|
1175
|
+
issuer: tilmeld.config.appName,
|
|
1176
|
+
label: this.$data.username,
|
|
1177
|
+
algorithm: 'SHA1',
|
|
1178
|
+
digits: 6,
|
|
1179
|
+
period: 30,
|
|
1180
|
+
secret,
|
|
1181
|
+
});
|
|
1182
|
+
const uri = totp.toString();
|
|
1183
|
+
const qrcode = await toDataURL(uri);
|
|
1184
|
+
return {
|
|
1185
|
+
uri,
|
|
1186
|
+
qrcode,
|
|
1187
|
+
secret: secret.base32,
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1192
|
+
*
|
|
1193
|
+
* @param data The input data from the client.
|
|
1194
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1195
|
+
*/
|
|
1196
|
+
async $saveTOTPSecret(data) {
|
|
1197
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1198
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1199
|
+
}
|
|
1200
|
+
if (this.$data.totpSecret != null) {
|
|
1201
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1202
|
+
}
|
|
1203
|
+
if (data.secret == null ||
|
|
1204
|
+
typeof data.secret !== 'string' ||
|
|
1205
|
+
data.secret === '') {
|
|
1206
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1207
|
+
}
|
|
1208
|
+
if (data.code == null ||
|
|
1209
|
+
typeof data.code !== 'string' ||
|
|
1210
|
+
data.code.length !== 6) {
|
|
1211
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1212
|
+
}
|
|
1213
|
+
const tilmeld = enforceTilmeld(this);
|
|
1214
|
+
const totp = new TOTP({
|
|
1215
|
+
issuer: tilmeld.config.appName,
|
|
1216
|
+
label: this.$data.username,
|
|
1217
|
+
algorithm: 'SHA1',
|
|
1218
|
+
digits: 6,
|
|
1219
|
+
period: 30,
|
|
1220
|
+
secret: data.secret,
|
|
1221
|
+
});
|
|
1222
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1223
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1224
|
+
}
|
|
1225
|
+
this.$data.totpSecret = data.secret;
|
|
1226
|
+
if (await this.$save()) {
|
|
1227
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1235
|
+
* account.
|
|
1236
|
+
*
|
|
1237
|
+
* @param data The input data from the client.
|
|
1238
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1239
|
+
*/
|
|
1240
|
+
async $removeTOTPSecret(data) {
|
|
1241
|
+
const tilmeld = enforceTilmeld(this);
|
|
1242
|
+
if (data) {
|
|
1243
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1244
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1245
|
+
}
|
|
1246
|
+
if (data.code == null ||
|
|
1247
|
+
typeof data.code !== 'string' ||
|
|
1248
|
+
data.code.length !== 6) {
|
|
1249
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1250
|
+
}
|
|
1251
|
+
if (this.$data.totpSecret == null) {
|
|
1252
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1253
|
+
}
|
|
1254
|
+
const totp = new TOTP({
|
|
1255
|
+
issuer: tilmeld.config.appName,
|
|
1256
|
+
label: this.$data.username,
|
|
1257
|
+
algorithm: 'SHA1',
|
|
1258
|
+
digits: 6,
|
|
1259
|
+
period: 30,
|
|
1260
|
+
secret: this.$data.totpSecret,
|
|
1261
|
+
});
|
|
1262
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1263
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
else if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1267
|
+
return {
|
|
1268
|
+
result: false,
|
|
1269
|
+
message: 'You must provide your password and 2FA code.',
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
delete this.$data.totpSecret;
|
|
1273
|
+
if (await this.$save()) {
|
|
1274
|
+
return {
|
|
1275
|
+
result: true,
|
|
1276
|
+
message: 'Two factor secret has been removed.',
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Check that a username is valid.
|
|
1285
|
+
*
|
|
1286
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1287
|
+
*/
|
|
562
1288
|
async $checkUsername() {
|
|
563
|
-
const tilmeld = this
|
|
1289
|
+
const tilmeld = enforceTilmeld(this);
|
|
1290
|
+
this.$nymph.config.debugInfo('tilmeld', `Username check "${this.$data.username}".`);
|
|
564
1291
|
if (!tilmeld.config.emailUsernames) {
|
|
565
|
-
|
|
1292
|
+
let [username, domain] = tilmeld.config.domainSupport
|
|
1293
|
+
? splitn(this.$data.username ?? '', '@', 2)
|
|
1294
|
+
: [this.$data.username, null];
|
|
1295
|
+
// Lowercase the domain part.
|
|
1296
|
+
if (domain) {
|
|
1297
|
+
domain = domain.toLowerCase();
|
|
1298
|
+
this.$data.username = `${username}@${domain}`;
|
|
1299
|
+
}
|
|
1300
|
+
if (this.$data.username == null ||
|
|
1301
|
+
!this.$data.username.length ||
|
|
1302
|
+
username == null ||
|
|
1303
|
+
!username.length) {
|
|
566
1304
|
return { result: false, message: 'Please specify a username.' };
|
|
567
1305
|
}
|
|
568
|
-
if (tilmeld.config.
|
|
569
|
-
|
|
1306
|
+
if (username.length < tilmeld.config.minUsernameLength) {
|
|
1307
|
+
return {
|
|
1308
|
+
result: false,
|
|
1309
|
+
message: 'Usernames must be at least ' +
|
|
1310
|
+
tilmeld.config.minUsernameLength +
|
|
1311
|
+
' characters.',
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (username.length > tilmeld.config.maxUsernameLength) {
|
|
570
1315
|
return {
|
|
571
1316
|
result: false,
|
|
572
1317
|
message: 'Usernames must not exceed ' +
|
|
@@ -574,18 +1319,34 @@ class User extends AbleObject_1.default {
|
|
|
574
1319
|
' characters.',
|
|
575
1320
|
};
|
|
576
1321
|
}
|
|
577
|
-
if (
|
|
1322
|
+
if (difference(username.split(''), tilmeld.config.validChars.split(''))
|
|
1323
|
+
.length) {
|
|
578
1324
|
return {
|
|
579
1325
|
result: false,
|
|
580
1326
|
message: tilmeld.config.validCharsNotice,
|
|
581
1327
|
};
|
|
582
1328
|
}
|
|
583
|
-
if (!tilmeld.config.validRegex.test(
|
|
1329
|
+
if (!tilmeld.config.validRegex.test(username)) {
|
|
584
1330
|
return {
|
|
585
1331
|
result: false,
|
|
586
1332
|
message: tilmeld.config.validRegexNotice,
|
|
587
1333
|
};
|
|
588
1334
|
}
|
|
1335
|
+
if (domain != null) {
|
|
1336
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
1337
|
+
return {
|
|
1338
|
+
result: false,
|
|
1339
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
if (!this.$is(tilmeld.currentUser) &&
|
|
1343
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
1344
|
+
return {
|
|
1345
|
+
result: false,
|
|
1346
|
+
message: "You don't have the authority to manage users on this domain.",
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
589
1350
|
const selector = {
|
|
590
1351
|
type: '&',
|
|
591
1352
|
ilike: [
|
|
@@ -600,6 +1361,17 @@ class User extends AbleObject_1.default {
|
|
|
600
1361
|
if (test != null) {
|
|
601
1362
|
return { result: false, message: 'That username is taken.' };
|
|
602
1363
|
}
|
|
1364
|
+
for (let callback of this.constructor
|
|
1365
|
+
.checkUsernameCallbacks) {
|
|
1366
|
+
if (callback) {
|
|
1367
|
+
const result = await callback(this, {
|
|
1368
|
+
username: this.$data.username,
|
|
1369
|
+
});
|
|
1370
|
+
if (!result.result) {
|
|
1371
|
+
return { message: 'Username is not available.', ...result };
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
603
1375
|
return {
|
|
604
1376
|
result: true,
|
|
605
1377
|
message: this.guid != null ? 'Username is valid.' : 'Username is available!',
|
|
@@ -610,8 +1382,15 @@ class User extends AbleObject_1.default {
|
|
|
610
1382
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
611
1383
|
return { result: false, message: 'Please specify an email.' };
|
|
612
1384
|
}
|
|
613
|
-
if (tilmeld.config.
|
|
614
|
-
|
|
1385
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1386
|
+
return {
|
|
1387
|
+
result: false,
|
|
1388
|
+
message: 'Emails must be at least ' +
|
|
1389
|
+
tilmeld.config.minUsernameLength +
|
|
1390
|
+
' characters.',
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
615
1394
|
return {
|
|
616
1395
|
result: false,
|
|
617
1396
|
message: 'Emails must not exceed ' +
|
|
@@ -622,8 +1401,14 @@ class User extends AbleObject_1.default {
|
|
|
622
1401
|
return await this.$checkEmail();
|
|
623
1402
|
}
|
|
624
1403
|
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Check that an email is unique.
|
|
1406
|
+
*
|
|
1407
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1408
|
+
*/
|
|
625
1409
|
async $checkEmail() {
|
|
626
|
-
const tilmeld = this
|
|
1410
|
+
const tilmeld = enforceTilmeld(this);
|
|
1411
|
+
this.$nymph.config.debugInfo('tilmeld', `Email check "${this.$data.email}".`);
|
|
627
1412
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
628
1413
|
if (tilmeld.config.verifyEmail) {
|
|
629
1414
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -657,8 +1442,14 @@ class User extends AbleObject_1.default {
|
|
|
657
1442
|
message: this.guid != null ? 'Email is valid.' : 'Email address is valid!',
|
|
658
1443
|
};
|
|
659
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Check that a phone number is unique.
|
|
1447
|
+
*
|
|
1448
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1449
|
+
*/
|
|
660
1450
|
async $checkPhone() {
|
|
661
|
-
const tilmeld = this
|
|
1451
|
+
const tilmeld = enforceTilmeld(this);
|
|
1452
|
+
this.$nymph.config.debugInfo('tilmeld', `Phone check "${this.$data.phone}".`);
|
|
662
1453
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
663
1454
|
return { result: false, message: 'Please specify a phone number.' };
|
|
664
1455
|
}
|
|
@@ -686,7 +1477,7 @@ class User extends AbleObject_1.default {
|
|
|
686
1477
|
};
|
|
687
1478
|
}
|
|
688
1479
|
async $register(data) {
|
|
689
|
-
let tilmeld = this
|
|
1480
|
+
let tilmeld = enforceTilmeld(this);
|
|
690
1481
|
if (!tilmeld.config.allowRegistration) {
|
|
691
1482
|
return {
|
|
692
1483
|
result: false,
|
|
@@ -701,21 +1492,6 @@ class User extends AbleObject_1.default {
|
|
|
701
1492
|
message: 'This is already a registered user.',
|
|
702
1493
|
};
|
|
703
1494
|
}
|
|
704
|
-
try {
|
|
705
|
-
for (let callback of this.constructor
|
|
706
|
-
.beforeRegisterCallbacks) {
|
|
707
|
-
if (callback) {
|
|
708
|
-
await callback(this, data);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
catch (e) {
|
|
713
|
-
return {
|
|
714
|
-
result: false,
|
|
715
|
-
loggedin: false,
|
|
716
|
-
message: e.message,
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
1495
|
if (!('password' in data) || !data.password.length) {
|
|
720
1496
|
return {
|
|
721
1497
|
result: false,
|
|
@@ -728,7 +1504,7 @@ class User extends AbleObject_1.default {
|
|
|
728
1504
|
return { ...unCheck, loggedin: false };
|
|
729
1505
|
}
|
|
730
1506
|
this.$password(data.password);
|
|
731
|
-
if (tilmeld.config.regFields.
|
|
1507
|
+
if (tilmeld.config.regFields.includes('name')) {
|
|
732
1508
|
this.$data.name =
|
|
733
1509
|
this.$data.nameFirst +
|
|
734
1510
|
(this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
|
|
@@ -740,59 +1516,35 @@ class User extends AbleObject_1.default {
|
|
|
740
1516
|
if (tilmeld.config.emailUsernames) {
|
|
741
1517
|
this.$data.email = this.$data.username;
|
|
742
1518
|
}
|
|
743
|
-
|
|
1519
|
+
// Start transaction.
|
|
1520
|
+
const transaction = 'tilmeld-register-' + nanoid();
|
|
744
1521
|
const nymph = this.$nymph;
|
|
745
1522
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
746
|
-
this.$
|
|
747
|
-
tilmeld = this
|
|
1523
|
+
this.$setNymph(tnymph);
|
|
1524
|
+
tilmeld = enforceTilmeld(this);
|
|
1525
|
+
let message = '';
|
|
1526
|
+
let loggedin = false;
|
|
748
1527
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
generatedPrimaryGroup.email = this.$data.email;
|
|
756
|
-
const parent = await tnymph.getEntity({ class: tilmeld.Group }, {
|
|
757
|
-
type: '&',
|
|
758
|
-
equal: ['defaultPrimary', true],
|
|
759
|
-
});
|
|
760
|
-
if (parent != null) {
|
|
761
|
-
generatedPrimaryGroup.parent = parent;
|
|
762
|
-
}
|
|
763
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
764
|
-
await tnymph.rollback(transaction);
|
|
765
|
-
this.$nymph = nymph;
|
|
766
|
-
return {
|
|
767
|
-
result: false,
|
|
768
|
-
loggedin: false,
|
|
769
|
-
message: 'Error creating primary group for user.',
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
this.$data.group = generatedPrimaryGroup;
|
|
773
|
-
}
|
|
774
|
-
else {
|
|
775
|
-
const group = await tnymph.getEntity({ class: tilmeld.Group }, {
|
|
776
|
-
type: '&',
|
|
777
|
-
equal: ['defaultPrimary', true],
|
|
778
|
-
});
|
|
779
|
-
if (group != null) {
|
|
780
|
-
this.$data.group = group;
|
|
1528
|
+
try {
|
|
1529
|
+
for (let callback of this.constructor
|
|
1530
|
+
.beforeRegisterCallbacks) {
|
|
1531
|
+
if (callback) {
|
|
1532
|
+
await callback(this, data);
|
|
1533
|
+
}
|
|
781
1534
|
}
|
|
782
1535
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
type: '&',
|
|
792
|
-
equal: ['defaultSecondary', true],
|
|
793
|
-
});
|
|
1536
|
+
catch (e) {
|
|
1537
|
+
await tnymph.rollback(transaction);
|
|
1538
|
+
this.$setNymph(nymph);
|
|
1539
|
+
return {
|
|
1540
|
+
result: false,
|
|
1541
|
+
loggedin: false,
|
|
1542
|
+
message: e.message,
|
|
1543
|
+
};
|
|
794
1544
|
}
|
|
795
|
-
if (tilmeld.config.
|
|
1545
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1546
|
+
tilmeld.config.verifyEmail) {
|
|
1547
|
+
// The user will be enabled after verifying their e-mail address.
|
|
796
1548
|
if (!tilmeld.config.unverifiedAccess) {
|
|
797
1549
|
this.$data.enabled = false;
|
|
798
1550
|
}
|
|
@@ -800,6 +1552,8 @@ class User extends AbleObject_1.default {
|
|
|
800
1552
|
else {
|
|
801
1553
|
this.$data.enabled = true;
|
|
802
1554
|
}
|
|
1555
|
+
// If createAdmin is true and there are no other users, grant
|
|
1556
|
+
// "system/admin".
|
|
803
1557
|
let madeAdmin = false;
|
|
804
1558
|
if (tilmeld.config.createAdmin) {
|
|
805
1559
|
const otherUsers = await tnymph.getEntities({
|
|
@@ -808,13 +1562,17 @@ class User extends AbleObject_1.default {
|
|
|
808
1562
|
limit: 1,
|
|
809
1563
|
return: 'guid',
|
|
810
1564
|
});
|
|
1565
|
+
// Make sure it's not just null, cause that means an error.
|
|
811
1566
|
if (!otherUsers.length) {
|
|
812
1567
|
this.$grant('system/admin');
|
|
813
1568
|
this.$data.enabled = true;
|
|
814
1569
|
madeAdmin = true;
|
|
815
1570
|
}
|
|
816
1571
|
}
|
|
1572
|
+
this.$nymph.config.debugLog('tilmeld', `Registering new user "${this.$data.username}".`);
|
|
817
1573
|
if (await this.$saveSkipAC()) {
|
|
1574
|
+
this.$nymph.config.debugLog('tilmeld', `New user registered "${this.$data.username}".`);
|
|
1575
|
+
// Send the new user registered email.
|
|
818
1576
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
819
1577
|
await tilmeld.config.sendEmail(tilmeld, {
|
|
820
1578
|
template: 'UserRegistered',
|
|
@@ -831,31 +1589,20 @@ class User extends AbleObject_1.default {
|
|
|
831
1589
|
},
|
|
832
1590
|
}, this);
|
|
833
1591
|
}
|
|
834
|
-
|
|
835
|
-
let loggedin = false;
|
|
836
|
-
if (generatedPrimaryGroup != null) {
|
|
837
|
-
generatedPrimaryGroup.user = this;
|
|
838
|
-
if (!(await generatedPrimaryGroup.$saveSkipAC())) {
|
|
839
|
-
await tnymph.rollback(transaction);
|
|
840
|
-
this.$nymph = nymph;
|
|
841
|
-
return {
|
|
842
|
-
result: false,
|
|
843
|
-
loggedin: false,
|
|
844
|
-
message: 'Your primary group could not be assigned. Please try again later.',
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
}
|
|
1592
|
+
// Finish up.
|
|
848
1593
|
if (tilmeld.config.verifyEmail &&
|
|
849
1594
|
!tilmeld.config.unverifiedAccess &&
|
|
850
|
-
!madeAdmin
|
|
1595
|
+
!madeAdmin &&
|
|
1596
|
+
tilmeld.config.userFields.includes('email')) {
|
|
851
1597
|
message +=
|
|
852
1598
|
`Almost there. An email has been sent to ${this.$data.email} ` +
|
|
853
1599
|
'with a verification link for you to finish registration.';
|
|
854
1600
|
}
|
|
855
1601
|
else if (tilmeld.config.verifyEmail &&
|
|
856
1602
|
tilmeld.config.unverifiedAccess &&
|
|
857
|
-
!madeAdmin
|
|
858
|
-
|
|
1603
|
+
!madeAdmin &&
|
|
1604
|
+
tilmeld.config.userFields.includes('email')) {
|
|
1605
|
+
if (!(await tilmeld.login(this, true))) {
|
|
859
1606
|
throw new Error('An error occurred trying to log you in.');
|
|
860
1607
|
}
|
|
861
1608
|
message +=
|
|
@@ -865,7 +1612,7 @@ class User extends AbleObject_1.default {
|
|
|
865
1612
|
loggedin = true;
|
|
866
1613
|
}
|
|
867
1614
|
else {
|
|
868
|
-
if (!tilmeld.login(this, true)) {
|
|
1615
|
+
if (!(await tilmeld.login(this, true))) {
|
|
869
1616
|
throw new Error('An error occurred trying to log you in.');
|
|
870
1617
|
}
|
|
871
1618
|
message += "You're now registered and logged in!";
|
|
@@ -880,23 +1627,11 @@ class User extends AbleObject_1.default {
|
|
|
880
1627
|
});
|
|
881
1628
|
}
|
|
882
1629
|
}
|
|
883
|
-
try {
|
|
884
|
-
await tnymph.commit(transaction);
|
|
885
|
-
this.$nymph = nymph;
|
|
886
|
-
this.$nymph.tilmeld?.fillSession(this);
|
|
887
|
-
}
|
|
888
|
-
catch (e) {
|
|
889
|
-
throw e;
|
|
890
|
-
}
|
|
891
|
-
return {
|
|
892
|
-
result: true,
|
|
893
|
-
loggedin,
|
|
894
|
-
message,
|
|
895
|
-
};
|
|
896
1630
|
}
|
|
897
1631
|
else {
|
|
1632
|
+
this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}".`);
|
|
898
1633
|
await tnymph.rollback(transaction);
|
|
899
|
-
this.$nymph
|
|
1634
|
+
this.$setNymph(nymph);
|
|
900
1635
|
return {
|
|
901
1636
|
result: false,
|
|
902
1637
|
loggedin: false,
|
|
@@ -905,22 +1640,69 @@ class User extends AbleObject_1.default {
|
|
|
905
1640
|
}
|
|
906
1641
|
}
|
|
907
1642
|
catch (e) {
|
|
1643
|
+
this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}": ${e}`);
|
|
908
1644
|
await tnymph.rollback(transaction);
|
|
909
|
-
this.$nymph
|
|
1645
|
+
this.$setNymph(nymph);
|
|
1646
|
+
throw e;
|
|
1647
|
+
}
|
|
1648
|
+
try {
|
|
1649
|
+
await tnymph.commit(transaction);
|
|
1650
|
+
this.$setNymph(nymph);
|
|
1651
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
1652
|
+
}
|
|
1653
|
+
catch (e) {
|
|
910
1654
|
throw e;
|
|
911
1655
|
}
|
|
1656
|
+
return {
|
|
1657
|
+
result: true,
|
|
1658
|
+
loggedin,
|
|
1659
|
+
message,
|
|
1660
|
+
};
|
|
912
1661
|
}
|
|
913
1662
|
async $save() {
|
|
914
|
-
let tilmeld = this
|
|
1663
|
+
let tilmeld = enforceTilmeld(this);
|
|
915
1664
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
916
1665
|
return false;
|
|
917
1666
|
}
|
|
918
|
-
if (
|
|
1667
|
+
if (!this.$skipAcWhenSaving &&
|
|
1668
|
+
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
919
1669
|
!tilmeld.gatekeeper('system/admin') &&
|
|
920
|
-
this.$
|
|
921
|
-
throw new
|
|
1670
|
+
this.$data.abilities?.includes('system/admin')) {
|
|
1671
|
+
throw new AccessControlError("You don't have the authority to modify system admins.");
|
|
1672
|
+
}
|
|
1673
|
+
// Lowercase the domain part.
|
|
1674
|
+
if (tilmeld.config.domainSupport) {
|
|
1675
|
+
const [username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
1676
|
+
if (domain) {
|
|
1677
|
+
this.$data.username = `${username}@${domain.toLowerCase()}`;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (tilmeld.config.domainSupport) {
|
|
1681
|
+
const [_username, domain] = splitn(this.$data.username ?? '', '@', 2);
|
|
1682
|
+
const [_originalUsername, originalDomain] = splitn(this.$originalUsername ?? '', '@', 2);
|
|
1683
|
+
if (this.guid != null &&
|
|
1684
|
+
((originalDomain == null && domain != null) ||
|
|
1685
|
+
(domain == null && originalDomain != null))) {
|
|
1686
|
+
throw new AccessControlError("Users can't switch to or from having a domain once created.");
|
|
1687
|
+
}
|
|
1688
|
+
if (!this.$skipAcWhenSaving &&
|
|
1689
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1690
|
+
!tilmeld.gatekeeper('system/admin')) {
|
|
1691
|
+
if (domain != null &&
|
|
1692
|
+
!this.$is(tilmeld.currentUser) &&
|
|
1693
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
1694
|
+
throw new AccessControlError("You don't have the authority to modify users on this domain.");
|
|
1695
|
+
}
|
|
1696
|
+
if (originalDomain != null &&
|
|
1697
|
+
originalDomain !== domain &&
|
|
1698
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
1699
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
1700
|
+
throw new AccessControlError("You don't have the authority to modify users on this domain.");
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
922
1703
|
}
|
|
923
1704
|
let sendVerification = false;
|
|
1705
|
+
// Formatting.
|
|
924
1706
|
this.$data.username = this.$data.username.trim();
|
|
925
1707
|
if (tilmeld.config.emailUsernames) {
|
|
926
1708
|
this.$data.email = this.$data.username;
|
|
@@ -934,49 +1716,63 @@ class User extends AbleObject_1.default {
|
|
|
934
1716
|
(this.$data.nameMiddle ? ' ' + this.$data.nameMiddle : '') +
|
|
935
1717
|
(this.$data.nameLast ? ' ' + this.$data.nameLast : '');
|
|
936
1718
|
this.$data.enabled = !!this.$data.enabled;
|
|
937
|
-
|
|
1719
|
+
// Clear empty values.
|
|
1720
|
+
if (this.$data.nameFirst === '' ||
|
|
1721
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
938
1722
|
delete this.$data.nameFirst;
|
|
939
1723
|
}
|
|
940
|
-
if (this.$data.nameMiddle === ''
|
|
1724
|
+
if (this.$data.nameMiddle === '' ||
|
|
1725
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
941
1726
|
delete this.$data.nameMiddle;
|
|
942
1727
|
}
|
|
943
|
-
if (this.$data.nameLast === ''
|
|
1728
|
+
if (this.$data.nameLast === '' ||
|
|
1729
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
944
1730
|
delete this.$data.nameLast;
|
|
945
1731
|
}
|
|
946
|
-
if (this.$data.name === '') {
|
|
1732
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
947
1733
|
delete this.$data.name;
|
|
948
1734
|
}
|
|
949
|
-
if (this.$data.email === ''
|
|
1735
|
+
if (this.$data.email === '' ||
|
|
1736
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
950
1737
|
delete this.$data.email;
|
|
951
1738
|
}
|
|
952
1739
|
if (this.$data.avatar === '') {
|
|
953
1740
|
delete this.$data.avatar;
|
|
954
1741
|
}
|
|
955
|
-
if (this.$data.phone === ''
|
|
1742
|
+
if (this.$data.phone === '' ||
|
|
1743
|
+
!tilmeld.config.userFields.includes('phone')) {
|
|
956
1744
|
delete this.$data.phone;
|
|
957
1745
|
}
|
|
958
|
-
if (this.$data.secret === ''
|
|
1746
|
+
if (this.$data.secret === '' ||
|
|
1747
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
959
1748
|
delete this.$data.secret;
|
|
960
1749
|
}
|
|
961
|
-
if (this.$data.emailChangeDate === 0
|
|
1750
|
+
if (this.$data.emailChangeDate === 0 ||
|
|
1751
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
962
1752
|
delete this.$data.emailChangeDate;
|
|
963
1753
|
}
|
|
964
|
-
if (this.$data.newEmailSecret === ''
|
|
1754
|
+
if (this.$data.newEmailSecret === '' ||
|
|
1755
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
965
1756
|
delete this.$data.newEmailSecret;
|
|
966
1757
|
}
|
|
967
|
-
if (this.$data.newEmailAddress === ''
|
|
1758
|
+
if (this.$data.newEmailAddress === '' ||
|
|
1759
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
968
1760
|
delete this.$data.newEmailAddress;
|
|
969
1761
|
}
|
|
970
|
-
if (this.$data.cancelEmailSecret === ''
|
|
1762
|
+
if (this.$data.cancelEmailSecret === '' ||
|
|
1763
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
971
1764
|
delete this.$data.cancelEmailSecret;
|
|
972
1765
|
}
|
|
973
|
-
if (this.$data.cancelEmailAddress === ''
|
|
1766
|
+
if (this.$data.cancelEmailAddress === '' ||
|
|
1767
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
974
1768
|
delete this.$data.cancelEmailAddress;
|
|
975
1769
|
}
|
|
976
|
-
if (this.$data.recoverSecret === ''
|
|
1770
|
+
if (this.$data.recoverSecret === '' ||
|
|
1771
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
977
1772
|
delete this.$data.recoverSecret;
|
|
978
1773
|
}
|
|
979
|
-
if (this.$data.recoverSecretDate === 0
|
|
1774
|
+
if (this.$data.recoverSecretDate === 0 ||
|
|
1775
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
980
1776
|
delete this.$data.recoverSecretDate;
|
|
981
1777
|
}
|
|
982
1778
|
if (this.$data.password === '') {
|
|
@@ -988,50 +1784,65 @@ class User extends AbleObject_1.default {
|
|
|
988
1784
|
if (this.$data.inheritAbilities === false) {
|
|
989
1785
|
delete this.$data.inheritAbilities;
|
|
990
1786
|
}
|
|
1787
|
+
// Verification.
|
|
991
1788
|
const unCheck = await this.$checkUsername();
|
|
992
1789
|
if (!unCheck.result) {
|
|
993
|
-
throw new
|
|
1790
|
+
throw new BadUsernameError(unCheck.message);
|
|
994
1791
|
}
|
|
995
|
-
if (!tilmeld.config.emailUsernames
|
|
1792
|
+
if (!tilmeld.config.emailUsernames &&
|
|
1793
|
+
tilmeld.config.userFields.includes('email')) {
|
|
996
1794
|
const emCheck = await this.$checkEmail();
|
|
997
1795
|
if (!emCheck.result) {
|
|
998
|
-
throw new
|
|
1796
|
+
throw new BadEmailError(emCheck.message);
|
|
999
1797
|
}
|
|
1000
1798
|
}
|
|
1001
|
-
|
|
1799
|
+
// Email changes.
|
|
1800
|
+
if (!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1801
|
+
tilmeld.config.userFields.includes('email')) {
|
|
1802
|
+
// The user isn't an admin, so email address changes should contain some
|
|
1803
|
+
// security measures.
|
|
1002
1804
|
if (tilmeld.config.verifyEmail) {
|
|
1805
|
+
// The user needs to verify this new email address.
|
|
1003
1806
|
if (this.guid == null) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1807
|
+
// If this is the first user, they'll be made an admin, so they don't
|
|
1808
|
+
// need to verify.
|
|
1809
|
+
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1810
|
+
this.$data.secret = nanoid();
|
|
1006
1811
|
sendVerification = true;
|
|
1007
1812
|
}
|
|
1008
1813
|
}
|
|
1009
1814
|
else if (this.$originalEmail != null &&
|
|
1010
1815
|
this.$originalEmail !== this.$data.email) {
|
|
1816
|
+
// The user already has an old email address.
|
|
1011
1817
|
if (tilmeld.config.emailRateLimit !== '' &&
|
|
1012
1818
|
this.$data.emailChangeDate != null &&
|
|
1013
1819
|
this.$data.emailChangeDate >=
|
|
1014
|
-
(
|
|
1015
|
-
throw new
|
|
1016
|
-
new Date((
|
|
1820
|
+
strtotime('-' + tilmeld.config.emailRateLimit) * 1000) {
|
|
1821
|
+
throw new EmailChangeRateLimitExceededError('You already changed your email address recently. Please wait until ' +
|
|
1822
|
+
new Date(strtotime('+' + tilmeld.config.emailRateLimit, Math.floor(this.$data.emailChangeDate / 1000)) * 1000).toString() +
|
|
1017
1823
|
' to change your email address again.');
|
|
1018
1824
|
}
|
|
1019
1825
|
else {
|
|
1020
1826
|
if (this.$data.newEmailSecret == null &&
|
|
1021
1827
|
tilmeld.config.emailRateLimit !== '' &&
|
|
1828
|
+
// Make sure the user has at least the rate limit time to cancel
|
|
1829
|
+
// an email change.
|
|
1022
1830
|
(this.$data.emailChangeDate == null ||
|
|
1023
1831
|
this.$data.emailChangeDate <
|
|
1024
|
-
(
|
|
1832
|
+
strtotime('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1833
|
+
// Save the old email in case the cancel change link is clicked.
|
|
1025
1834
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1026
|
-
this.$data.cancelEmailSecret =
|
|
1835
|
+
this.$data.cancelEmailSecret = nanoid();
|
|
1027
1836
|
this.$data.emailChangeDate = Date.now();
|
|
1028
1837
|
}
|
|
1838
|
+
// Save the new address and reset to the old one (until they verify
|
|
1839
|
+
// it).
|
|
1029
1840
|
this.$data.newEmailAddress = this.$data.email;
|
|
1030
1841
|
this.$data.email = this.$originalEmail;
|
|
1031
1842
|
if (tilmeld.config.emailUsernames) {
|
|
1032
1843
|
this.$data.username = this.$data.email;
|
|
1033
1844
|
}
|
|
1034
|
-
this.$data.newEmailSecret =
|
|
1845
|
+
this.$data.newEmailSecret = nanoid();
|
|
1035
1846
|
sendVerification = true;
|
|
1036
1847
|
}
|
|
1037
1848
|
}
|
|
@@ -1039,11 +1850,15 @@ class User extends AbleObject_1.default {
|
|
|
1039
1850
|
else if (this.guid != null &&
|
|
1040
1851
|
this.$originalEmail != null &&
|
|
1041
1852
|
this.$originalEmail !== this.$data.email &&
|
|
1853
|
+
// Make sure the user has at least the rate limit time to cancel an
|
|
1854
|
+
// email change.
|
|
1042
1855
|
(this.$data.emailChangeDate == null ||
|
|
1043
1856
|
this.$data.emailChangeDate <
|
|
1044
|
-
(
|
|
1857
|
+
strtotime('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1858
|
+
// The user doesn't need to verify their new email address, but should
|
|
1859
|
+
// be able to cancel the email change from their old address.
|
|
1045
1860
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1046
|
-
this.$data.cancelEmailSecret =
|
|
1861
|
+
this.$data.cancelEmailSecret = nanoid();
|
|
1047
1862
|
sendVerification = true;
|
|
1048
1863
|
}
|
|
1049
1864
|
}
|
|
@@ -1052,72 +1867,178 @@ class User extends AbleObject_1.default {
|
|
|
1052
1867
|
}
|
|
1053
1868
|
delete this.$data.passwordTemp;
|
|
1054
1869
|
if (this.$data.password == null) {
|
|
1055
|
-
throw new
|
|
1056
|
-
}
|
|
1057
|
-
try {
|
|
1058
|
-
tilmeld.config.validatorUser(this);
|
|
1059
|
-
}
|
|
1060
|
-
catch (e) {
|
|
1061
|
-
throw new errors_1.BadDataError(e?.message);
|
|
1870
|
+
throw new BadDataError('A password is required.');
|
|
1062
1871
|
}
|
|
1063
|
-
|
|
1872
|
+
// Start transaction.
|
|
1873
|
+
const transaction = 'tilmeld-save-user-' + (this.guid || nanoid());
|
|
1064
1874
|
const nymph = this.$nymph;
|
|
1065
1875
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1066
|
-
this.$
|
|
1067
|
-
tilmeld = this
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
this.$
|
|
1876
|
+
this.$setNymph(tnymph);
|
|
1877
|
+
tilmeld = enforceTilmeld(this);
|
|
1878
|
+
const Group = tnymph.getEntityClass(GroupClass);
|
|
1879
|
+
try {
|
|
1880
|
+
let group = this.$data.group;
|
|
1881
|
+
await group?.$wake();
|
|
1882
|
+
if (group == null && this.guid == null) {
|
|
1883
|
+
if (tilmeld.config.generatePrimary) {
|
|
1884
|
+
// Generate a new primary group for the user.
|
|
1885
|
+
group = await Group.factory();
|
|
1886
|
+
group.user = this;
|
|
1887
|
+
const parent = await tnymph.getEntity({ class: Group }, {
|
|
1888
|
+
type: '&',
|
|
1889
|
+
equal: ['defaultPrimary', true],
|
|
1890
|
+
});
|
|
1891
|
+
if (parent != null) {
|
|
1892
|
+
group.parent = parent;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
else {
|
|
1896
|
+
// Add the default primary.
|
|
1897
|
+
const group = await tnymph.getEntity({ class: Group }, {
|
|
1898
|
+
type: '&',
|
|
1899
|
+
equal: ['defaultPrimary', true],
|
|
1900
|
+
});
|
|
1901
|
+
if (group != null) {
|
|
1902
|
+
this.$data.group = group;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1907
|
+
// Update the user's generated primary group.
|
|
1908
|
+
let changed = false;
|
|
1909
|
+
if (group.groupname !== this.$data.username) {
|
|
1910
|
+
group.groupname = this.$data.username;
|
|
1911
|
+
changed = true;
|
|
1912
|
+
}
|
|
1913
|
+
if (group.avatar !== this.$data.avatar) {
|
|
1914
|
+
group.avatar = this.$data.avatar;
|
|
1915
|
+
changed = true;
|
|
1916
|
+
}
|
|
1917
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1918
|
+
group.email !== this.$data.email) {
|
|
1919
|
+
group.email = this.$data.email;
|
|
1920
|
+
changed = true;
|
|
1921
|
+
}
|
|
1922
|
+
if (tilmeld.config.userFields.includes('name') &&
|
|
1923
|
+
group.name !== this.$data.name) {
|
|
1924
|
+
group.name = this.$data.name;
|
|
1925
|
+
changed = true;
|
|
1926
|
+
}
|
|
1927
|
+
if (tilmeld.config.userFields.includes('phone') &&
|
|
1928
|
+
group.phone !== this.$data.phone) {
|
|
1929
|
+
group.phone = this.$data.phone;
|
|
1930
|
+
changed = true;
|
|
1931
|
+
}
|
|
1932
|
+
if (changed || group.guid == null) {
|
|
1933
|
+
if (!(await group.$saveSkipAC())) {
|
|
1934
|
+
throw Error('Error updating primary group for user.');
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
this.$data.group = group;
|
|
1938
|
+
}
|
|
1939
|
+
if (this.$data.groups == null || this.$data.groups.length === 0) {
|
|
1940
|
+
// Add secondary groups.
|
|
1941
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1942
|
+
tilmeld.config.verifyEmail &&
|
|
1943
|
+
tilmeld.config.unverifiedAccess &&
|
|
1944
|
+
this.$data.secret != null) {
|
|
1945
|
+
// Add the default secondaries for unverified users.
|
|
1946
|
+
this.$data.groups = await tnymph.getEntities({ class: Group }, {
|
|
1947
|
+
type: '&',
|
|
1948
|
+
equal: ['unverifiedSecondary', true],
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
else {
|
|
1952
|
+
// Add the default secondaries.
|
|
1953
|
+
this.$data.groups = await tnymph.getEntities({ class: Group }, {
|
|
1954
|
+
type: '&',
|
|
1955
|
+
equal: ['defaultSecondary', true],
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1071
1959
|
try {
|
|
1072
|
-
|
|
1073
|
-
this.$data.group.avatar = this.$data.avatar;
|
|
1074
|
-
this.$data.group.email = this.$data.email;
|
|
1075
|
-
this.$data.group.name = this.$data.name;
|
|
1076
|
-
this.$data.group.phone = this.$data.phone;
|
|
1077
|
-
await this.$data.group.$saveSkipAC();
|
|
1960
|
+
tilmeld.config.validatorUser(tilmeld, this);
|
|
1078
1961
|
}
|
|
1079
1962
|
catch (e) {
|
|
1080
|
-
|
|
1081
|
-
this.$nymph = nymph;
|
|
1082
|
-
throw e;
|
|
1963
|
+
throw new BadDataError(e?.message);
|
|
1083
1964
|
}
|
|
1084
1965
|
}
|
|
1085
|
-
|
|
1966
|
+
catch (e) {
|
|
1967
|
+
await tnymph.rollback(transaction);
|
|
1968
|
+
this.$setNymph(nymph);
|
|
1969
|
+
throw e;
|
|
1970
|
+
}
|
|
1971
|
+
let ret = false;
|
|
1086
1972
|
let preGuid = this.guid;
|
|
1087
1973
|
let preCdate = this.cdate;
|
|
1088
1974
|
let preMdate = this.mdate;
|
|
1089
1975
|
try {
|
|
1976
|
+
for (let callback of this.constructor
|
|
1977
|
+
.beforeSaveCallbacks) {
|
|
1978
|
+
if (callback) {
|
|
1979
|
+
await callback(this);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1090
1982
|
ret = await super.$save();
|
|
1091
1983
|
}
|
|
1092
1984
|
catch (e) {
|
|
1093
1985
|
await tnymph.rollback(transaction);
|
|
1094
|
-
this
|
|
1986
|
+
this.guid = preGuid;
|
|
1987
|
+
this.cdate = preCdate;
|
|
1988
|
+
this.mdate = preMdate;
|
|
1989
|
+
this.$setNymph(nymph);
|
|
1095
1990
|
throw e;
|
|
1096
1991
|
}
|
|
1097
1992
|
if (ret) {
|
|
1098
1993
|
if (sendVerification) {
|
|
1994
|
+
// The email has changed, so send a new verification email.
|
|
1099
1995
|
if (!(await this.$sendEmailVerification())) {
|
|
1100
1996
|
await tnymph.rollback(transaction);
|
|
1101
1997
|
this.guid = preGuid;
|
|
1102
1998
|
this.cdate = preCdate;
|
|
1103
1999
|
this.mdate = preMdate;
|
|
1104
|
-
this.$nymph
|
|
2000
|
+
this.$setNymph(nymph);
|
|
1105
2001
|
throw new Error("Couldn't send verification email.");
|
|
1106
2002
|
}
|
|
1107
2003
|
}
|
|
1108
2004
|
this.$descendantGroups = undefined;
|
|
1109
2005
|
this.$gatekeeperCache = undefined;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
2006
|
+
try {
|
|
2007
|
+
for (let callback of this.constructor
|
|
2008
|
+
.afterSaveCallbacks) {
|
|
2009
|
+
if (callback) {
|
|
2010
|
+
await callback(this);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
catch (e) {
|
|
2015
|
+
await tnymph.rollback(transaction);
|
|
2016
|
+
this.guid = preGuid;
|
|
2017
|
+
this.cdate = preCdate;
|
|
2018
|
+
this.mdate = preMdate;
|
|
2019
|
+
this.$setNymph(nymph);
|
|
2020
|
+
throw e;
|
|
1113
2021
|
}
|
|
2022
|
+
ret = await tnymph.commit(transaction);
|
|
1114
2023
|
}
|
|
1115
2024
|
else {
|
|
1116
2025
|
await tnymph.rollback(transaction);
|
|
1117
2026
|
}
|
|
1118
|
-
this.$nymph
|
|
2027
|
+
this.$setNymph(nymph);
|
|
2028
|
+
if (ret) {
|
|
2029
|
+
this.$originalEmail = this.$data.email;
|
|
2030
|
+
this.$originalUsername = this.$data.username;
|
|
2031
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
2032
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
2033
|
+
// Update the user in the session cache.
|
|
2034
|
+
await tilmeld.fillSession(this);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1119
2037
|
return ret;
|
|
1120
2038
|
}
|
|
2039
|
+
/**
|
|
2040
|
+
* This should *never* be accessible on the client.
|
|
2041
|
+
*/
|
|
1121
2042
|
async $saveSkipAC() {
|
|
1122
2043
|
this.$skipAcWhenSaving = true;
|
|
1123
2044
|
return await this.$save();
|
|
@@ -1130,24 +2051,96 @@ class User extends AbleObject_1.default {
|
|
|
1130
2051
|
return false;
|
|
1131
2052
|
}
|
|
1132
2053
|
async $delete() {
|
|
1133
|
-
const tilmeld = this
|
|
1134
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1135
|
-
throw new
|
|
2054
|
+
const tilmeld = enforceTilmeld(this);
|
|
2055
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
2056
|
+
throw new AccessControlError("You don't have the authority to delete users.");
|
|
1136
2057
|
}
|
|
1137
|
-
if (!
|
|
1138
|
-
|
|
1139
|
-
|
|
2058
|
+
if (!this.$skipAcWhenDeleting &&
|
|
2059
|
+
!tilmeld.gatekeeper('system/admin') &&
|
|
2060
|
+
this.$data.abilities?.includes('system/admin')) {
|
|
2061
|
+
throw new AccessControlError("You don't have the authority to delete system admins.");
|
|
1140
2062
|
}
|
|
1141
|
-
if (
|
|
1142
|
-
|
|
2063
|
+
if (this.$data.username !== this.$originalUsername) {
|
|
2064
|
+
throw new AccessControlError("You can't delete a user while switching their username.");
|
|
1143
2065
|
}
|
|
1144
|
-
|
|
2066
|
+
const transaction = 'tilmeld-delete-user-' + this.guid;
|
|
2067
|
+
const nymph = this.$nymph;
|
|
2068
|
+
const tnymph = await nymph.startTransaction(transaction);
|
|
2069
|
+
this.$setNymph(tnymph);
|
|
2070
|
+
let success = false;
|
|
2071
|
+
try {
|
|
2072
|
+
for (let callback of this.constructor
|
|
2073
|
+
.beforeDeleteCallbacks) {
|
|
2074
|
+
if (callback) {
|
|
2075
|
+
await callback(this);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
2079
|
+
await this.$logout();
|
|
2080
|
+
}
|
|
2081
|
+
if (this.$data.group != null && this.$is(this.$data.group.user)) {
|
|
2082
|
+
// Read the skip ac value here, because super.$delete changes it.
|
|
2083
|
+
const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
|
|
2084
|
+
// Delete the user.
|
|
2085
|
+
success = await super.$delete();
|
|
2086
|
+
if (success) {
|
|
2087
|
+
// Delete the generated group.
|
|
2088
|
+
success = $skipAcWhenDeleting
|
|
2089
|
+
? await this.$data.group.$deleteSkipAC()
|
|
2090
|
+
: await this.$data.group.$delete();
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
else {
|
|
2094
|
+
success = await super.$delete();
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
catch (e) {
|
|
2098
|
+
await tnymph.rollback(transaction);
|
|
2099
|
+
this.$setNymph(nymph);
|
|
2100
|
+
throw e;
|
|
2101
|
+
}
|
|
2102
|
+
if (success) {
|
|
2103
|
+
try {
|
|
2104
|
+
for (let callback of this.constructor
|
|
2105
|
+
.afterDeleteCallbacks) {
|
|
2106
|
+
if (callback) {
|
|
2107
|
+
await callback(this);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
catch (e) {
|
|
2112
|
+
await tnymph.rollback(transaction);
|
|
2113
|
+
this.$setNymph(nymph);
|
|
2114
|
+
throw e;
|
|
2115
|
+
}
|
|
2116
|
+
success = await tnymph.commit(transaction);
|
|
2117
|
+
}
|
|
2118
|
+
else {
|
|
2119
|
+
await tnymph.rollback(transaction);
|
|
2120
|
+
}
|
|
2121
|
+
this.$setNymph(nymph);
|
|
2122
|
+
return success;
|
|
2123
|
+
}
|
|
2124
|
+
/*
|
|
2125
|
+
* This should *never* be accessible on the client.
|
|
2126
|
+
*/
|
|
2127
|
+
async $deleteSkipAC() {
|
|
2128
|
+
this.$skipAcWhenDeleting = true;
|
|
2129
|
+
return await this.$delete();
|
|
2130
|
+
}
|
|
2131
|
+
$tilmeldDeleteSkipAC() {
|
|
2132
|
+
if (this.$skipAcWhenDeleting) {
|
|
2133
|
+
this.$skipAcWhenDeleting = false;
|
|
2134
|
+
return true;
|
|
2135
|
+
}
|
|
2136
|
+
return false;
|
|
1145
2137
|
}
|
|
1146
2138
|
static on(event, callback) {
|
|
1147
2139
|
const prop = (event + 'Callbacks');
|
|
1148
2140
|
if (!(prop in this)) {
|
|
1149
2141
|
throw new Error('Invalid event type.');
|
|
1150
2142
|
}
|
|
2143
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1151
2144
|
this[prop].push(callback);
|
|
1152
2145
|
return () => this.off(event, callback);
|
|
1153
2146
|
}
|
|
@@ -1156,55 +2149,13 @@ class User extends AbleObject_1.default {
|
|
|
1156
2149
|
if (!(prop in this)) {
|
|
1157
2150
|
return false;
|
|
1158
2151
|
}
|
|
2152
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1159
2153
|
const i = this[prop].indexOf(callback);
|
|
1160
2154
|
if (i > -1) {
|
|
2155
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1161
2156
|
this[prop].splice(i, 1);
|
|
1162
2157
|
}
|
|
1163
2158
|
return true;
|
|
1164
2159
|
}
|
|
1165
2160
|
}
|
|
1166
|
-
exports.default = User;
|
|
1167
|
-
User.ETYPE = 'tilmeld_user';
|
|
1168
|
-
User.class = 'User';
|
|
1169
|
-
User.beforeRegisterCallbacks = [];
|
|
1170
|
-
User.afterRegisterCallbacks = [];
|
|
1171
|
-
User.beforeLoginCallbacks = [];
|
|
1172
|
-
User.afterLoginCallbacks = [];
|
|
1173
|
-
User.beforeLogoutCallbacks = [];
|
|
1174
|
-
User.afterLogoutCallbacks = [];
|
|
1175
|
-
User.DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
1176
|
-
'$checkUsername',
|
|
1177
|
-
'$checkEmail',
|
|
1178
|
-
'$checkPhone',
|
|
1179
|
-
'$getAvatar',
|
|
1180
|
-
'$register',
|
|
1181
|
-
];
|
|
1182
|
-
User.DEFAULT_PRIVATE_DATA = [
|
|
1183
|
-
'email',
|
|
1184
|
-
'originalEmail',
|
|
1185
|
-
'phone',
|
|
1186
|
-
'group',
|
|
1187
|
-
'groups',
|
|
1188
|
-
'abilities',
|
|
1189
|
-
'inheritAbilities',
|
|
1190
|
-
'recoverSecret',
|
|
1191
|
-
'recoverSecretDate',
|
|
1192
|
-
'password',
|
|
1193
|
-
'salt',
|
|
1194
|
-
'secret',
|
|
1195
|
-
'newEmailAddress',
|
|
1196
|
-
'newEmailSecret',
|
|
1197
|
-
'cancelEmailAddress',
|
|
1198
|
-
'cancelEmailSecret',
|
|
1199
|
-
'emailChangeDate',
|
|
1200
|
-
];
|
|
1201
|
-
User.DEFAULT_ALLOWLIST_DATA = [];
|
|
1202
|
-
User.clientEnabledStaticMethods = [
|
|
1203
|
-
'current',
|
|
1204
|
-
'loginUser',
|
|
1205
|
-
'sendRecovery',
|
|
1206
|
-
'recover',
|
|
1207
|
-
'getClientConfig',
|
|
1208
|
-
];
|
|
1209
|
-
User.searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
1210
2161
|
//# sourceMappingURL=User.js.map
|