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