@nymphjs/tilmeld 1.0.0-beta.8 → 1.0.0-beta.81
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 +368 -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/src/Group.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
|
+
Nymph,
|
|
2
3
|
EntityData,
|
|
3
|
-
EntityInterface,
|
|
4
4
|
EntityJson,
|
|
5
5
|
EntityPatch,
|
|
6
6
|
Options,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import md5 from 'crypto-js/md5';
|
|
11
11
|
import { difference } from 'lodash';
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import { enforceTilmeld } from './enforceTilmeld';
|
|
14
14
|
import AbleObject from './AbleObject';
|
|
15
15
|
import {
|
|
16
16
|
BadDataError,
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
BadUsernameError,
|
|
19
19
|
CouldNotChangeDefaultPrimaryGroupError,
|
|
20
20
|
} from './errors';
|
|
21
|
-
import User
|
|
21
|
+
import type User from './User';
|
|
22
|
+
import type { UserData } from './User';
|
|
22
23
|
|
|
23
24
|
export type GroupData = {
|
|
24
25
|
/**
|
|
@@ -85,7 +86,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
85
86
|
/**
|
|
86
87
|
* The instance of Tilmeld to use for queries.
|
|
87
88
|
*/
|
|
88
|
-
static tilmeld: Tilmeld;
|
|
89
89
|
static ETYPE = 'tilmeld_group';
|
|
90
90
|
static class = 'Group';
|
|
91
91
|
|
|
@@ -112,16 +112,16 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
112
112
|
protected $allowlistTags?: string[] = [];
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
* This
|
|
115
|
+
* This should only be used by the backend.
|
|
116
116
|
*/
|
|
117
117
|
private $skipAcWhenSaving = false;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
/**
|
|
119
|
+
* This should only be used by the backend.
|
|
120
|
+
*/
|
|
121
|
+
private $skipAcWhenDeleting = false;
|
|
122
122
|
|
|
123
123
|
static async factoryGroupname(
|
|
124
|
-
groupname?: string
|
|
124
|
+
groupname?: string,
|
|
125
125
|
): Promise<Group & GroupData> {
|
|
126
126
|
const entity = new this();
|
|
127
127
|
if (groupname != null) {
|
|
@@ -132,7 +132,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
132
132
|
{
|
|
133
133
|
type: '&',
|
|
134
134
|
ilike: ['groupname', groupname.replace(/([\\%_])/g, (s) => `\\${s}`)],
|
|
135
|
-
}
|
|
135
|
+
},
|
|
136
136
|
);
|
|
137
137
|
if (entity != null) {
|
|
138
138
|
return entity;
|
|
@@ -141,21 +141,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
141
141
|
return entity;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
static factorySync(guid?: string): Group & GroupData {
|
|
145
|
-
return super.factorySync(guid) as Group & GroupData;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
constructor(guid?: string) {
|
|
149
|
-
super(guid);
|
|
150
|
-
|
|
151
|
-
if (this.guid == null) {
|
|
152
|
-
// Defaults.
|
|
153
|
-
this.$data.enabled = true;
|
|
154
|
-
this.$data.abilities = [];
|
|
155
|
-
this.$updateDataProtection();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
144
|
/**
|
|
160
145
|
* Get all the groups that can be assigned as primary groups.
|
|
161
146
|
*
|
|
@@ -165,16 +150,17 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
165
150
|
*/
|
|
166
151
|
public static async getPrimaryGroups(
|
|
167
152
|
options?: Options,
|
|
168
|
-
selectors?: Selector[]
|
|
153
|
+
selectors?: Selector[],
|
|
169
154
|
) {
|
|
170
|
-
|
|
155
|
+
const tilmeld = enforceTilmeld(this);
|
|
156
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
171
157
|
throw new Error("You don't have permission to do that.");
|
|
172
158
|
}
|
|
173
159
|
|
|
174
160
|
return await this.getAssignableGroups(
|
|
175
|
-
|
|
176
|
-
{ ...options, class:
|
|
177
|
-
[...(selectors ?? [])]
|
|
161
|
+
tilmeld.config.highestPrimary,
|
|
162
|
+
{ ...options, class: tilmeld.Group, return: 'entity' },
|
|
163
|
+
[...(selectors ?? [])],
|
|
178
164
|
);
|
|
179
165
|
}
|
|
180
166
|
|
|
@@ -187,24 +173,26 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
187
173
|
*/
|
|
188
174
|
public static async getSecondaryGroups(
|
|
189
175
|
options?: Options,
|
|
190
|
-
selectors?: Selector[]
|
|
176
|
+
selectors?: Selector[],
|
|
191
177
|
) {
|
|
192
|
-
|
|
178
|
+
const tilmeld = enforceTilmeld(this);
|
|
179
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
193
180
|
throw new Error("You don't have permission to do that.");
|
|
194
181
|
}
|
|
195
182
|
|
|
196
183
|
return await this.getAssignableGroups(
|
|
197
|
-
|
|
198
|
-
{ ...options, class:
|
|
199
|
-
[...(selectors ?? [])]
|
|
184
|
+
tilmeld.config.highestSecondary,
|
|
185
|
+
{ ...options, class: tilmeld.Group, return: 'entity' },
|
|
186
|
+
[...(selectors ?? [])],
|
|
200
187
|
);
|
|
201
188
|
}
|
|
202
189
|
|
|
203
190
|
private static async getAssignableGroups(
|
|
204
191
|
highestParent: string | boolean,
|
|
205
192
|
options: Options<typeof Group>,
|
|
206
|
-
selectors: Selector[]
|
|
193
|
+
selectors: Selector[],
|
|
207
194
|
) {
|
|
195
|
+
const tilmeld = enforceTilmeld(this);
|
|
208
196
|
let assignableGroups: (Group & GroupData)[] = [];
|
|
209
197
|
|
|
210
198
|
if (highestParent === false) {
|
|
@@ -212,24 +200,68 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
212
200
|
}
|
|
213
201
|
|
|
214
202
|
assignableGroups = await this.nymph.getEntities(
|
|
215
|
-
{ ...options, class:
|
|
216
|
-
...selectors
|
|
203
|
+
{ ...options, class: tilmeld.Group },
|
|
204
|
+
...selectors,
|
|
217
205
|
);
|
|
218
206
|
if (highestParent !== true) {
|
|
219
|
-
assignableGroups =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
207
|
+
assignableGroups = (
|
|
208
|
+
await Promise.all(
|
|
209
|
+
assignableGroups.map(async (group) => {
|
|
210
|
+
let curGroup = group;
|
|
211
|
+
let parent = curGroup.parent;
|
|
212
|
+
await parent?.$wake();
|
|
213
|
+
while (parent != null && parent?.cdate != null) {
|
|
214
|
+
if (parent?.guid === highestParent) {
|
|
215
|
+
return group;
|
|
216
|
+
}
|
|
217
|
+
curGroup = parent;
|
|
218
|
+
parent = curGroup.parent;
|
|
219
|
+
await parent?.$wake();
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}),
|
|
223
|
+
)
|
|
224
|
+
).filter((group) => group != null) as (Group & GroupData)[];
|
|
229
225
|
}
|
|
230
226
|
return assignableGroups;
|
|
231
227
|
}
|
|
232
228
|
|
|
229
|
+
constructor() {
|
|
230
|
+
super();
|
|
231
|
+
|
|
232
|
+
// Defaults.
|
|
233
|
+
this.$data.enabled = true;
|
|
234
|
+
this.$data.abilities = [];
|
|
235
|
+
this.$updateDataProtection();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
$setNymph(nymph: Nymph) {
|
|
239
|
+
this.$nymph = nymph;
|
|
240
|
+
if (!this.$asleep()) {
|
|
241
|
+
if (this.$data.user && this.$data.user.$nymph !== nymph) {
|
|
242
|
+
this.$data.user.$setNymph(nymph);
|
|
243
|
+
}
|
|
244
|
+
if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
|
|
245
|
+
this.$data.parent.$setNymph(nymph);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async $getUniques(): Promise<string[]> {
|
|
251
|
+
const tilmeld = enforceTilmeld(this);
|
|
252
|
+
const uniques = [`u:${this.$data.groupname}`];
|
|
253
|
+
if (
|
|
254
|
+
!tilmeld.config.emailUsernames &&
|
|
255
|
+
tilmeld.config.userFields.includes('email')
|
|
256
|
+
) {
|
|
257
|
+
uniques.push(`e:${this.$data.email}`);
|
|
258
|
+
}
|
|
259
|
+
if (this.$data.defaultPrimary) {
|
|
260
|
+
uniques.push(`o:default-primary`);
|
|
261
|
+
}
|
|
262
|
+
return uniques;
|
|
263
|
+
}
|
|
264
|
+
|
|
233
265
|
public $getAvatar() {
|
|
234
266
|
if (this.$data.avatar != null && this.$data.avatar !== '') {
|
|
235
267
|
return this.$data.avatar;
|
|
@@ -245,17 +277,42 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
245
277
|
}
|
|
246
278
|
|
|
247
279
|
public $jsonAcceptData(input: EntityJson, allowConflict = false) {
|
|
248
|
-
const tilmeld = this
|
|
249
|
-
this.$
|
|
280
|
+
const tilmeld = enforceTilmeld(this);
|
|
281
|
+
this.$check();
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
'abilities' in input.data &&
|
|
285
|
+
input.data.abilities?.includes('system/admin') !==
|
|
286
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
287
|
+
(!tilmeld.currentUser ||
|
|
288
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
289
|
+
) {
|
|
290
|
+
throw new BadDataError(
|
|
291
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
292
|
+
);
|
|
293
|
+
}
|
|
250
294
|
|
|
251
295
|
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
!tilmeld.
|
|
296
|
+
'abilities' in input.data &&
|
|
297
|
+
input.data.abilities?.includes('tilmeld/admin') !==
|
|
298
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
299
|
+
(!tilmeld.currentUser ||
|
|
300
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
256
301
|
) {
|
|
257
302
|
throw new BadDataError(
|
|
258
|
-
"You don't have the authority to
|
|
303
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
'abilities' in input.data &&
|
|
309
|
+
input.data.abilities?.includes('tilmeld/switch') !==
|
|
310
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
311
|
+
(!tilmeld.currentUser ||
|
|
312
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
313
|
+
) {
|
|
314
|
+
throw new BadDataError(
|
|
315
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
259
316
|
);
|
|
260
317
|
}
|
|
261
318
|
|
|
@@ -263,17 +320,42 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
|
|
266
|
-
const tilmeld = this
|
|
267
|
-
this.$
|
|
323
|
+
const tilmeld = enforceTilmeld(this);
|
|
324
|
+
this.$check();
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
'abilities' in patch.set &&
|
|
328
|
+
patch.set.abilities?.includes('system/admin') !==
|
|
329
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
330
|
+
(!tilmeld.currentUser ||
|
|
331
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
332
|
+
) {
|
|
333
|
+
throw new BadDataError(
|
|
334
|
+
"You don't have the authority to grant or revoke system/admin.",
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
'abilities' in patch.set &&
|
|
340
|
+
patch.set.abilities?.includes('tilmeld/admin') !==
|
|
341
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
342
|
+
(!tilmeld.currentUser ||
|
|
343
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
344
|
+
) {
|
|
345
|
+
throw new BadDataError(
|
|
346
|
+
"You don't have the authority to grant or revoke tilmeld/admin.",
|
|
347
|
+
);
|
|
348
|
+
}
|
|
268
349
|
|
|
269
350
|
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
!tilmeld.
|
|
351
|
+
'abilities' in patch.set &&
|
|
352
|
+
patch.set.abilities?.includes('tilmeld/switch') !==
|
|
353
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
354
|
+
(!tilmeld.currentUser ||
|
|
355
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))
|
|
274
356
|
) {
|
|
275
357
|
throw new BadDataError(
|
|
276
|
-
"You don't have the authority to
|
|
358
|
+
"You don't have the authority to grant or revoke tilmeld/switch.",
|
|
277
359
|
);
|
|
278
360
|
}
|
|
279
361
|
|
|
@@ -291,7 +373,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
291
373
|
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
292
374
|
*/
|
|
293
375
|
public $updateDataProtection(givenUser?: User & UserData) {
|
|
294
|
-
const tilmeld = this
|
|
376
|
+
const tilmeld = enforceTilmeld(this);
|
|
295
377
|
let user = givenUser ?? tilmeld.User.current();
|
|
296
378
|
|
|
297
379
|
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
@@ -303,8 +385,8 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
303
385
|
|
|
304
386
|
if (
|
|
305
387
|
user != null &&
|
|
306
|
-
(
|
|
307
|
-
|
|
388
|
+
(user.abilities?.includes('tilmeld/admin') ||
|
|
389
|
+
user.abilities?.includes('system/admin'))
|
|
308
390
|
) {
|
|
309
391
|
// Users who can edit groups can see their data.
|
|
310
392
|
this.$privateData = [];
|
|
@@ -312,7 +394,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
312
394
|
} else if (
|
|
313
395
|
this.$data.user != null &&
|
|
314
396
|
user != null &&
|
|
315
|
-
this.$data.user
|
|
397
|
+
this.$data.user.guid === user.guid
|
|
316
398
|
) {
|
|
317
399
|
// Users can see their group's data.
|
|
318
400
|
this.$privateData = [];
|
|
@@ -325,11 +407,13 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
325
407
|
* @param group The group, or the group's GUID.
|
|
326
408
|
* @returns True or false.
|
|
327
409
|
*/
|
|
328
|
-
public $isDescendant(
|
|
329
|
-
|
|
410
|
+
public async $isDescendant(
|
|
411
|
+
givenGroup: (Group & GroupData) | string,
|
|
412
|
+
): Promise<boolean> {
|
|
413
|
+
const tilmeld = enforceTilmeld(this);
|
|
330
414
|
let group: Group & GroupData;
|
|
331
415
|
if (typeof givenGroup === 'string') {
|
|
332
|
-
group = tilmeld.Group.
|
|
416
|
+
group = await tilmeld.Group.factory(givenGroup);
|
|
333
417
|
} else {
|
|
334
418
|
group = givenGroup;
|
|
335
419
|
}
|
|
@@ -337,13 +421,15 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
337
421
|
return false;
|
|
338
422
|
}
|
|
339
423
|
// Check to see if the group is a descendant of the given group.
|
|
340
|
-
|
|
424
|
+
const parent = this.$data.parent;
|
|
425
|
+
await parent?.$wake();
|
|
426
|
+
if (parent == null) {
|
|
341
427
|
return false;
|
|
342
428
|
}
|
|
343
|
-
if (
|
|
429
|
+
if (parent.$is(group)) {
|
|
344
430
|
return true;
|
|
345
431
|
}
|
|
346
|
-
if (
|
|
432
|
+
if (await parent.$isDescendant(group)) {
|
|
347
433
|
return true;
|
|
348
434
|
}
|
|
349
435
|
return false;
|
|
@@ -355,14 +441,14 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
355
441
|
* @returns An array of groups.
|
|
356
442
|
*/
|
|
357
443
|
public async $getChildren() {
|
|
358
|
-
const tilmeld = this
|
|
444
|
+
const tilmeld = enforceTilmeld(this);
|
|
359
445
|
return await this.$nymph.getEntities(
|
|
360
446
|
{ class: tilmeld.Group },
|
|
361
447
|
{
|
|
362
448
|
type: '&',
|
|
363
449
|
equal: ['enabled', true],
|
|
364
450
|
ref: ['parent', this],
|
|
365
|
-
}
|
|
451
|
+
},
|
|
366
452
|
);
|
|
367
453
|
}
|
|
368
454
|
|
|
@@ -373,9 +459,9 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
373
459
|
* @returns An array of groups.
|
|
374
460
|
*/
|
|
375
461
|
public async $getDescendants(
|
|
376
|
-
andSelf = false
|
|
462
|
+
andSelf = false,
|
|
377
463
|
): Promise<(Group & GroupData)[]> {
|
|
378
|
-
const tilmeld = this
|
|
464
|
+
const tilmeld = enforceTilmeld(this);
|
|
379
465
|
let groups: (Group & GroupData)[] = [];
|
|
380
466
|
const entities = await this.$nymph.getEntities(
|
|
381
467
|
{ class: tilmeld.Group },
|
|
@@ -383,7 +469,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
383
469
|
type: '&',
|
|
384
470
|
equal: ['enabled', true],
|
|
385
471
|
ref: ['parent', this],
|
|
386
|
-
}
|
|
472
|
+
},
|
|
387
473
|
);
|
|
388
474
|
for (let entity of entities) {
|
|
389
475
|
groups = (await entity.$getDescendants(true)).concat(groups);
|
|
@@ -394,37 +480,6 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
394
480
|
return groups;
|
|
395
481
|
}
|
|
396
482
|
|
|
397
|
-
/**
|
|
398
|
-
* Gets an array of the group's descendant groups.
|
|
399
|
-
*
|
|
400
|
-
* @param andSelf Include this group in the returned array.
|
|
401
|
-
* @returns An array of groups.
|
|
402
|
-
*/
|
|
403
|
-
public $getDescendantsSync(andSelf = false): (Group & GroupData)[] {
|
|
404
|
-
const tilmeld = this.$nymph.tilmeld as Tilmeld;
|
|
405
|
-
let groups: (Group & GroupData)[] = [];
|
|
406
|
-
let entity: EntityInterface | null;
|
|
407
|
-
let offset = 0;
|
|
408
|
-
do {
|
|
409
|
-
entity = this.$nymph.driver.getEntitySync(
|
|
410
|
-
{ class: tilmeld.Group, offset },
|
|
411
|
-
{
|
|
412
|
-
type: '&',
|
|
413
|
-
equal: ['enabled', true],
|
|
414
|
-
ref: ['parent', this],
|
|
415
|
-
}
|
|
416
|
-
);
|
|
417
|
-
if (entity != null) {
|
|
418
|
-
groups = entity.$getDescendantsSync(true).concat(groups);
|
|
419
|
-
}
|
|
420
|
-
offset++;
|
|
421
|
-
} while (entity != null);
|
|
422
|
-
if (andSelf) {
|
|
423
|
-
groups.push(this);
|
|
424
|
-
}
|
|
425
|
-
return groups;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
483
|
/**
|
|
429
484
|
* Get the number of parents the group has.
|
|
430
485
|
*
|
|
@@ -436,13 +491,17 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
436
491
|
*
|
|
437
492
|
* @returns The level of the group.
|
|
438
493
|
*/
|
|
439
|
-
public $getLevel() {
|
|
440
|
-
const tilmeld = this
|
|
441
|
-
let group = tilmeld.Group.
|
|
494
|
+
public async $getLevel() {
|
|
495
|
+
const tilmeld = enforceTilmeld(this);
|
|
496
|
+
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
497
|
+
let parent = group.parent;
|
|
498
|
+
await parent?.$wake();
|
|
442
499
|
let level = 0;
|
|
443
|
-
while (
|
|
500
|
+
while (parent != null && parent.cdate != null && level < 1024) {
|
|
444
501
|
level++;
|
|
445
|
-
group =
|
|
502
|
+
group = parent;
|
|
503
|
+
parent = group.parent;
|
|
504
|
+
await parent?.$wake();
|
|
446
505
|
}
|
|
447
506
|
return level;
|
|
448
507
|
}
|
|
@@ -458,9 +517,9 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
458
517
|
public async $getUsers(
|
|
459
518
|
descendants = false,
|
|
460
519
|
limit?: number,
|
|
461
|
-
offset?: number
|
|
520
|
+
offset?: number,
|
|
462
521
|
): Promise<(User & UserData)[]> {
|
|
463
|
-
const tilmeld = this
|
|
522
|
+
const tilmeld = enforceTilmeld(this);
|
|
464
523
|
let groups: (Group & GroupData)[] = [];
|
|
465
524
|
if (descendants) {
|
|
466
525
|
groups = await this.$getDescendants();
|
|
@@ -489,7 +548,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
489
548
|
group,
|
|
490
549
|
]),
|
|
491
550
|
],
|
|
492
|
-
}
|
|
551
|
+
},
|
|
493
552
|
);
|
|
494
553
|
}
|
|
495
554
|
|
|
@@ -502,15 +561,21 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
502
561
|
result: boolean;
|
|
503
562
|
message: string;
|
|
504
563
|
}> {
|
|
505
|
-
const tilmeld = this
|
|
564
|
+
const tilmeld = enforceTilmeld(this);
|
|
506
565
|
if (!tilmeld.config.emailUsernames) {
|
|
507
566
|
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
508
567
|
return { result: false, message: 'Please specify a groupname.' };
|
|
509
568
|
}
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
569
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
570
|
+
return {
|
|
571
|
+
result: false,
|
|
572
|
+
message:
|
|
573
|
+
'Groupnames must be at least ' +
|
|
574
|
+
tilmeld.config.minUsernameLength +
|
|
575
|
+
' characters.',
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
514
579
|
return {
|
|
515
580
|
result: false,
|
|
516
581
|
message:
|
|
@@ -522,7 +587,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
522
587
|
if (
|
|
523
588
|
difference(
|
|
524
589
|
this.$data.groupname.split(''),
|
|
525
|
-
tilmeld.config.validChars.split('')
|
|
590
|
+
tilmeld.config.validChars.split(''),
|
|
526
591
|
).length
|
|
527
592
|
) {
|
|
528
593
|
return {
|
|
@@ -549,7 +614,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
549
614
|
}
|
|
550
615
|
const test = await this.$nymph.getEntity(
|
|
551
616
|
{ class: tilmeld.Group, skipAc: true },
|
|
552
|
-
selector
|
|
617
|
+
selector,
|
|
553
618
|
);
|
|
554
619
|
if (test != null) {
|
|
555
620
|
return { result: false, message: 'That groupname is taken.' };
|
|
@@ -565,10 +630,16 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
565
630
|
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
566
631
|
return { result: false, message: 'Please specify an email.' };
|
|
567
632
|
}
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
633
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
634
|
+
return {
|
|
635
|
+
result: false,
|
|
636
|
+
message:
|
|
637
|
+
'Emails must be at least ' +
|
|
638
|
+
tilmeld.config.minUsernameLength +
|
|
639
|
+
' characters.',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
572
643
|
return {
|
|
573
644
|
result: false,
|
|
574
645
|
message:
|
|
@@ -588,7 +659,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
588
659
|
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
589
660
|
*/
|
|
590
661
|
public async $checkEmail(): Promise<{ result: boolean; message: string }> {
|
|
591
|
-
const tilmeld = this
|
|
662
|
+
const tilmeld = enforceTilmeld(this);
|
|
592
663
|
if (this.$data.email === '') {
|
|
593
664
|
return { result: true, message: '' };
|
|
594
665
|
}
|
|
@@ -610,7 +681,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
610
681
|
}
|
|
611
682
|
const test = await this.$nymph.getEntity(
|
|
612
683
|
{ class: tilmeld.Group, skipAc: true },
|
|
613
|
-
selector
|
|
684
|
+
selector,
|
|
614
685
|
);
|
|
615
686
|
if (test != null) {
|
|
616
687
|
return {
|
|
@@ -627,7 +698,7 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
627
698
|
}
|
|
628
699
|
|
|
629
700
|
public async $save() {
|
|
630
|
-
const tilmeld = this
|
|
701
|
+
const tilmeld = enforceTilmeld(this);
|
|
631
702
|
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
632
703
|
return false;
|
|
633
704
|
}
|
|
@@ -643,16 +714,22 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
643
714
|
this.$data.enabled = !!this.$data.enabled;
|
|
644
715
|
|
|
645
716
|
// Clear empty values.
|
|
646
|
-
if (this.$data.name === '') {
|
|
717
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
647
718
|
delete this.$data.name;
|
|
648
719
|
}
|
|
649
|
-
if (
|
|
720
|
+
if (
|
|
721
|
+
this.$data.email === '' ||
|
|
722
|
+
!tilmeld.config.userFields.includes('email')
|
|
723
|
+
) {
|
|
650
724
|
delete this.$data.email;
|
|
651
725
|
}
|
|
652
726
|
if (this.$data.avatar === '') {
|
|
653
727
|
delete this.$data.avatar;
|
|
654
728
|
}
|
|
655
|
-
if (
|
|
729
|
+
if (
|
|
730
|
+
this.$data.phone === '' ||
|
|
731
|
+
!tilmeld.config.userFields.includes('phone')
|
|
732
|
+
) {
|
|
656
733
|
delete this.$data.phone;
|
|
657
734
|
}
|
|
658
735
|
if (this.$data.parent == null) {
|
|
@@ -671,10 +748,12 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
671
748
|
delete this.$data.unverifiedSecondary;
|
|
672
749
|
}
|
|
673
750
|
|
|
674
|
-
// Groups should never have 'system/admin'
|
|
751
|
+
// Groups should never have 'system/admin', 'tilmeld/admin', or
|
|
752
|
+
// 'tilmeld/switch' abilities.
|
|
675
753
|
this.$data.abilities = difference(this.$data.abilities ?? [], [
|
|
676
754
|
'system/admin',
|
|
677
755
|
'tilmeld/admin',
|
|
756
|
+
'tilmeld/switch',
|
|
678
757
|
null,
|
|
679
758
|
undefined,
|
|
680
759
|
]) as string[];
|
|
@@ -684,7 +763,10 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
684
763
|
if (!unCheck.result) {
|
|
685
764
|
throw new BadUsernameError(unCheck.message);
|
|
686
765
|
}
|
|
687
|
-
if (
|
|
766
|
+
if (
|
|
767
|
+
!tilmeld.config.emailUsernames &&
|
|
768
|
+
tilmeld.config.userFields.includes('email')
|
|
769
|
+
) {
|
|
688
770
|
const emCheck = await this.$checkEmail();
|
|
689
771
|
if (!emCheck.result) {
|
|
690
772
|
throw new BadEmailError(emCheck.message);
|
|
@@ -692,19 +774,21 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
692
774
|
}
|
|
693
775
|
|
|
694
776
|
// Validate group parent. Make sure it's not a descendant of this group.
|
|
777
|
+
const parent = this.$data.parent;
|
|
778
|
+
await parent?.$wake();
|
|
695
779
|
if (
|
|
696
|
-
|
|
697
|
-
(
|
|
698
|
-
this.$is(
|
|
699
|
-
|
|
780
|
+
parent &&
|
|
781
|
+
(parent.cdate == null ||
|
|
782
|
+
this.$is(parent) ||
|
|
783
|
+
(await parent.$isDescendant(this)))
|
|
700
784
|
) {
|
|
701
785
|
throw new BadDataError(
|
|
702
|
-
"Group parent can't be itself or descendant of itself."
|
|
786
|
+
"Group parent can't be itself or descendant of itself.",
|
|
703
787
|
);
|
|
704
788
|
}
|
|
705
789
|
|
|
706
790
|
try {
|
|
707
|
-
tilmeld.config.validatorGroup(this);
|
|
791
|
+
tilmeld.config.validatorGroup(tilmeld, this);
|
|
708
792
|
} catch (e: any) {
|
|
709
793
|
throw new BadDataError(e?.message);
|
|
710
794
|
}
|
|
@@ -713,14 +797,18 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
713
797
|
if (this.$data.defaultPrimary) {
|
|
714
798
|
const currentPrimary = await this.$nymph.getEntity(
|
|
715
799
|
{ class: tilmeld.Group },
|
|
716
|
-
{ type: '&', truthy: 'defaultPrimary' }
|
|
800
|
+
{ type: '&', truthy: 'defaultPrimary' },
|
|
717
801
|
);
|
|
718
802
|
if (currentPrimary != null && !this.$is(currentPrimary)) {
|
|
719
803
|
currentPrimary.defaultPrimary = false;
|
|
720
|
-
if (
|
|
804
|
+
if (
|
|
805
|
+
this.$skipAcWhenSaving
|
|
806
|
+
? !(await currentPrimary.$saveSkipAC())
|
|
807
|
+
: !(await currentPrimary.$save())
|
|
808
|
+
) {
|
|
721
809
|
throw new CouldNotChangeDefaultPrimaryGroupError(
|
|
722
810
|
'Could not change new user primary group from ' +
|
|
723
|
-
`${currentPrimary.groupname}
|
|
811
|
+
`${currentPrimary.groupname}.`,
|
|
724
812
|
);
|
|
725
813
|
}
|
|
726
814
|
}
|
|
@@ -746,24 +834,28 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
746
834
|
}
|
|
747
835
|
|
|
748
836
|
public async $delete() {
|
|
749
|
-
let tilmeld = this
|
|
750
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
837
|
+
let tilmeld = enforceTilmeld(this);
|
|
838
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
751
839
|
throw new BadDataError("You don't have the authority to delete groups.");
|
|
752
840
|
}
|
|
753
841
|
|
|
754
842
|
const transaction = 'tilmeld-delete-' + this.guid;
|
|
755
843
|
const nymph = this.$nymph;
|
|
756
844
|
const tnymph = await nymph.startTransaction(transaction);
|
|
757
|
-
this.$
|
|
758
|
-
tilmeld = this
|
|
845
|
+
this.$setNymph(tnymph);
|
|
846
|
+
tilmeld = enforceTilmeld(this);
|
|
759
847
|
|
|
760
848
|
// Delete descendants.
|
|
761
849
|
const descendants = await this.$getDescendants();
|
|
762
850
|
if (descendants.length) {
|
|
763
851
|
for (let curGroup of descendants) {
|
|
764
|
-
if (
|
|
852
|
+
if (
|
|
853
|
+
this.$skipAcWhenDeleting
|
|
854
|
+
? !(await curGroup.$deleteSkipAC())
|
|
855
|
+
: !(await curGroup.$delete())
|
|
856
|
+
) {
|
|
765
857
|
await tnymph.rollback(transaction);
|
|
766
|
-
this.$nymph
|
|
858
|
+
this.$setNymph(nymph);
|
|
767
859
|
return false;
|
|
768
860
|
}
|
|
769
861
|
}
|
|
@@ -778,13 +870,17 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
778
870
|
{
|
|
779
871
|
type: '&',
|
|
780
872
|
ref: ['group', this],
|
|
781
|
-
}
|
|
873
|
+
},
|
|
782
874
|
);
|
|
783
875
|
for (let user of primaryUsers) {
|
|
784
876
|
delete user.group;
|
|
785
|
-
if (
|
|
877
|
+
if (
|
|
878
|
+
this.$skipAcWhenDeleting
|
|
879
|
+
? !(await user.$saveSkipAC())
|
|
880
|
+
: !(await user.$save())
|
|
881
|
+
) {
|
|
786
882
|
await tnymph.rollback(transaction);
|
|
787
|
-
this.$nymph
|
|
883
|
+
this.$setNymph(nymph);
|
|
788
884
|
return false;
|
|
789
885
|
}
|
|
790
886
|
}
|
|
@@ -798,11 +894,15 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
798
894
|
{
|
|
799
895
|
type: '&',
|
|
800
896
|
ref: ['groups', this],
|
|
801
|
-
}
|
|
897
|
+
},
|
|
802
898
|
);
|
|
803
899
|
for (let user of secondaryUsers) {
|
|
804
900
|
user.$delGroup(this);
|
|
805
|
-
if (
|
|
901
|
+
if (
|
|
902
|
+
this.$skipAcWhenDeleting
|
|
903
|
+
? !(await user.$saveSkipAC())
|
|
904
|
+
: !(await user.$save())
|
|
905
|
+
) {
|
|
806
906
|
await tnymph.rollback(transaction);
|
|
807
907
|
return false;
|
|
808
908
|
}
|
|
@@ -815,7 +915,23 @@ export default class Group extends AbleObject<GroupData> {
|
|
|
815
915
|
} else {
|
|
816
916
|
await tnymph.rollback(transaction);
|
|
817
917
|
}
|
|
818
|
-
this.$nymph
|
|
918
|
+
this.$setNymph(nymph);
|
|
819
919
|
return success;
|
|
820
920
|
}
|
|
921
|
+
|
|
922
|
+
/*
|
|
923
|
+
* This should *never* be accessible on the client.
|
|
924
|
+
*/
|
|
925
|
+
async $deleteSkipAC() {
|
|
926
|
+
this.$skipAcWhenDeleting = true;
|
|
927
|
+
return await this.$delete();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
$tilmeldDeleteSkipAC() {
|
|
931
|
+
if (this.$skipAcWhenDeleting) {
|
|
932
|
+
this.$skipAcWhenDeleting = false;
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
821
937
|
}
|