@nymphjs/tilmeld 1.0.0-beta.7 → 1.0.0-beta.71
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 +330 -0
- package/README.md +14 -1
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +16 -1
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +130 -10
- package/dist/Group.js +311 -129
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +77 -2
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +237 -9
- package/dist/Tilmeld.js +569 -201
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +344 -16
- package/dist/User.js +979 -313
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +63 -2
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +250 -3
- package/dist/conf/defaults.js +146 -98
- package/dist/conf/defaults.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +22 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -2
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -2
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -2
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -2
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -26
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +102 -4
- package/src/Group.ts +274 -158
- package/src/Tilmeld.ts +383 -249
- package/src/User.test.ts +80 -4
- package/src/User.ts +1061 -382
- package/src/conf/d.ts +26 -13
- package/src/conf/defaults.ts +151 -104
- 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/index.ts +2 -0
- package/src/tilmeldJoiProps.ts +9 -9
- package/tsconfig.json +3 -3
- package/typedoc.json +4 -0
package/dist/User.js
CHANGED
|
@@ -3,18 +3,115 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const guid_1 = require("@nymphjs/guid");
|
|
6
7
|
const strtotime_1 = __importDefault(require("locutus/php/datetime/strtotime"));
|
|
7
8
|
const enc_base64_1 = __importDefault(require("crypto-js/enc-base64"));
|
|
8
9
|
const sha256_1 = __importDefault(require("crypto-js/sha256"));
|
|
9
10
|
const md5_1 = __importDefault(require("crypto-js/md5"));
|
|
10
11
|
const lodash_1 = require("lodash");
|
|
11
|
-
const
|
|
12
|
+
const otpauth_1 = require("otpauth");
|
|
13
|
+
const qrcode_1 = require("qrcode");
|
|
14
|
+
const enforceTilmeld_1 = require("./enforceTilmeld");
|
|
12
15
|
const AbleObject_1 = __importDefault(require("./AbleObject"));
|
|
13
16
|
const errors_1 = require("./errors");
|
|
17
|
+
/**
|
|
18
|
+
* A user data model.
|
|
19
|
+
*
|
|
20
|
+
* Written by Hunter Perrin for SciActive.
|
|
21
|
+
*
|
|
22
|
+
* @author Hunter Perrin <hperrin@gmail.com>
|
|
23
|
+
* @copyright SciActive Inc
|
|
24
|
+
* @see http://nymph.io/
|
|
25
|
+
*/
|
|
14
26
|
class User extends AbleObject_1.default {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The instance of Tilmeld to use for queries.
|
|
29
|
+
*/
|
|
30
|
+
static ETYPE = 'tilmeld_user';
|
|
31
|
+
static class = 'User';
|
|
32
|
+
static checkUsernameCallbacks = [];
|
|
33
|
+
static beforeRegisterCallbacks = [];
|
|
34
|
+
static afterRegisterCallbacks = [];
|
|
35
|
+
static beforeLoginCallbacks = [];
|
|
36
|
+
static afterLoginCallbacks = [];
|
|
37
|
+
static beforeLogoutCallbacks = [];
|
|
38
|
+
static afterLogoutCallbacks = [];
|
|
39
|
+
static DEFAULT_CLIENT_ENABLED_METHODS = [
|
|
40
|
+
'$checkUsername',
|
|
41
|
+
'$checkEmail',
|
|
42
|
+
'$checkPhone',
|
|
43
|
+
'$getAvatar',
|
|
44
|
+
'$register',
|
|
45
|
+
];
|
|
46
|
+
static DEFAULT_PRIVATE_DATA = [
|
|
47
|
+
// The things that are commented are there to show what is _not_ private.
|
|
48
|
+
'abilities',
|
|
49
|
+
// 'username',
|
|
50
|
+
// 'nameFirst',
|
|
51
|
+
// 'nameMiddle',
|
|
52
|
+
// 'nameLast',
|
|
53
|
+
// 'name',
|
|
54
|
+
'email',
|
|
55
|
+
// 'avatar',
|
|
56
|
+
'phone',
|
|
57
|
+
'group',
|
|
58
|
+
'groups',
|
|
59
|
+
'inheritAbilities',
|
|
60
|
+
'enabled',
|
|
61
|
+
'secret',
|
|
62
|
+
'emailChangeDate',
|
|
63
|
+
'newEmailSecret',
|
|
64
|
+
'newEmailAddress',
|
|
65
|
+
'cancelEmailSecret',
|
|
66
|
+
'cancelEmailAddress',
|
|
67
|
+
'recoverSecret',
|
|
68
|
+
'recoverSecretDate',
|
|
69
|
+
'salt',
|
|
70
|
+
'password',
|
|
71
|
+
'passwordTemp',
|
|
72
|
+
'revokeTokenDate',
|
|
73
|
+
'totpSecret',
|
|
74
|
+
];
|
|
75
|
+
static DEFAULT_ALLOWLIST_DATA = [];
|
|
76
|
+
$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
77
|
+
static clientEnabledStaticMethods = [
|
|
78
|
+
'current',
|
|
79
|
+
'loginUser',
|
|
80
|
+
'sendRecovery',
|
|
81
|
+
'recover',
|
|
82
|
+
'getClientConfig',
|
|
83
|
+
];
|
|
84
|
+
$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
85
|
+
static searchRestrictedData = [...User.DEFAULT_PRIVATE_DATA];
|
|
86
|
+
$allowlistData = [...User.DEFAULT_ALLOWLIST_DATA];
|
|
87
|
+
$allowlistTags = [];
|
|
88
|
+
/**
|
|
89
|
+
* Gatekeeper ability cache.
|
|
90
|
+
*
|
|
91
|
+
* Gatekeeper will cache the user's abilities that it calculates, so it can
|
|
92
|
+
* check faster if that user has been checked before.
|
|
93
|
+
*/
|
|
94
|
+
$gatekeeperCache;
|
|
95
|
+
/**
|
|
96
|
+
* This should only be used by the backend.
|
|
97
|
+
*/
|
|
98
|
+
$skipAcWhenSaving = false;
|
|
99
|
+
/**
|
|
100
|
+
* This should only be used by the backend.
|
|
101
|
+
*/
|
|
102
|
+
$skipAcWhenDeleting = false;
|
|
103
|
+
/**
|
|
104
|
+
* The user's group descendants.
|
|
105
|
+
*/
|
|
106
|
+
$descendantGroups;
|
|
107
|
+
/**
|
|
108
|
+
* Used to save the current email address to send verification if it changes
|
|
109
|
+
* from the frontend.
|
|
110
|
+
*
|
|
111
|
+
* If you are changing a user's email address and want to bypass email
|
|
112
|
+
* verification, don't set this.
|
|
113
|
+
*/
|
|
114
|
+
$originalEmail;
|
|
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 = (0, enforceTilmeld_1.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 = (0, enforceTilmeld_1.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
|
}
|
|
185
|
+
// Create a unique secret.
|
|
96
186
|
getUser.recoverSecret = (0, guid_1.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 = (0, enforceTilmeld_1.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
|
-
(0, strtotime_1.default)('+' +
|
|
239
|
+
(0, strtotime_1.default)('+' + 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,24 @@ class User extends AbleObject_1.default {
|
|
|
161
262
|
}
|
|
162
263
|
}
|
|
163
264
|
static getClientConfig() {
|
|
265
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
164
266
|
return {
|
|
165
|
-
regFields:
|
|
166
|
-
userFields:
|
|
167
|
-
emailUsernames:
|
|
168
|
-
allowRegistration:
|
|
169
|
-
allowUsernameChange:
|
|
170
|
-
pwRecovery:
|
|
171
|
-
verifyEmail:
|
|
172
|
-
unverifiedAccess:
|
|
267
|
+
regFields: tilmeld.config.regFields,
|
|
268
|
+
userFields: tilmeld.config.userFields,
|
|
269
|
+
emailUsernames: tilmeld.config.emailUsernames,
|
|
270
|
+
allowRegistration: tilmeld.config.allowRegistration,
|
|
271
|
+
allowUsernameChange: tilmeld.config.allowUsernameChange,
|
|
272
|
+
pwRecovery: tilmeld.config.pwRecovery,
|
|
273
|
+
verifyEmail: tilmeld.config.verifyEmail,
|
|
274
|
+
unverifiedAccess: tilmeld.config.unverifiedAccess,
|
|
173
275
|
};
|
|
174
276
|
}
|
|
175
277
|
static async loginUser(data) {
|
|
278
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
176
279
|
if (!('username' in data) || !data.username.length) {
|
|
177
280
|
return { result: false, message: 'Incorrect login/password.' };
|
|
178
281
|
}
|
|
179
|
-
const user = await
|
|
282
|
+
const user = await tilmeld.User.factoryUsername(data.username);
|
|
180
283
|
const result = await user.$login(data);
|
|
181
284
|
if (result.result) {
|
|
182
285
|
user.$updateDataProtection();
|
|
@@ -184,20 +287,68 @@ class User extends AbleObject_1.default {
|
|
|
184
287
|
}
|
|
185
288
|
return result;
|
|
186
289
|
}
|
|
290
|
+
constructor() {
|
|
291
|
+
super();
|
|
292
|
+
// Defaults.
|
|
293
|
+
this.$data.enabled = true;
|
|
294
|
+
this.$data.abilities = [];
|
|
295
|
+
this.$data.groups = [];
|
|
296
|
+
this.$data.inheritAbilities = true;
|
|
297
|
+
this.$updateDataProtection();
|
|
298
|
+
}
|
|
299
|
+
$setNymph(nymph) {
|
|
300
|
+
this.$nymph = nymph;
|
|
301
|
+
if (!this.$asleep()) {
|
|
302
|
+
if (this.$data.group && this.$data.group.$nymph !== nymph) {
|
|
303
|
+
this.$data.group.$setNymph(nymph);
|
|
304
|
+
}
|
|
305
|
+
for (const group of this.$data.groups ?? []) {
|
|
306
|
+
if (group && group.$nymph !== nymph) {
|
|
307
|
+
group.$setNymph(nymph);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async $getUniques() {
|
|
313
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
314
|
+
const uniques = [`u:${this.$data.username}`];
|
|
315
|
+
if (!tilmeld.config.emailUsernames &&
|
|
316
|
+
tilmeld.config.userFields.includes('email')) {
|
|
317
|
+
uniques.push(`e:${this.$data.email}`);
|
|
318
|
+
if (this.$data.newEmailAddress != null) {
|
|
319
|
+
uniques.push(`e:${this.$data.newEmailAddress}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return uniques;
|
|
323
|
+
}
|
|
187
324
|
async $login(data) {
|
|
188
|
-
const tilmeld = this
|
|
325
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
189
326
|
if (this.guid == null) {
|
|
190
327
|
return { result: false, message: 'Incorrect login/password.' };
|
|
191
328
|
}
|
|
192
329
|
if (!this.$data.enabled) {
|
|
193
330
|
return { result: false, message: 'This user is disabled.' };
|
|
194
331
|
}
|
|
195
|
-
if (this.$gatekeeper()) {
|
|
332
|
+
if (await this.$gatekeeper()) {
|
|
196
333
|
return { result: true, message: 'You are already logged in.' };
|
|
197
334
|
}
|
|
198
335
|
if (!this.$checkPassword(data.password)) {
|
|
199
336
|
return { result: false, message: 'Incorrect login/password.' };
|
|
200
337
|
}
|
|
338
|
+
if (this.$data.totpSecret != null) {
|
|
339
|
+
if (data.code == null ||
|
|
340
|
+
typeof data.code !== 'string' ||
|
|
341
|
+
data.code.length !== 6) {
|
|
342
|
+
return {
|
|
343
|
+
result: false,
|
|
344
|
+
needTOTP: true,
|
|
345
|
+
message: 'You need to provide a 2FA code.',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (!this.$checkTOTPCode(data.code)) {
|
|
349
|
+
return { result: false, message: 'Incorrect 2FA code.' };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
201
352
|
try {
|
|
202
353
|
for (let callback of this.constructor
|
|
203
354
|
.beforeLoginCallbacks) {
|
|
@@ -212,7 +363,9 @@ class User extends AbleObject_1.default {
|
|
|
212
363
|
message: e.message,
|
|
213
364
|
};
|
|
214
365
|
}
|
|
215
|
-
|
|
366
|
+
this.$nymph.config.debugLog('tilmeld', `User login for "${this.$data.username}".`);
|
|
367
|
+
// Authentication was successful, attempt to login.
|
|
368
|
+
if (!(await tilmeld.login(this, true))) {
|
|
216
369
|
return { result: false, message: 'Incorrect login/password.' };
|
|
217
370
|
}
|
|
218
371
|
for (let callback of this.constructor
|
|
@@ -221,10 +374,73 @@ class User extends AbleObject_1.default {
|
|
|
221
374
|
await callback(this);
|
|
222
375
|
}
|
|
223
376
|
}
|
|
377
|
+
// Login was successful.
|
|
224
378
|
return { result: true, message: 'You are logged in.' };
|
|
225
379
|
}
|
|
380
|
+
async $switchUser(data) {
|
|
381
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
382
|
+
if (this.guid == null) {
|
|
383
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
384
|
+
}
|
|
385
|
+
let user = tilmeld.User.current();
|
|
386
|
+
if (!user || user.guid == null) {
|
|
387
|
+
return { result: false, message: "You're not logged in." };
|
|
388
|
+
}
|
|
389
|
+
const isCurrentUser = user != null && user.$is(this);
|
|
390
|
+
if (isCurrentUser) {
|
|
391
|
+
return { result: false, message: "You can't switch to yourself." };
|
|
392
|
+
}
|
|
393
|
+
const toAbilities = this.$data.abilities || [];
|
|
394
|
+
const fromAbilities = user.abilities || [];
|
|
395
|
+
if (!fromAbilities.includes('tilmeld/switch') &&
|
|
396
|
+
!fromAbilities.includes('system/admin')) {
|
|
397
|
+
return {
|
|
398
|
+
result: false,
|
|
399
|
+
message: "You don't have permission to switch users.",
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (toAbilities.includes('system/admin') ||
|
|
403
|
+
toAbilities.includes('tilmeld/admin')) {
|
|
404
|
+
return { result: false, message: "You can't switch to an admin." };
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
for (let callback of this.constructor
|
|
408
|
+
.beforeLoginCallbacks) {
|
|
409
|
+
if (callback) {
|
|
410
|
+
await callback(this, {
|
|
411
|
+
username: this.$data.username || '',
|
|
412
|
+
password: '',
|
|
413
|
+
...(data || {}),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
return {
|
|
420
|
+
result: false,
|
|
421
|
+
message: e.message,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
this.$nymph.config.debugLog('tilmeld', `User login switch for "${user.username}" to "${this.$data.username}".`);
|
|
425
|
+
// Authentication was successful, attempt to login.
|
|
426
|
+
if (!(await tilmeld.loginSwitch(this, true))) {
|
|
427
|
+
return { result: false, message: 'Incorrect login/password.' };
|
|
428
|
+
}
|
|
429
|
+
for (let callback of this.constructor
|
|
430
|
+
.afterLoginCallbacks) {
|
|
431
|
+
if (callback) {
|
|
432
|
+
await callback(this);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Login was successful.
|
|
436
|
+
return { result: true, message: 'You are logged in.' };
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Log a user out of the system.
|
|
440
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
441
|
+
*/
|
|
226
442
|
async $logout() {
|
|
227
|
-
const tilmeld = this
|
|
443
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
228
444
|
try {
|
|
229
445
|
for (let callback of this.constructor
|
|
230
446
|
.beforeLogoutCallbacks) {
|
|
@@ -239,7 +455,8 @@ class User extends AbleObject_1.default {
|
|
|
239
455
|
message: e.message,
|
|
240
456
|
};
|
|
241
457
|
}
|
|
242
|
-
tilmeld.
|
|
458
|
+
this.$nymph.config.debugLog('tilmeld', `User logout for "${this.$data.username}".`);
|
|
459
|
+
await tilmeld.logout();
|
|
243
460
|
for (let callback of this.constructor
|
|
244
461
|
.afterLogoutCallbacks) {
|
|
245
462
|
if (callback) {
|
|
@@ -259,52 +476,87 @@ class User extends AbleObject_1.default {
|
|
|
259
476
|
(0, md5_1.default)(this.$data.email.trim().toLowerCase()).toString() +
|
|
260
477
|
'?d=identicon&s=40');
|
|
261
478
|
}
|
|
479
|
+
$getGid() {
|
|
480
|
+
if (this.$data.group) {
|
|
481
|
+
return this.$data.group.guid;
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
$getGids() {
|
|
486
|
+
if (this.$data.groups) {
|
|
487
|
+
return this.$data.groups
|
|
488
|
+
.map((group) => group.guid)
|
|
489
|
+
.filter((guid) => guid != null);
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get the user's group descendants.
|
|
495
|
+
*/
|
|
262
496
|
async $getDescendantGroups() {
|
|
263
497
|
if (this.$descendantGroups == null) {
|
|
264
498
|
this.$descendantGroups = [];
|
|
265
499
|
if (this.$data.group != null) {
|
|
500
|
+
await this.$data.group.$wake();
|
|
266
501
|
this.$descendantGroups = await this.$data.group.$getDescendants();
|
|
267
502
|
}
|
|
503
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
268
504
|
for (let curGroup of this.$data.groups ?? []) {
|
|
269
505
|
this.$descendantGroups = this.$descendantGroups?.concat(await curGroup.$getDescendants());
|
|
270
506
|
}
|
|
271
507
|
}
|
|
272
508
|
return this.$descendantGroups;
|
|
273
509
|
}
|
|
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
510
|
$jsonAcceptData(input, allowConflict = false) {
|
|
287
|
-
const tilmeld = this
|
|
288
|
-
this.$
|
|
511
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
512
|
+
this.$check();
|
|
289
513
|
if ('abilities' in input.data &&
|
|
290
|
-
input.data.abilities
|
|
291
|
-
|
|
292
|
-
tilmeld.
|
|
293
|
-
|
|
294
|
-
throw new errors_1.BadDataError("You don't have the authority to
|
|
514
|
+
input.data.abilities?.includes('system/admin') !==
|
|
515
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
516
|
+
(!tilmeld.currentUser ||
|
|
517
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
518
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
519
|
+
}
|
|
520
|
+
if ('abilities' in input.data &&
|
|
521
|
+
input.data.abilities?.includes('tilmeld/admin') !==
|
|
522
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
523
|
+
(!tilmeld.currentUser ||
|
|
524
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
525
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
526
|
+
}
|
|
527
|
+
if ('abilities' in input.data &&
|
|
528
|
+
input.data.abilities?.includes('tilmeld/switch') !==
|
|
529
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
530
|
+
(!tilmeld.currentUser ||
|
|
531
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
532
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
295
533
|
}
|
|
296
534
|
this.$originalEmail = this.$data.email;
|
|
297
535
|
super.$jsonAcceptData(input, allowConflict);
|
|
298
536
|
}
|
|
299
537
|
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
300
|
-
const tilmeld = this
|
|
301
|
-
this.$
|
|
538
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
539
|
+
this.$check();
|
|
302
540
|
if ('abilities' in patch.set &&
|
|
303
|
-
patch.set.abilities
|
|
304
|
-
|
|
305
|
-
tilmeld.
|
|
306
|
-
|
|
307
|
-
throw new errors_1.BadDataError("You don't have the authority to
|
|
541
|
+
patch.set.abilities?.includes('system/admin') !==
|
|
542
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
543
|
+
(!tilmeld.currentUser ||
|
|
544
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
545
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
546
|
+
}
|
|
547
|
+
if ('abilities' in patch.set &&
|
|
548
|
+
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
549
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
550
|
+
(!tilmeld.currentUser ||
|
|
551
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
552
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
553
|
+
}
|
|
554
|
+
if ('abilities' in patch.set &&
|
|
555
|
+
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
556
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
557
|
+
(!tilmeld.currentUser ||
|
|
558
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
559
|
+
throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
308
560
|
}
|
|
309
561
|
this.$originalEmail = this.$data.email;
|
|
310
562
|
super.$jsonAcceptPatch(patch, allowConflict);
|
|
@@ -313,8 +565,13 @@ class User extends AbleObject_1.default {
|
|
|
313
565
|
super.$putData(data, sdata);
|
|
314
566
|
this.$updateDataProtection();
|
|
315
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Update the data protection arrays for a user.
|
|
570
|
+
*
|
|
571
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
572
|
+
*/
|
|
316
573
|
$updateDataProtection(givenUser) {
|
|
317
|
-
const tilmeld = this
|
|
574
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
318
575
|
let user = givenUser ?? tilmeld.User.current();
|
|
319
576
|
this.$clientEnabledMethods = [...User.DEFAULT_CLIENT_ENABLED_METHODS];
|
|
320
577
|
this.$privateData = [...User.DEFAULT_PRIVATE_DATA];
|
|
@@ -323,75 +580,146 @@ class User extends AbleObject_1.default {
|
|
|
323
580
|
this.$privateData.push('username');
|
|
324
581
|
}
|
|
325
582
|
const isCurrentUser = user != null && user.$is(this);
|
|
583
|
+
// While loading, accessing abilities on the current user will infinitely
|
|
584
|
+
// loop, but if this is the current user, we can use its abilities.
|
|
326
585
|
const abilities = (isCurrentUser || user == null
|
|
327
586
|
? this.$data.abilities
|
|
328
587
|
: user?.abilities) ?? [];
|
|
329
588
|
const isNewUser = this.guid == null;
|
|
330
589
|
if (isCurrentUser) {
|
|
590
|
+
// Users can check to see what abilities they have.
|
|
331
591
|
this.$clientEnabledMethods.push('$gatekeeper');
|
|
332
592
|
this.$clientEnabledMethods.push('$changePassword');
|
|
593
|
+
this.$clientEnabledMethods.push('$revokeCurrentTokens');
|
|
333
594
|
this.$clientEnabledMethods.push('$logout');
|
|
334
595
|
this.$clientEnabledMethods.push('$sendEmailVerification');
|
|
596
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
597
|
+
this.$clientEnabledMethods.push('$getNewTOTPSecret');
|
|
598
|
+
this.$clientEnabledMethods.push('$saveTOTPSecret');
|
|
599
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
335
600
|
}
|
|
336
601
|
if (user != null &&
|
|
337
|
-
(abilities.
|
|
338
|
-
abilities.
|
|
339
|
-
|
|
602
|
+
(abilities.includes('tilmeld/admin') ||
|
|
603
|
+
abilities.includes('system/admin'))) {
|
|
604
|
+
// Users who can edit other users can see most of their data.
|
|
605
|
+
this.$privateData = ['password', 'salt', 'totpSecret'];
|
|
606
|
+
this.$clientEnabledMethods.push('$hasTOTPSecret');
|
|
607
|
+
this.$clientEnabledMethods.push('$removeTOTPSecret');
|
|
340
608
|
this.$allowlistData = undefined;
|
|
341
609
|
}
|
|
342
610
|
else if (isCurrentUser || isNewUser) {
|
|
611
|
+
// Users can see their own data, and edit some of it.
|
|
343
612
|
if (tilmeld.config.allowUsernameChange || isNewUser) {
|
|
344
613
|
this.$allowlistData.push('username');
|
|
345
614
|
}
|
|
346
615
|
this.$allowlistData.push('avatar');
|
|
347
|
-
if (tilmeld.config.userFields.
|
|
616
|
+
if (tilmeld.config.userFields.includes('name')) {
|
|
348
617
|
this.$allowlistData.push('nameFirst');
|
|
349
618
|
this.$allowlistData.push('nameMiddle');
|
|
350
619
|
this.$allowlistData.push('nameLast');
|
|
351
620
|
this.$allowlistData.push('name');
|
|
352
621
|
}
|
|
353
|
-
if (tilmeld.config.userFields.
|
|
622
|
+
if (tilmeld.config.userFields.includes('email')) {
|
|
354
623
|
this.$allowlistData.push('email');
|
|
355
624
|
}
|
|
356
|
-
if (tilmeld.config.userFields.
|
|
625
|
+
if (tilmeld.config.userFields.includes('phone')) {
|
|
357
626
|
this.$allowlistData.push('phone');
|
|
358
627
|
}
|
|
359
628
|
this.$privateData = [
|
|
629
|
+
// The things that are commented are there to show what is _not_ private.
|
|
630
|
+
// 'abilities',
|
|
631
|
+
// 'username',
|
|
632
|
+
// 'nameFirst',
|
|
633
|
+
// 'nameMiddle',
|
|
634
|
+
// 'nameLast',
|
|
635
|
+
// 'name',
|
|
636
|
+
// 'email',
|
|
637
|
+
// 'avatar',
|
|
638
|
+
// 'phone',
|
|
639
|
+
// 'group',
|
|
640
|
+
// 'groups',
|
|
641
|
+
// 'inheritAbilities',
|
|
642
|
+
'enabled',
|
|
360
643
|
'secret',
|
|
644
|
+
'emailChangeDate',
|
|
361
645
|
'newEmailSecret',
|
|
362
|
-
'
|
|
646
|
+
// 'newEmailAddress',
|
|
363
647
|
'cancelEmailSecret',
|
|
364
|
-
'
|
|
648
|
+
'cancelEmailAddress',
|
|
365
649
|
'recoverSecret',
|
|
366
650
|
'recoverSecretDate',
|
|
367
|
-
'password',
|
|
368
651
|
'salt',
|
|
652
|
+
'password',
|
|
653
|
+
'passwordTemp',
|
|
654
|
+
// 'revokeTokenDate',
|
|
655
|
+
'totpSecret',
|
|
369
656
|
];
|
|
370
657
|
}
|
|
658
|
+
if (user != null &&
|
|
659
|
+
!isCurrentUser &&
|
|
660
|
+
(abilities.includes('tilmeld/switch') ||
|
|
661
|
+
abilities.includes('system/admin'))) {
|
|
662
|
+
// Users with this ability can switch to other users.
|
|
663
|
+
this.$clientEnabledMethods.push('$switchUser');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
$updateGroupDataProtection() {
|
|
667
|
+
if (this.$data.group != null && !this.$data.group.$asleep()) {
|
|
668
|
+
this.$data.group.$updateDataProtection();
|
|
669
|
+
}
|
|
670
|
+
for (let group of this.$data.groups ?? []) {
|
|
671
|
+
if (!group.$asleep()) {
|
|
672
|
+
group.$updateDataProtection();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
371
675
|
}
|
|
372
|
-
|
|
373
|
-
|
|
676
|
+
/**
|
|
677
|
+
* Check to see if a user has an ability.
|
|
678
|
+
*
|
|
679
|
+
* This function will check both user and group abilities, if the user is
|
|
680
|
+
* marked to inherit the abilities of its group.
|
|
681
|
+
*
|
|
682
|
+
* If `ability` is undefined, it will check to see if the user is currently
|
|
683
|
+
* logged in.
|
|
684
|
+
*
|
|
685
|
+
* If the user has the "system/admin" ability, this function will return true.
|
|
686
|
+
*
|
|
687
|
+
* @param ability The ability.
|
|
688
|
+
* @returns True or false.
|
|
689
|
+
*/
|
|
690
|
+
async $gatekeeper(ability) {
|
|
691
|
+
await this.$wake();
|
|
692
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
374
693
|
if (ability == null) {
|
|
375
694
|
return tilmeld.User.current()?.$is(this) ?? false;
|
|
376
695
|
}
|
|
696
|
+
// Check the cache to see if we've already checked this user.
|
|
377
697
|
if (this.$gatekeeperCache == null) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
698
|
+
this.$gatekeeperCache = await this.$getGatekeeperCache();
|
|
699
|
+
}
|
|
700
|
+
return ((ability in this.$gatekeeperCache && !!this.$gatekeeperCache[ability]) ||
|
|
701
|
+
!!this.$data.abilities?.includes('system/admin'));
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Build a gatekeeper cache object.
|
|
705
|
+
*/
|
|
706
|
+
async $getGatekeeperCache() {
|
|
707
|
+
await this.$wake();
|
|
708
|
+
let abilities = this.$data.abilities ?? [];
|
|
709
|
+
if (this.$data.inheritAbilities) {
|
|
710
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
711
|
+
for (let curGroup of this.$data.groups ?? []) {
|
|
712
|
+
if (curGroup.enabled) {
|
|
713
|
+
abilities = abilities.concat(curGroup.abilities ?? []);
|
|
389
714
|
}
|
|
390
715
|
}
|
|
391
|
-
this.$
|
|
716
|
+
await this.$data.group?.$wake();
|
|
717
|
+
const group = this.$data.group;
|
|
718
|
+
if (group != null && group.cdate != null && group.enabled) {
|
|
719
|
+
abilities = abilities.concat(group.abilities ?? []);
|
|
720
|
+
}
|
|
392
721
|
}
|
|
393
|
-
return ((ability
|
|
394
|
-
!!this.$gatekeeperCache['system/admin']);
|
|
722
|
+
return Object.fromEntries(abilities.map((ability) => [ability, true]));
|
|
395
723
|
}
|
|
396
724
|
$clearCache() {
|
|
397
725
|
const ret = super.$clearCache();
|
|
@@ -399,8 +727,13 @@ class User extends AbleObject_1.default {
|
|
|
399
727
|
this.$gatekeeperCache = undefined;
|
|
400
728
|
return ret;
|
|
401
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Send the user email verification/change/cancellation links.
|
|
732
|
+
*
|
|
733
|
+
* @returns True on success, false on failure.
|
|
734
|
+
*/
|
|
402
735
|
async $sendEmailVerification() {
|
|
403
|
-
const tilmeld = this
|
|
736
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
404
737
|
if (this.guid == null) {
|
|
405
738
|
return false;
|
|
406
739
|
}
|
|
@@ -463,18 +796,33 @@ class User extends AbleObject_1.default {
|
|
|
463
796
|
}
|
|
464
797
|
return success;
|
|
465
798
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Add the user to a (secondary) group.
|
|
801
|
+
*
|
|
802
|
+
* @param group The group.
|
|
803
|
+
* @returns True if the user is already in the group. The resulting array of groups if the user was not.
|
|
804
|
+
*/
|
|
805
|
+
async $addGroup(group) {
|
|
806
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
807
|
+
let groups = this.$data.groups;
|
|
808
|
+
if (groups == null) {
|
|
809
|
+
groups = [];
|
|
810
|
+
}
|
|
811
|
+
if (!group.$inArray(groups)) {
|
|
812
|
+
groups.push(group);
|
|
813
|
+
this.$data.groups = groups;
|
|
814
|
+
return groups;
|
|
473
815
|
}
|
|
474
816
|
return true;
|
|
475
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Check the given password against the user's.
|
|
820
|
+
*
|
|
821
|
+
* @param password The password in question.
|
|
822
|
+
* @returns True if the passwords match, otherwise false.
|
|
823
|
+
*/
|
|
476
824
|
$checkPassword(password) {
|
|
477
|
-
const tilmeld = this
|
|
825
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
478
826
|
switch (tilmeld.config.pwMethod) {
|
|
479
827
|
case 'plain':
|
|
480
828
|
return this.$data.password === password;
|
|
@@ -486,10 +834,18 @@ class User extends AbleObject_1.default {
|
|
|
486
834
|
(0, sha256_1.default)(password + this.$data.salt).toString(enc_base64_1.default));
|
|
487
835
|
}
|
|
488
836
|
}
|
|
489
|
-
|
|
490
|
-
|
|
837
|
+
/**
|
|
838
|
+
* Remove the user from a (secondary) group.
|
|
839
|
+
*
|
|
840
|
+
* @param group The group.
|
|
841
|
+
* @returns True if the user wasn't in the group. The resulting array of groups if the user was.
|
|
842
|
+
*/
|
|
843
|
+
async $delGroup(group) {
|
|
844
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
845
|
+
let groups = this.$data.groups || [];
|
|
846
|
+
if (this.$data.groups != null && group.$inArray(groups)) {
|
|
491
847
|
const newGroups = [];
|
|
492
|
-
for (let curGroup of
|
|
848
|
+
for (let curGroup of groups) {
|
|
493
849
|
if (!group.$is(curGroup)) {
|
|
494
850
|
newGroups.push(curGroup);
|
|
495
851
|
}
|
|
@@ -499,7 +855,13 @@ class User extends AbleObject_1.default {
|
|
|
499
855
|
}
|
|
500
856
|
return true;
|
|
501
857
|
}
|
|
502
|
-
|
|
858
|
+
/**
|
|
859
|
+
* Check whether the user is in a (primary or secondary) group.
|
|
860
|
+
*
|
|
861
|
+
* @param group The group, or the group's GUID.
|
|
862
|
+
* @returns True or false.
|
|
863
|
+
*/
|
|
864
|
+
async $inGroup(group) {
|
|
503
865
|
if (typeof group === 'string') {
|
|
504
866
|
if (this.$data.group?.guid === group) {
|
|
505
867
|
return true;
|
|
@@ -513,35 +875,66 @@ class User extends AbleObject_1.default {
|
|
|
513
875
|
}
|
|
514
876
|
return (group.$is(this.$data.group) || group.$inArray(this.$data.groups ?? []));
|
|
515
877
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
878
|
+
/**
|
|
879
|
+
* Check whether the user is a descendant of a group.
|
|
880
|
+
*
|
|
881
|
+
* @param group The group, or the group's GUID.
|
|
882
|
+
* @returns True or false.
|
|
883
|
+
*/
|
|
884
|
+
async $isDescendant(group) {
|
|
885
|
+
// Check to see if the user is in a descendant group of the given group.
|
|
886
|
+
await this.$data.group?.$wake();
|
|
887
|
+
if (this.$data.group != null &&
|
|
888
|
+
this.$data.group?.cdate != null &&
|
|
889
|
+
(await this.$data.group?.$isDescendant(group))) {
|
|
519
890
|
return true;
|
|
520
891
|
}
|
|
892
|
+
await Promise.all(this.$data.groups?.map((e) => e.$wake()) || []);
|
|
521
893
|
for (let curGroup of this.$data.groups ?? []) {
|
|
522
|
-
if (curGroup.$isDescendant(group)) {
|
|
894
|
+
if (await curGroup.$isDescendant(group)) {
|
|
523
895
|
return true;
|
|
524
896
|
}
|
|
525
897
|
}
|
|
526
898
|
return false;
|
|
527
899
|
}
|
|
900
|
+
/**
|
|
901
|
+
* A frontend accessible method to change the user's password.
|
|
902
|
+
*
|
|
903
|
+
* @param data The input data from the client.
|
|
904
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
905
|
+
*/
|
|
528
906
|
async $changePassword(data) {
|
|
907
|
+
this.$nymph.config.debugLog('tilmeld', `Password change request for "${this.$data.username}".`);
|
|
529
908
|
if (!('newPassword' in data) || !data.newPassword.length) {
|
|
530
909
|
return { result: false, message: 'Please specify a password.' };
|
|
531
910
|
}
|
|
532
911
|
if (!this.$checkPassword(data.currentPassword ?? '')) {
|
|
912
|
+
this.$nymph.config.debugLog('tilmeld', `Incorrect password for change request for "${this.$data.username}".`);
|
|
533
913
|
return { result: false, message: 'Incorrect password.' };
|
|
534
914
|
}
|
|
535
915
|
this.$data.passwordTemp = data.newPassword;
|
|
916
|
+
if (data.revokeCurrentTokens) {
|
|
917
|
+
this.$data.revokeTokenDate = Date.now();
|
|
918
|
+
}
|
|
536
919
|
if (await this.$save()) {
|
|
920
|
+
if (data.revokeCurrentTokens) {
|
|
921
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
922
|
+
await tilmeld.login(this, true);
|
|
923
|
+
}
|
|
537
924
|
return { result: true, message: 'Your password has been changed.' };
|
|
538
925
|
}
|
|
539
926
|
else {
|
|
540
927
|
return { result: false, message: "Couldn't save new password." };
|
|
541
928
|
}
|
|
542
929
|
}
|
|
930
|
+
/**
|
|
931
|
+
* Change the user's password.
|
|
932
|
+
*
|
|
933
|
+
* @param password The new password.
|
|
934
|
+
* @returns The resulting password or hash which is stored in the entity.
|
|
935
|
+
*/
|
|
543
936
|
$password(password) {
|
|
544
|
-
const tilmeld = this
|
|
937
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
545
938
|
switch (tilmeld.config.pwMethod) {
|
|
546
939
|
case 'plain':
|
|
547
940
|
delete this.$data.salt;
|
|
@@ -559,14 +952,194 @@ class User extends AbleObject_1.default {
|
|
|
559
952
|
}
|
|
560
953
|
return this.$data.password;
|
|
561
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* A frontend accessible method to revoke all currently issued tokens.
|
|
957
|
+
*
|
|
958
|
+
* @param data The input data from the client.
|
|
959
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
960
|
+
*/
|
|
961
|
+
async $revokeCurrentTokens(data) {
|
|
962
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
963
|
+
return { result: false, message: 'Incorrect password.' };
|
|
964
|
+
}
|
|
965
|
+
this.$data.revokeTokenDate = Date.now();
|
|
966
|
+
if (await this.$save()) {
|
|
967
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
968
|
+
await tilmeld.login(this, true);
|
|
969
|
+
return {
|
|
970
|
+
result: true,
|
|
971
|
+
message: 'You have logged out of all other sessions.',
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
return { result: false, message: "Couldn't save revocation date." };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async $hasTOTPSecret() {
|
|
979
|
+
return this.$data.totpSecret != null;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Check the given code against the user's TOTP secret.
|
|
983
|
+
*
|
|
984
|
+
* @param code The code in question.
|
|
985
|
+
* @returns True if the code is valid, otherwise false.
|
|
986
|
+
*/
|
|
987
|
+
$checkTOTPCode(code) {
|
|
988
|
+
if (this.$data.totpSecret == null) {
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
992
|
+
const totp = new otpauth_1.TOTP({
|
|
993
|
+
issuer: tilmeld.config.appName,
|
|
994
|
+
label: this.$data.username,
|
|
995
|
+
algorithm: 'SHA1',
|
|
996
|
+
digits: 6,
|
|
997
|
+
period: 30,
|
|
998
|
+
secret: this.$data.totpSecret,
|
|
999
|
+
});
|
|
1000
|
+
return totp.validate({ token: code, window: 2 }) != null;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* A frontend accessible method to generate a new TOTP secret.
|
|
1004
|
+
*
|
|
1005
|
+
* @returns An object with 'uri', 'qrcode', and 'secret'.
|
|
1006
|
+
*/
|
|
1007
|
+
async $getNewTOTPSecret() {
|
|
1008
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1009
|
+
if (this.$data.totpSecret != null) {
|
|
1010
|
+
throw new errors_1.BadDataError('You already have a 2FA secret.');
|
|
1011
|
+
}
|
|
1012
|
+
const secret = new otpauth_1.Secret();
|
|
1013
|
+
const totp = new otpauth_1.TOTP({
|
|
1014
|
+
issuer: tilmeld.config.appName,
|
|
1015
|
+
label: this.$data.username,
|
|
1016
|
+
algorithm: 'SHA1',
|
|
1017
|
+
digits: 6,
|
|
1018
|
+
period: 30,
|
|
1019
|
+
secret,
|
|
1020
|
+
});
|
|
1021
|
+
const uri = totp.toString();
|
|
1022
|
+
const qrcode = await (0, qrcode_1.toDataURL)(uri);
|
|
1023
|
+
return {
|
|
1024
|
+
uri,
|
|
1025
|
+
qrcode,
|
|
1026
|
+
secret: secret.base32,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* A frontend accessible method to save a TOTP secret into the user's account.
|
|
1031
|
+
*
|
|
1032
|
+
* @param data The input data from the client.
|
|
1033
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1034
|
+
*/
|
|
1035
|
+
async $saveTOTPSecret(data) {
|
|
1036
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1037
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1038
|
+
}
|
|
1039
|
+
if (this.$data.totpSecret != null) {
|
|
1040
|
+
return { result: false, message: 'You already have a 2FA secret.' };
|
|
1041
|
+
}
|
|
1042
|
+
if (data.secret == null ||
|
|
1043
|
+
typeof data.secret !== 'string' ||
|
|
1044
|
+
data.secret === '') {
|
|
1045
|
+
return { result: false, message: '2FA secret is invalid.' };
|
|
1046
|
+
}
|
|
1047
|
+
if (data.code == null ||
|
|
1048
|
+
typeof data.code !== 'string' ||
|
|
1049
|
+
data.code.length !== 6) {
|
|
1050
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1051
|
+
}
|
|
1052
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1053
|
+
const totp = new otpauth_1.TOTP({
|
|
1054
|
+
issuer: tilmeld.config.appName,
|
|
1055
|
+
label: this.$data.username,
|
|
1056
|
+
algorithm: 'SHA1',
|
|
1057
|
+
digits: 6,
|
|
1058
|
+
period: 30,
|
|
1059
|
+
secret: data.secret,
|
|
1060
|
+
});
|
|
1061
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1062
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1063
|
+
}
|
|
1064
|
+
this.$data.totpSecret = data.secret;
|
|
1065
|
+
if (await this.$save()) {
|
|
1066
|
+
return { result: true, message: 'Your two factor secret is now set.' };
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
return { result: false, message: "Couldn't save two factor secret." };
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* A frontend accessible method to remove the TOTP secret from the user's
|
|
1074
|
+
* account.
|
|
1075
|
+
*
|
|
1076
|
+
* @param data The input data from the client.
|
|
1077
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1078
|
+
*/
|
|
1079
|
+
async $removeTOTPSecret(data) {
|
|
1080
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1081
|
+
if (data) {
|
|
1082
|
+
if (!this.$checkPassword(data.password ?? '')) {
|
|
1083
|
+
return { result: false, message: 'Incorrect password.' };
|
|
1084
|
+
}
|
|
1085
|
+
if (data.code == null ||
|
|
1086
|
+
typeof data.code !== 'string' ||
|
|
1087
|
+
data.code.length !== 6) {
|
|
1088
|
+
return { result: false, message: '2FA code is invalid.' };
|
|
1089
|
+
}
|
|
1090
|
+
if (this.$data.totpSecret == null) {
|
|
1091
|
+
return { result: false, message: "You don't have a 2FA secret." };
|
|
1092
|
+
}
|
|
1093
|
+
const totp = new otpauth_1.TOTP({
|
|
1094
|
+
issuer: tilmeld.config.appName,
|
|
1095
|
+
label: this.$data.username,
|
|
1096
|
+
algorithm: 'SHA1',
|
|
1097
|
+
digits: 6,
|
|
1098
|
+
period: 30,
|
|
1099
|
+
secret: this.$data.totpSecret,
|
|
1100
|
+
});
|
|
1101
|
+
if (totp.validate({ token: data.code, window: 2 }) == null) {
|
|
1102
|
+
return { result: false, message: '2FA code is incorrect.' };
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1106
|
+
return {
|
|
1107
|
+
result: false,
|
|
1108
|
+
message: 'You must provide your password and 2FA code.',
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
delete this.$data.totpSecret;
|
|
1112
|
+
if (await this.$save()) {
|
|
1113
|
+
return {
|
|
1114
|
+
result: true,
|
|
1115
|
+
message: 'Two factor secret has been removed.',
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
return { result: false, message: "Couldn't remove two factor secret." };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Check that a username is valid.
|
|
1124
|
+
*
|
|
1125
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1126
|
+
*/
|
|
562
1127
|
async $checkUsername() {
|
|
563
|
-
const tilmeld = this
|
|
1128
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1129
|
+
this.$nymph.config.debugLog('tilmeld', `Username check "${this.$data.username}".`);
|
|
564
1130
|
if (!tilmeld.config.emailUsernames) {
|
|
565
1131
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
566
1132
|
return { result: false, message: 'Please specify a username.' };
|
|
567
1133
|
}
|
|
568
|
-
if (tilmeld.config.
|
|
569
|
-
|
|
1134
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1135
|
+
return {
|
|
1136
|
+
result: false,
|
|
1137
|
+
message: 'Usernames must be at least ' +
|
|
1138
|
+
tilmeld.config.minUsernameLength +
|
|
1139
|
+
' characters.',
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
570
1143
|
return {
|
|
571
1144
|
result: false,
|
|
572
1145
|
message: 'Usernames must not exceed ' +
|
|
@@ -600,6 +1173,17 @@ class User extends AbleObject_1.default {
|
|
|
600
1173
|
if (test != null) {
|
|
601
1174
|
return { result: false, message: 'That username is taken.' };
|
|
602
1175
|
}
|
|
1176
|
+
for (let callback of this.constructor
|
|
1177
|
+
.checkUsernameCallbacks) {
|
|
1178
|
+
if (callback) {
|
|
1179
|
+
const result = await callback(this, {
|
|
1180
|
+
username: this.$data.username,
|
|
1181
|
+
});
|
|
1182
|
+
if (!result.result) {
|
|
1183
|
+
return { message: 'Username is not available.', ...result };
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
603
1187
|
return {
|
|
604
1188
|
result: true,
|
|
605
1189
|
message: this.guid != null ? 'Username is valid.' : 'Username is available!',
|
|
@@ -610,8 +1194,15 @@ class User extends AbleObject_1.default {
|
|
|
610
1194
|
if (this.$data.username == null || !this.$data.username.length) {
|
|
611
1195
|
return { result: false, message: 'Please specify an email.' };
|
|
612
1196
|
}
|
|
613
|
-
if (tilmeld.config.
|
|
614
|
-
|
|
1197
|
+
if (this.$data.username.length < tilmeld.config.minUsernameLength) {
|
|
1198
|
+
return {
|
|
1199
|
+
result: false,
|
|
1200
|
+
message: 'Emails must be at least ' +
|
|
1201
|
+
tilmeld.config.minUsernameLength +
|
|
1202
|
+
' characters.',
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
if (this.$data.username.length > tilmeld.config.maxUsernameLength) {
|
|
615
1206
|
return {
|
|
616
1207
|
result: false,
|
|
617
1208
|
message: 'Emails must not exceed ' +
|
|
@@ -622,8 +1213,14 @@ class User extends AbleObject_1.default {
|
|
|
622
1213
|
return await this.$checkEmail();
|
|
623
1214
|
}
|
|
624
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Check that an email is unique.
|
|
1218
|
+
*
|
|
1219
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1220
|
+
*/
|
|
625
1221
|
async $checkEmail() {
|
|
626
|
-
const tilmeld = this
|
|
1222
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1223
|
+
this.$nymph.config.debugLog('tilmeld', `Email check "${this.$data.email}".`);
|
|
627
1224
|
if (this.$data.email == null || !this.$data.email.length) {
|
|
628
1225
|
if (tilmeld.config.verifyEmail) {
|
|
629
1226
|
return { result: false, message: 'Please specify an email.' };
|
|
@@ -657,8 +1254,14 @@ class User extends AbleObject_1.default {
|
|
|
657
1254
|
message: this.guid != null ? 'Email is valid.' : 'Email address is valid!',
|
|
658
1255
|
};
|
|
659
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Check that a phone number is unique.
|
|
1259
|
+
*
|
|
1260
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
1261
|
+
*/
|
|
660
1262
|
async $checkPhone() {
|
|
661
|
-
const tilmeld = this
|
|
1263
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1264
|
+
this.$nymph.config.debugLog('tilmeld', `Phone check "${this.$data.phone}".`);
|
|
662
1265
|
if (this.$data.phone == null || !this.$data.phone.length) {
|
|
663
1266
|
return { result: false, message: 'Please specify a phone number.' };
|
|
664
1267
|
}
|
|
@@ -686,7 +1289,7 @@ class User extends AbleObject_1.default {
|
|
|
686
1289
|
};
|
|
687
1290
|
}
|
|
688
1291
|
async $register(data) {
|
|
689
|
-
let tilmeld = this
|
|
1292
|
+
let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
690
1293
|
if (!tilmeld.config.allowRegistration) {
|
|
691
1294
|
return {
|
|
692
1295
|
result: false,
|
|
@@ -701,21 +1304,6 @@ class User extends AbleObject_1.default {
|
|
|
701
1304
|
message: 'This is already a registered user.',
|
|
702
1305
|
};
|
|
703
1306
|
}
|
|
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
1307
|
if (!('password' in data) || !data.password.length) {
|
|
720
1308
|
return {
|
|
721
1309
|
result: false,
|
|
@@ -728,7 +1316,7 @@ class User extends AbleObject_1.default {
|
|
|
728
1316
|
return { ...unCheck, loggedin: false };
|
|
729
1317
|
}
|
|
730
1318
|
this.$password(data.password);
|
|
731
|
-
if (tilmeld.config.regFields.
|
|
1319
|
+
if (tilmeld.config.regFields.includes('name')) {
|
|
732
1320
|
this.$data.name =
|
|
733
1321
|
this.$data.nameFirst +
|
|
734
1322
|
(this.$data.nameMiddle == null ? '' : ' ' + this.$data.nameMiddle) +
|
|
@@ -740,59 +1328,35 @@ class User extends AbleObject_1.default {
|
|
|
740
1328
|
if (tilmeld.config.emailUsernames) {
|
|
741
1329
|
this.$data.email = this.$data.username;
|
|
742
1330
|
}
|
|
1331
|
+
// Start transaction.
|
|
743
1332
|
const transaction = 'tilmeld-register-' + (0, guid_1.nanoid)();
|
|
744
1333
|
const nymph = this.$nymph;
|
|
745
1334
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
746
|
-
this.$
|
|
747
|
-
tilmeld = this
|
|
1335
|
+
this.$setNymph(tnymph);
|
|
1336
|
+
tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1337
|
+
let message = '';
|
|
1338
|
+
let loggedin = false;
|
|
748
1339
|
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;
|
|
1340
|
+
try {
|
|
1341
|
+
for (let callback of this.constructor
|
|
1342
|
+
.beforeRegisterCallbacks) {
|
|
1343
|
+
if (callback) {
|
|
1344
|
+
await callback(this, data);
|
|
1345
|
+
}
|
|
781
1346
|
}
|
|
782
1347
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
type: '&',
|
|
792
|
-
equal: ['defaultSecondary', true],
|
|
793
|
-
});
|
|
1348
|
+
catch (e) {
|
|
1349
|
+
await tnymph.rollback(transaction);
|
|
1350
|
+
this.$setNymph(nymph);
|
|
1351
|
+
return {
|
|
1352
|
+
result: false,
|
|
1353
|
+
loggedin: false,
|
|
1354
|
+
message: e.message,
|
|
1355
|
+
};
|
|
794
1356
|
}
|
|
795
|
-
if (tilmeld.config.
|
|
1357
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1358
|
+
tilmeld.config.verifyEmail) {
|
|
1359
|
+
// The user will be enabled after verifying their e-mail address.
|
|
796
1360
|
if (!tilmeld.config.unverifiedAccess) {
|
|
797
1361
|
this.$data.enabled = false;
|
|
798
1362
|
}
|
|
@@ -800,6 +1364,8 @@ class User extends AbleObject_1.default {
|
|
|
800
1364
|
else {
|
|
801
1365
|
this.$data.enabled = true;
|
|
802
1366
|
}
|
|
1367
|
+
// If createAdmin is true and there are no other users, grant
|
|
1368
|
+
// "system/admin".
|
|
803
1369
|
let madeAdmin = false;
|
|
804
1370
|
if (tilmeld.config.createAdmin) {
|
|
805
1371
|
const otherUsers = await tnymph.getEntities({
|
|
@@ -808,13 +1374,17 @@ class User extends AbleObject_1.default {
|
|
|
808
1374
|
limit: 1,
|
|
809
1375
|
return: 'guid',
|
|
810
1376
|
});
|
|
1377
|
+
// Make sure it's not just null, cause that means an error.
|
|
811
1378
|
if (!otherUsers.length) {
|
|
812
1379
|
this.$grant('system/admin');
|
|
813
1380
|
this.$data.enabled = true;
|
|
814
1381
|
madeAdmin = true;
|
|
815
1382
|
}
|
|
816
1383
|
}
|
|
1384
|
+
this.$nymph.config.debugLog('tilmeld', `Registering new user "${this.$data.username}".`);
|
|
817
1385
|
if (await this.$saveSkipAC()) {
|
|
1386
|
+
this.$nymph.config.debugLog('tilmeld', `New user registered "${this.$data.username}".`);
|
|
1387
|
+
// Send the new user registered email.
|
|
818
1388
|
if (tilmeld.config.userRegisteredRecipient != null) {
|
|
819
1389
|
await tilmeld.config.sendEmail(tilmeld, {
|
|
820
1390
|
template: 'UserRegistered',
|
|
@@ -831,31 +1401,20 @@ class User extends AbleObject_1.default {
|
|
|
831
1401
|
},
|
|
832
1402
|
}, this);
|
|
833
1403
|
}
|
|
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
|
-
}
|
|
1404
|
+
// Finish up.
|
|
848
1405
|
if (tilmeld.config.verifyEmail &&
|
|
849
1406
|
!tilmeld.config.unverifiedAccess &&
|
|
850
|
-
!madeAdmin
|
|
1407
|
+
!madeAdmin &&
|
|
1408
|
+
tilmeld.config.userFields.includes('email')) {
|
|
851
1409
|
message +=
|
|
852
1410
|
`Almost there. An email has been sent to ${this.$data.email} ` +
|
|
853
1411
|
'with a verification link for you to finish registration.';
|
|
854
1412
|
}
|
|
855
1413
|
else if (tilmeld.config.verifyEmail &&
|
|
856
1414
|
tilmeld.config.unverifiedAccess &&
|
|
857
|
-
!madeAdmin
|
|
858
|
-
|
|
1415
|
+
!madeAdmin &&
|
|
1416
|
+
tilmeld.config.userFields.includes('email')) {
|
|
1417
|
+
if (!(await tilmeld.login(this, true))) {
|
|
859
1418
|
throw new Error('An error occurred trying to log you in.');
|
|
860
1419
|
}
|
|
861
1420
|
message +=
|
|
@@ -865,7 +1424,7 @@ class User extends AbleObject_1.default {
|
|
|
865
1424
|
loggedin = true;
|
|
866
1425
|
}
|
|
867
1426
|
else {
|
|
868
|
-
if (!tilmeld.login(this, true)) {
|
|
1427
|
+
if (!(await tilmeld.login(this, true))) {
|
|
869
1428
|
throw new Error('An error occurred trying to log you in.');
|
|
870
1429
|
}
|
|
871
1430
|
message += "You're now registered and logged in!";
|
|
@@ -880,23 +1439,11 @@ class User extends AbleObject_1.default {
|
|
|
880
1439
|
});
|
|
881
1440
|
}
|
|
882
1441
|
}
|
|
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
1442
|
}
|
|
897
1443
|
else {
|
|
1444
|
+
this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}".`);
|
|
898
1445
|
await tnymph.rollback(transaction);
|
|
899
|
-
this.$nymph
|
|
1446
|
+
this.$setNymph(nymph);
|
|
900
1447
|
return {
|
|
901
1448
|
result: false,
|
|
902
1449
|
loggedin: false,
|
|
@@ -905,22 +1452,38 @@ class User extends AbleObject_1.default {
|
|
|
905
1452
|
}
|
|
906
1453
|
}
|
|
907
1454
|
catch (e) {
|
|
1455
|
+
this.$nymph.config.debugError('tilmeld', `Error registering new user "${this.$data.username}": ${e}`);
|
|
908
1456
|
await tnymph.rollback(transaction);
|
|
909
|
-
this.$nymph
|
|
1457
|
+
this.$setNymph(nymph);
|
|
910
1458
|
throw e;
|
|
911
1459
|
}
|
|
1460
|
+
try {
|
|
1461
|
+
await tnymph.commit(transaction);
|
|
1462
|
+
this.$setNymph(nymph);
|
|
1463
|
+
await this.$nymph.tilmeld?.fillSession(this);
|
|
1464
|
+
}
|
|
1465
|
+
catch (e) {
|
|
1466
|
+
throw e;
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
result: true,
|
|
1470
|
+
loggedin,
|
|
1471
|
+
message,
|
|
1472
|
+
};
|
|
912
1473
|
}
|
|
913
1474
|
async $save() {
|
|
914
|
-
let tilmeld = this
|
|
1475
|
+
let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
915
1476
|
if (this.$data.username == null || !this.$data.username.trim().length) {
|
|
916
1477
|
return false;
|
|
917
1478
|
}
|
|
918
|
-
if (
|
|
1479
|
+
if (!this.$skipAcWhenSaving &&
|
|
1480
|
+
tilmeld.gatekeeper('tilmeld/admin') &&
|
|
919
1481
|
!tilmeld.gatekeeper('system/admin') &&
|
|
920
|
-
this.$
|
|
1482
|
+
this.$data.abilities?.includes('system/admin')) {
|
|
921
1483
|
throw new errors_1.BadDataError("You don't have the authority to modify system admins.");
|
|
922
1484
|
}
|
|
923
1485
|
let sendVerification = false;
|
|
1486
|
+
// Formatting.
|
|
924
1487
|
this.$data.username = this.$data.username.trim();
|
|
925
1488
|
if (tilmeld.config.emailUsernames) {
|
|
926
1489
|
this.$data.email = this.$data.username;
|
|
@@ -934,49 +1497,63 @@ class User extends AbleObject_1.default {
|
|
|
934
1497
|
(this.$data.nameMiddle ? ' ' + this.$data.nameMiddle : '') +
|
|
935
1498
|
(this.$data.nameLast ? ' ' + this.$data.nameLast : '');
|
|
936
1499
|
this.$data.enabled = !!this.$data.enabled;
|
|
937
|
-
|
|
1500
|
+
// Clear empty values.
|
|
1501
|
+
if (this.$data.nameFirst === '' ||
|
|
1502
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
938
1503
|
delete this.$data.nameFirst;
|
|
939
1504
|
}
|
|
940
|
-
if (this.$data.nameMiddle === ''
|
|
1505
|
+
if (this.$data.nameMiddle === '' ||
|
|
1506
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
941
1507
|
delete this.$data.nameMiddle;
|
|
942
1508
|
}
|
|
943
|
-
if (this.$data.nameLast === ''
|
|
1509
|
+
if (this.$data.nameLast === '' ||
|
|
1510
|
+
!tilmeld.config.userFields.includes('name')) {
|
|
944
1511
|
delete this.$data.nameLast;
|
|
945
1512
|
}
|
|
946
|
-
if (this.$data.name === '') {
|
|
1513
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
947
1514
|
delete this.$data.name;
|
|
948
1515
|
}
|
|
949
|
-
if (this.$data.email === ''
|
|
1516
|
+
if (this.$data.email === '' ||
|
|
1517
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
950
1518
|
delete this.$data.email;
|
|
951
1519
|
}
|
|
952
1520
|
if (this.$data.avatar === '') {
|
|
953
1521
|
delete this.$data.avatar;
|
|
954
1522
|
}
|
|
955
|
-
if (this.$data.phone === ''
|
|
1523
|
+
if (this.$data.phone === '' ||
|
|
1524
|
+
!tilmeld.config.userFields.includes('phone')) {
|
|
956
1525
|
delete this.$data.phone;
|
|
957
1526
|
}
|
|
958
|
-
if (this.$data.secret === ''
|
|
1527
|
+
if (this.$data.secret === '' ||
|
|
1528
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
959
1529
|
delete this.$data.secret;
|
|
960
1530
|
}
|
|
961
|
-
if (this.$data.emailChangeDate === 0
|
|
1531
|
+
if (this.$data.emailChangeDate === 0 ||
|
|
1532
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
962
1533
|
delete this.$data.emailChangeDate;
|
|
963
1534
|
}
|
|
964
|
-
if (this.$data.newEmailSecret === ''
|
|
1535
|
+
if (this.$data.newEmailSecret === '' ||
|
|
1536
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
965
1537
|
delete this.$data.newEmailSecret;
|
|
966
1538
|
}
|
|
967
|
-
if (this.$data.newEmailAddress === ''
|
|
1539
|
+
if (this.$data.newEmailAddress === '' ||
|
|
1540
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
968
1541
|
delete this.$data.newEmailAddress;
|
|
969
1542
|
}
|
|
970
|
-
if (this.$data.cancelEmailSecret === ''
|
|
1543
|
+
if (this.$data.cancelEmailSecret === '' ||
|
|
1544
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
971
1545
|
delete this.$data.cancelEmailSecret;
|
|
972
1546
|
}
|
|
973
|
-
if (this.$data.cancelEmailAddress === ''
|
|
1547
|
+
if (this.$data.cancelEmailAddress === '' ||
|
|
1548
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
974
1549
|
delete this.$data.cancelEmailAddress;
|
|
975
1550
|
}
|
|
976
|
-
if (this.$data.recoverSecret === ''
|
|
1551
|
+
if (this.$data.recoverSecret === '' ||
|
|
1552
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
977
1553
|
delete this.$data.recoverSecret;
|
|
978
1554
|
}
|
|
979
|
-
if (this.$data.recoverSecretDate === 0
|
|
1555
|
+
if (this.$data.recoverSecretDate === 0 ||
|
|
1556
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
980
1557
|
delete this.$data.recoverSecretDate;
|
|
981
1558
|
}
|
|
982
1559
|
if (this.$data.password === '') {
|
|
@@ -988,26 +1565,36 @@ class User extends AbleObject_1.default {
|
|
|
988
1565
|
if (this.$data.inheritAbilities === false) {
|
|
989
1566
|
delete this.$data.inheritAbilities;
|
|
990
1567
|
}
|
|
1568
|
+
// Verification.
|
|
991
1569
|
const unCheck = await this.$checkUsername();
|
|
992
1570
|
if (!unCheck.result) {
|
|
993
1571
|
throw new errors_1.BadUsernameError(unCheck.message);
|
|
994
1572
|
}
|
|
995
|
-
if (!tilmeld.config.emailUsernames
|
|
1573
|
+
if (!tilmeld.config.emailUsernames &&
|
|
1574
|
+
tilmeld.config.userFields.includes('email')) {
|
|
996
1575
|
const emCheck = await this.$checkEmail();
|
|
997
1576
|
if (!emCheck.result) {
|
|
998
1577
|
throw new errors_1.BadEmailError(emCheck.message);
|
|
999
1578
|
}
|
|
1000
1579
|
}
|
|
1001
|
-
|
|
1580
|
+
// Email changes.
|
|
1581
|
+
if (!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
1582
|
+
tilmeld.config.userFields.includes('email')) {
|
|
1583
|
+
// The user isn't an admin, so email address changes should contain some
|
|
1584
|
+
// security measures.
|
|
1002
1585
|
if (tilmeld.config.verifyEmail) {
|
|
1586
|
+
// The user needs to verify this new email address.
|
|
1003
1587
|
if (this.guid == null) {
|
|
1004
|
-
|
|
1588
|
+
// If this is the first user, they'll be made an admin, so they don't
|
|
1589
|
+
// need to verify.
|
|
1590
|
+
if (!this.$data.abilities?.includes('system/admin')) {
|
|
1005
1591
|
this.$data.secret = (0, guid_1.nanoid)();
|
|
1006
1592
|
sendVerification = true;
|
|
1007
1593
|
}
|
|
1008
1594
|
}
|
|
1009
1595
|
else if (this.$originalEmail != null &&
|
|
1010
1596
|
this.$originalEmail !== this.$data.email) {
|
|
1597
|
+
// The user already has an old email address.
|
|
1011
1598
|
if (tilmeld.config.emailRateLimit !== '' &&
|
|
1012
1599
|
this.$data.emailChangeDate != null &&
|
|
1013
1600
|
this.$data.emailChangeDate >=
|
|
@@ -1019,13 +1606,18 @@ class User extends AbleObject_1.default {
|
|
|
1019
1606
|
else {
|
|
1020
1607
|
if (this.$data.newEmailSecret == null &&
|
|
1021
1608
|
tilmeld.config.emailRateLimit !== '' &&
|
|
1609
|
+
// Make sure the user has at least the rate limit time to cancel
|
|
1610
|
+
// an email change.
|
|
1022
1611
|
(this.$data.emailChangeDate == null ||
|
|
1023
1612
|
this.$data.emailChangeDate <
|
|
1024
1613
|
(0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1614
|
+
// Save the old email in case the cancel change link is clicked.
|
|
1025
1615
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1026
1616
|
this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
|
|
1027
1617
|
this.$data.emailChangeDate = Date.now();
|
|
1028
1618
|
}
|
|
1619
|
+
// Save the new address and reset to the old one (until they verify
|
|
1620
|
+
// it).
|
|
1029
1621
|
this.$data.newEmailAddress = this.$data.email;
|
|
1030
1622
|
this.$data.email = this.$originalEmail;
|
|
1031
1623
|
if (tilmeld.config.emailUsernames) {
|
|
@@ -1039,9 +1631,13 @@ class User extends AbleObject_1.default {
|
|
|
1039
1631
|
else if (this.guid != null &&
|
|
1040
1632
|
this.$originalEmail != null &&
|
|
1041
1633
|
this.$originalEmail !== this.$data.email &&
|
|
1634
|
+
// Make sure the user has at least the rate limit time to cancel an
|
|
1635
|
+
// email change.
|
|
1042
1636
|
(this.$data.emailChangeDate == null ||
|
|
1043
1637
|
this.$data.emailChangeDate <
|
|
1044
1638
|
(0, strtotime_1.default)('-' + tilmeld.config.emailRateLimit) * 1000)) {
|
|
1639
|
+
// The user doesn't need to verify their new email address, but should
|
|
1640
|
+
// be able to cancel the email change from their old address.
|
|
1045
1641
|
this.$data.cancelEmailAddress = this.$originalEmail;
|
|
1046
1642
|
this.$data.cancelEmailSecret = (0, guid_1.nanoid)();
|
|
1047
1643
|
sendVerification = true;
|
|
@@ -1054,70 +1650,165 @@ class User extends AbleObject_1.default {
|
|
|
1054
1650
|
if (this.$data.password == null) {
|
|
1055
1651
|
throw new errors_1.BadDataError('A password is required.');
|
|
1056
1652
|
}
|
|
1057
|
-
|
|
1058
|
-
tilmeld.config.validatorUser(this);
|
|
1059
|
-
}
|
|
1060
|
-
catch (e) {
|
|
1061
|
-
throw new errors_1.BadDataError(e?.message);
|
|
1062
|
-
}
|
|
1653
|
+
// Start transaction.
|
|
1063
1654
|
const transaction = 'tilmeld-save-' + (0, guid_1.nanoid)();
|
|
1064
1655
|
const nymph = this.$nymph;
|
|
1065
1656
|
const tnymph = await this.$nymph.startTransaction(transaction);
|
|
1066
|
-
this.$
|
|
1067
|
-
tilmeld = this
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1657
|
+
this.$setNymph(tnymph);
|
|
1658
|
+
tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1659
|
+
// If the primary group is generated now, the user assigned to it won't have
|
|
1660
|
+
// a guid yet, so it needs to be assigned after the user is saved.
|
|
1661
|
+
let saveGeneratedPrimaryGroupAgain = false;
|
|
1662
|
+
try {
|
|
1663
|
+
let group = this.$data.group;
|
|
1664
|
+
await group?.$wake();
|
|
1665
|
+
if (group == null && this.guid == null) {
|
|
1666
|
+
if (tilmeld.config.generatePrimary) {
|
|
1667
|
+
// Generate a new primary group for the user.
|
|
1668
|
+
group = await tilmeld.Group.factory();
|
|
1669
|
+
// This user doesn't have a guid yet, so that's why we set
|
|
1670
|
+
// saveGeneratedPrimaryGroupAgain.
|
|
1671
|
+
group.user = this;
|
|
1672
|
+
saveGeneratedPrimaryGroupAgain = true;
|
|
1673
|
+
const parent = await tnymph.getEntity({ class: tilmeld.Group }, {
|
|
1674
|
+
type: '&',
|
|
1675
|
+
equal: ['defaultPrimary', true],
|
|
1676
|
+
});
|
|
1677
|
+
if (parent != null) {
|
|
1678
|
+
group.parent = parent;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
else {
|
|
1682
|
+
// Add the default primary.
|
|
1683
|
+
const group = await tnymph.getEntity({ class: tilmeld.Group }, {
|
|
1684
|
+
type: '&',
|
|
1685
|
+
equal: ['defaultPrimary', true],
|
|
1686
|
+
});
|
|
1687
|
+
if (group != null) {
|
|
1688
|
+
this.$data.group = group;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (group != null && group.user != null && this.$is(group.user)) {
|
|
1693
|
+
// Update the user's generated primary group.
|
|
1694
|
+
let changed = false;
|
|
1695
|
+
if (group.groupname !== this.$data.username) {
|
|
1696
|
+
group.groupname = this.$data.username;
|
|
1697
|
+
changed = true;
|
|
1698
|
+
}
|
|
1699
|
+
if (group.avatar !== this.$data.avatar) {
|
|
1700
|
+
group.avatar = this.$data.avatar;
|
|
1701
|
+
changed = true;
|
|
1702
|
+
}
|
|
1703
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1704
|
+
group.email !== this.$data.email) {
|
|
1705
|
+
group.email = this.$data.email;
|
|
1706
|
+
changed = true;
|
|
1707
|
+
}
|
|
1708
|
+
if (tilmeld.config.userFields.includes('name') &&
|
|
1709
|
+
group.name !== this.$data.name) {
|
|
1710
|
+
group.name = this.$data.name;
|
|
1711
|
+
changed = true;
|
|
1712
|
+
}
|
|
1713
|
+
if (tilmeld.config.userFields.includes('phone') &&
|
|
1714
|
+
group.phone !== this.$data.phone) {
|
|
1715
|
+
group.phone = this.$data.phone;
|
|
1716
|
+
changed = true;
|
|
1717
|
+
}
|
|
1718
|
+
if (changed) {
|
|
1719
|
+
if (!(await group.$saveSkipAC())) {
|
|
1720
|
+
throw Error('Error updating primary group for user.');
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
this.$data.group = group;
|
|
1724
|
+
}
|
|
1725
|
+
if (this.$data.groups == null || this.$data.groups.length === 0) {
|
|
1726
|
+
// Add secondary groups.
|
|
1727
|
+
if (tilmeld.config.userFields.includes('email') &&
|
|
1728
|
+
tilmeld.config.verifyEmail &&
|
|
1729
|
+
tilmeld.config.unverifiedAccess &&
|
|
1730
|
+
this.$data.secret != null) {
|
|
1731
|
+
// Add the default secondaries for unverified users.
|
|
1732
|
+
this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
|
|
1733
|
+
type: '&',
|
|
1734
|
+
equal: ['unverifiedSecondary', true],
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
// Add the default secondaries.
|
|
1739
|
+
this.$data.groups = await tnymph.getEntities({ class: tilmeld.Group }, {
|
|
1740
|
+
type: '&',
|
|
1741
|
+
equal: ['defaultSecondary', true],
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1071
1745
|
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();
|
|
1746
|
+
tilmeld.config.validatorUser(tilmeld, this);
|
|
1078
1747
|
}
|
|
1079
1748
|
catch (e) {
|
|
1080
|
-
|
|
1081
|
-
this.$nymph = nymph;
|
|
1082
|
-
throw e;
|
|
1749
|
+
throw new errors_1.BadDataError(e?.message);
|
|
1083
1750
|
}
|
|
1084
1751
|
}
|
|
1752
|
+
catch (e) {
|
|
1753
|
+
await tnymph.rollback(transaction);
|
|
1754
|
+
this.$setNymph(nymph);
|
|
1755
|
+
throw e;
|
|
1756
|
+
}
|
|
1085
1757
|
let ret;
|
|
1086
1758
|
let preGuid = this.guid;
|
|
1087
1759
|
let preCdate = this.cdate;
|
|
1088
1760
|
let preMdate = this.mdate;
|
|
1089
1761
|
try {
|
|
1090
1762
|
ret = await super.$save();
|
|
1763
|
+
if (ret && saveGeneratedPrimaryGroupAgain) {
|
|
1764
|
+
const group = this.$data.group;
|
|
1765
|
+
if (group == null) {
|
|
1766
|
+
throw Error('Generated primary group not found.');
|
|
1767
|
+
}
|
|
1768
|
+
await group.$wake();
|
|
1769
|
+
// Now this user has a GUID, so saving it on the group will succeed.
|
|
1770
|
+
group.user = this;
|
|
1771
|
+
if (!(await group.$saveSkipAC())) {
|
|
1772
|
+
throw Error("Generated primary group couldn't be saved.");
|
|
1773
|
+
}
|
|
1774
|
+
this.$data.group = group;
|
|
1775
|
+
}
|
|
1091
1776
|
}
|
|
1092
1777
|
catch (e) {
|
|
1093
1778
|
await tnymph.rollback(transaction);
|
|
1094
|
-
this.$nymph
|
|
1779
|
+
this.$setNymph(nymph);
|
|
1095
1780
|
throw e;
|
|
1096
1781
|
}
|
|
1097
1782
|
if (ret) {
|
|
1098
1783
|
if (sendVerification) {
|
|
1784
|
+
// The email has changed, so send a new verification email.
|
|
1099
1785
|
if (!(await this.$sendEmailVerification())) {
|
|
1100
1786
|
await tnymph.rollback(transaction);
|
|
1101
1787
|
this.guid = preGuid;
|
|
1102
1788
|
this.cdate = preCdate;
|
|
1103
1789
|
this.mdate = preMdate;
|
|
1104
|
-
this.$nymph
|
|
1790
|
+
this.$setNymph(nymph);
|
|
1105
1791
|
throw new Error("Couldn't send verification email.");
|
|
1106
1792
|
}
|
|
1107
1793
|
}
|
|
1108
1794
|
this.$descendantGroups = undefined;
|
|
1109
1795
|
this.$gatekeeperCache = undefined;
|
|
1110
1796
|
await tnymph.commit(transaction);
|
|
1111
|
-
|
|
1112
|
-
|
|
1797
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(nymph);
|
|
1798
|
+
if (tilmeld.User.current(true).$is(this)) {
|
|
1799
|
+
// Update the user in the session cache.
|
|
1800
|
+
await tilmeld.fillSession(this);
|
|
1113
1801
|
}
|
|
1114
1802
|
}
|
|
1115
1803
|
else {
|
|
1116
1804
|
await tnymph.rollback(transaction);
|
|
1117
1805
|
}
|
|
1118
|
-
this.$nymph
|
|
1806
|
+
this.$setNymph(nymph);
|
|
1119
1807
|
return ret;
|
|
1120
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* This should *never* be accessible on the client.
|
|
1811
|
+
*/
|
|
1121
1812
|
async $saveSkipAC() {
|
|
1122
1813
|
this.$skipAcWhenSaving = true;
|
|
1123
1814
|
return await this.$save();
|
|
@@ -1130,12 +1821,13 @@ class User extends AbleObject_1.default {
|
|
|
1130
1821
|
return false;
|
|
1131
1822
|
}
|
|
1132
1823
|
async $delete() {
|
|
1133
|
-
const tilmeld = this
|
|
1134
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1824
|
+
const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
|
|
1825
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
1135
1826
|
throw new errors_1.BadDataError("You don't have the authority to delete users.");
|
|
1136
1827
|
}
|
|
1137
|
-
if (!
|
|
1138
|
-
|
|
1828
|
+
if (!this.$skipAcWhenDeleting &&
|
|
1829
|
+
!tilmeld.gatekeeper('system/admin') &&
|
|
1830
|
+
this.$data.abilities?.includes('system/admin')) {
|
|
1139
1831
|
throw new errors_1.BadDataError("You don't have the authority to delete system admins.");
|
|
1140
1832
|
}
|
|
1141
1833
|
if (tilmeld.User.current(true).$is(this)) {
|
|
@@ -1143,11 +1835,26 @@ class User extends AbleObject_1.default {
|
|
|
1143
1835
|
}
|
|
1144
1836
|
return await super.$delete();
|
|
1145
1837
|
}
|
|
1838
|
+
/*
|
|
1839
|
+
* This should *never* be accessible on the client.
|
|
1840
|
+
*/
|
|
1841
|
+
async $deleteSkipAC() {
|
|
1842
|
+
this.$skipAcWhenDeleting = true;
|
|
1843
|
+
return await this.$delete();
|
|
1844
|
+
}
|
|
1845
|
+
$tilmeldDeleteSkipAC() {
|
|
1846
|
+
if (this.$skipAcWhenDeleting) {
|
|
1847
|
+
this.$skipAcWhenDeleting = false;
|
|
1848
|
+
return true;
|
|
1849
|
+
}
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1146
1852
|
static on(event, callback) {
|
|
1147
1853
|
const prop = (event + 'Callbacks');
|
|
1148
1854
|
if (!(prop in this)) {
|
|
1149
1855
|
throw new Error('Invalid event type.');
|
|
1150
1856
|
}
|
|
1857
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1151
1858
|
this[prop].push(callback);
|
|
1152
1859
|
return () => this.off(event, callback);
|
|
1153
1860
|
}
|
|
@@ -1156,55 +1863,14 @@ class User extends AbleObject_1.default {
|
|
|
1156
1863
|
if (!(prop in this)) {
|
|
1157
1864
|
return false;
|
|
1158
1865
|
}
|
|
1866
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1159
1867
|
const i = this[prop].indexOf(callback);
|
|
1160
1868
|
if (i > -1) {
|
|
1869
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
1161
1870
|
this[prop].splice(i, 1);
|
|
1162
1871
|
}
|
|
1163
1872
|
return true;
|
|
1164
1873
|
}
|
|
1165
1874
|
}
|
|
1166
1875
|
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
1876
|
//# sourceMappingURL=User.js.map
|