@nymphjs/tilmeld 1.0.0-alpha.3 → 1.0.0-alpha.30

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/src/Tilmeld.ts CHANGED
@@ -4,8 +4,10 @@ import {
4
4
  FormattedSelector,
5
5
  Options,
6
6
  TilmeldInterface,
7
+ TilmeldAccessLevels,
7
8
  } from '@nymphjs/nymph';
8
9
  import { Request, Response } from 'express';
10
+ import { xor } from 'lodash';
9
11
 
10
12
  import { Config, ConfigDefaults as defaults } from './conf';
11
13
  import { AccessControlError } from './errors';
@@ -23,11 +25,6 @@ import { AccessControlData } from './Tilmeld.types';
23
25
  * @see http://nymph.io/
24
26
  */
25
27
  export default class Tilmeld implements TilmeldInterface {
26
- public static NO_ACCESS = 0;
27
- public static READ_ACCESS = 1;
28
- public static WRITE_ACCESS = 2;
29
- public static FULL_ACCESS = 4;
30
-
31
28
  /**
32
29
  * The Nymph instance.
33
30
  */
@@ -117,13 +114,15 @@ export default class Tilmeld implements TilmeldInterface {
117
114
  class NymphUser extends User {}
118
115
  NymphUser.nymph = this.nymph;
119
116
  NymphUser.tilmeld = this;
117
+ (NymphUser as any).skipOnClone = true;
120
118
  this.User = NymphUser;
121
- this.nymph.setEntityClass(NymphUser.class, NymphUser);
119
+ this.nymph.addEntityClass(NymphUser);
122
120
  class NymphGroup extends Group {}
123
121
  NymphGroup.nymph = this.nymph;
124
122
  NymphGroup.tilmeld = this;
123
+ (NymphUser as any).skipOnClone = true;
125
124
  this.Group = NymphGroup;
126
- this.nymph.setEntityClass(NymphGroup.class, NymphGroup);
125
+ this.nymph.addEntityClass(NymphGroup);
127
126
 
128
127
  this.initAccessControl();
129
128
  if (this.request != null && !this.skipAuthenticate) {
@@ -149,13 +148,13 @@ export default class Tilmeld implements TilmeldInterface {
149
148
  }
150
149
 
151
150
  private initAccessControl() {
152
- const tilmeld = this;
153
-
154
151
  // Check for the skip access control option and add AC selectors.
155
152
  const handleQuery = function (
153
+ nymph: Nymph,
156
154
  options: Options,
157
155
  selectors: FormattedSelector[]
158
156
  ) {
157
+ const tilmeld = nymph.tilmeld as Tilmeld;
159
158
  if (
160
159
  !options ||
161
160
  !('skipAc' in options) ||
@@ -164,20 +163,20 @@ export default class Tilmeld implements TilmeldInterface {
164
163
  ) {
165
164
  if (options?.source === 'client') {
166
165
  if (
167
- !tilmeld.gatekeeper('tilmeld/admin') &&
166
+ !tilmeld.gatekeeper('tilmeld/admin') && // The user is not an admin.
168
167
  ((!tilmeld.config.enableUserSearch &&
169
- options.class?.factorySync() instanceof User) ||
168
+ options.class?.factorySync() instanceof User) || // The use is searching for a user and searching is disabled.
170
169
  (!tilmeld.config.enableGroupSearch &&
171
- options.class?.factorySync() instanceof Group)) &&
172
- (selectors[0] == null ||
173
- selectors[0].type !== '&' ||
174
- (!('guid' in selectors[0]) && !('equal' in selectors[0])) ||
170
+ options.class?.factorySync() instanceof Group)) && // Or the same, but for a group.
171
+ (selectors[0] == null || // No selector specified.
172
+ selectors[0].type !== '&' || // Or the type is not and.
173
+ (!('guid' in selectors[0]) && !('equal' in selectors[0])) || // Or they're searching for something other than guid or equal.
175
174
  ('guid' in selectors[0] &&
176
- selectors[0].guid?.length === 1 &&
177
- typeof selectors[0].guid?.[0] !== 'string') ||
175
+ (!selectors[0].guid?.length ||
176
+ !selectors[0].guid?.[0]?.length)) || // Or they're not searching for a specific guid.
178
177
  ('equal' in selectors[0] &&
179
- selectors[0].equal?.length === 1 &&
180
- selectors[0].equal?.[0]?.[0] !== 'username'))
178
+ selectors[0].equal?.[0]?.[0] !== 'username' &&
179
+ selectors[0].equal?.[0]?.[0] !== 'groupname')) // Or they're not searching for a specific username/groupname.
181
180
  ) {
182
181
  // If the user is not specifically searching for a GUID or username,
183
182
  // and they're not allowed to search, it should fail.
@@ -188,10 +187,14 @@ export default class Tilmeld implements TilmeldInterface {
188
187
  tilmeld.addAccessControlSelectors(options, selectors);
189
188
  }
190
189
  };
191
- handleQuery.tilmeld = true;
190
+ handleQuery.skipOnClone = true;
192
191
 
193
192
  // Filter entities being deleted for user permissions.
194
- const checkPermissionsDelete = function (entity: EntityInterface) {
193
+ const checkPermissionsDelete = function (
194
+ nymph: Nymph,
195
+ entity: EntityInterface
196
+ ) {
197
+ const tilmeld = nymph.tilmeld as Tilmeld;
195
198
  if (
196
199
  typeof entity.$tilmeldDeleteSkipAC === 'function' &&
197
200
  entity.$tilmeldDeleteSkipAC()
@@ -199,32 +202,35 @@ export default class Tilmeld implements TilmeldInterface {
199
202
  return;
200
203
  }
201
204
  // Test for permissions.
202
- if (tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
205
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
203
206
  throw new AccessControlError('No permission to delete.');
204
207
  }
205
208
  };
206
- checkPermissionsDelete.tilmeld = true;
209
+ checkPermissionsDelete.skipOnClone = true;
207
210
 
208
211
  // Filter entities being deleted for user permissions.
209
212
  const checkPermissionsDeleteByID = function (
213
+ nymph: Nymph,
210
214
  guid: string,
211
215
  className?: string
212
216
  ) {
213
- const entity = tilmeld.nymph.driver.getEntitySync(
214
- { class: tilmeld.nymph.getEntityClass(className ?? 'Entity') },
217
+ const entity = nymph.driver.getEntitySync(
218
+ { class: nymph.getEntityClass(className ?? 'Entity') },
215
219
  { type: '&', guid: guid }
216
220
  );
217
221
  if (entity != null) {
218
- checkPermissionsDelete(entity);
222
+ checkPermissionsDelete(nymph, entity);
219
223
  }
220
224
  };
221
- checkPermissionsDeleteByID.tilmeld = true;
225
+ checkPermissionsDeleteByID.skipOnClone = true;
222
226
 
223
227
  // Filter entities being saved for user permissions, and filter any
224
228
  // disallowed changes to AC properties.
225
229
  const checkPermissionsSaveAndFilterAcChanges = function (
230
+ nymph: Nymph,
226
231
  entity: EntityInterface & AccessControlData
227
232
  ) {
233
+ const tilmeld = nymph.tilmeld as Tilmeld;
228
234
  if (!entity) {
229
235
  return;
230
236
  }
@@ -240,15 +246,15 @@ export default class Tilmeld implements TilmeldInterface {
240
246
  // allowing a change to ac properties.
241
247
 
242
248
  const originalAc = entity.$getOriginalAcValues();
243
- const newAc = {
244
- user: entity.user ?? null,
245
- group: entity.group ?? null,
246
- acUser: entity.acUser ?? null,
247
- acGroup: entity.acGroup ?? null,
248
- acOther: entity.acOther ?? null,
249
- acRead: entity.acRead ?? null,
250
- acWrite: entity.acWrite ?? null,
251
- acFull: entity.acFull ?? null,
249
+ const newAc: AccessControlData = {
250
+ user: entity.user ?? undefined,
251
+ group: entity.group ?? undefined,
252
+ acUser: entity.acUser ?? undefined,
253
+ acGroup: entity.acGroup ?? undefined,
254
+ acOther: entity.acOther ?? undefined,
255
+ acRead: entity.acRead ?? undefined,
256
+ acWrite: entity.acWrite ?? undefined,
257
+ acFull: entity.acFull ?? undefined,
252
258
  };
253
259
 
254
260
  const setAcProperties = (acValues: { [k: string]: any }) => {
@@ -264,20 +270,112 @@ export default class Tilmeld implements TilmeldInterface {
264
270
 
265
271
  // Restore original AC properties and check permissions.
266
272
  setAcProperties(originalAc);
267
- if (tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
268
- // Only allow changes to AC properties if the user has full access.
269
- // TODO: only allow changes to `user` and `group` if tilmeld admin or
270
- // group is user's group.
273
+
274
+ function areAcPropertiesChanged(
275
+ a: AccessControlData,
276
+ b: AccessControlData
277
+ ) {
278
+ for (const name of ['user', 'group'] as ('user' | 'group')[]) {
279
+ const aVal = a[name];
280
+ const bVal = b[name];
281
+ if (aVal == null && bVal == null) {
282
+ continue;
283
+ } else if (aVal == null || bVal == null) {
284
+ return true;
285
+ }
286
+ if (!aVal.$is(bVal)) {
287
+ return true;
288
+ }
289
+ }
290
+ for (const name of ['acUser', 'acGroup', 'acOther'] as (
291
+ | 'acUser'
292
+ | 'acGroup'
293
+ | 'acOther'
294
+ )[]) {
295
+ const aVal = a[name];
296
+ const bVal = b[name];
297
+ if (aVal == null && bVal == null) {
298
+ continue;
299
+ } else if (aVal == null || bVal == null) {
300
+ return true;
301
+ }
302
+ if (aVal !== bVal) {
303
+ return true;
304
+ }
305
+ }
306
+ for (const name of ['acRead', 'acWrite', 'acFull'] as (
307
+ | 'acRead'
308
+ | 'acWrite'
309
+ | 'acFull'
310
+ )[]) {
311
+ const aVal = a[name];
312
+ const bVal = b[name];
313
+ if (aVal == null && bVal == null) {
314
+ continue;
315
+ } else if (aVal == null || bVal == null) {
316
+ return true;
317
+ }
318
+ if (aVal.length !== bVal.length) {
319
+ return true;
320
+ }
321
+ if (
322
+ xor(
323
+ aVal.map((entity) => entity.guid),
324
+ bVal.map((entity) => entity.guid)
325
+ ).length
326
+ ) {
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
332
+
333
+ if (areAcPropertiesChanged(originalAc, newAc)) {
334
+ if (
335
+ !tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)
336
+ ) {
337
+ // Only allow changes to AC properties if the user has full access.
338
+ throw new AccessControlError(
339
+ 'No permission to change access control properties.'
340
+ );
341
+ }
342
+
343
+ if (
344
+ ((originalAc.user && !originalAc.user.$is(newAc.user)) ||
345
+ (!originalAc.user &&
346
+ newAc.user &&
347
+ !newAc.user.$is(tilmeld.currentUser))) &&
348
+ !tilmeld.gatekeeper('tilmeld/admin')
349
+ ) {
350
+ throw new AccessControlError(
351
+ 'No permission to assign to another user.'
352
+ );
353
+ }
354
+
355
+ if (
356
+ newAc.group &&
357
+ !newAc.group.$is(originalAc.group) &&
358
+ !(
359
+ newAc.group.$is(tilmeld.currentUser?.group) ||
360
+ newAc.group.$inArray(tilmeld.currentUser?.groups ?? [])
361
+ ) &&
362
+ !tilmeld.gatekeeper('tilmeld/admin')
363
+ ) {
364
+ throw new AccessControlError(
365
+ 'No permission to assign to another group.'
366
+ );
367
+ }
368
+
271
369
  setAcProperties(newAc);
272
370
  }
273
371
  }
274
372
 
275
373
  // Test for permissions.
276
- if (!tilmeld.checkPermissions(entity, Tilmeld.WRITE_ACCESS)) {
374
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.WRITE_ACCESS)) {
277
375
  throw new AccessControlError('No permission to write.');
278
376
  }
279
377
  };
280
- checkPermissionsSaveAndFilterAcChanges.tilmeld = true;
378
+ checkPermissionsSaveAndFilterAcChanges.skipOnClone = true;
281
379
 
282
380
  /*
283
381
  * Add the current user's "user", "group", and access control to new entity.
@@ -291,11 +389,15 @@ export default class Tilmeld implements TilmeldInterface {
291
389
  *
292
390
  * Default access control is
293
391
  *
294
- * - acUser = Tilmeld::FULL_ACCESS
295
- * - acGroup = Tilmeld::READ_ACCESS
296
- * - acOther = Tilmeld::NO_ACCESS
392
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
393
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
394
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
297
395
  */
298
- const addAccess = function (entity: EntityInterface & AccessControlData) {
396
+ const addAccess = function (
397
+ nymph: Nymph,
398
+ entity: EntityInterface & AccessControlData
399
+ ) {
400
+ const tilmeld = nymph.tilmeld as Tilmeld;
299
401
  const user = tilmeld.currentUser;
300
402
  if (
301
403
  user != null &&
@@ -314,13 +416,13 @@ export default class Tilmeld implements TilmeldInterface {
314
416
  entity.group = user.group;
315
417
  }
316
418
  if (entity.acUser == null) {
317
- entity.acUser = Tilmeld.FULL_ACCESS;
419
+ entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
318
420
  }
319
421
  if (entity.acGroup == null) {
320
- entity.acGroup = Tilmeld.READ_ACCESS;
422
+ entity.acGroup = TilmeldAccessLevels.READ_ACCESS;
321
423
  }
322
424
  if (entity.acOther == null) {
323
- entity.acOther = Tilmeld.NO_ACCESS;
425
+ entity.acOther = TilmeldAccessLevels.NO_ACCESS;
324
426
  }
325
427
  if (entity.acRead == null) {
326
428
  entity.acRead = [];
@@ -333,40 +435,53 @@ export default class Tilmeld implements TilmeldInterface {
333
435
  }
334
436
  }
335
437
  };
336
- addAccess.tilmeld = true;
438
+ addAccess.skipOnClone = true;
337
439
 
338
- const validate = function (entity: EntityInterface & AccessControlData) {
440
+ const validate = function (
441
+ _nymph: Nymph,
442
+ entity: EntityInterface & AccessControlData
443
+ ) {
339
444
  if (!(entity instanceof User) && !(entity instanceof Group)) {
340
445
  const ownershipAcPropertyValidator = (prop: any) => {
341
446
  if (
342
447
  typeof prop != 'number' ||
343
- prop < Tilmeld.NO_ACCESS ||
344
- prop > Tilmeld.FULL_ACCESS
448
+ prop < TilmeldAccessLevels.NO_ACCESS ||
449
+ prop > TilmeldAccessLevels.FULL_ACCESS
345
450
  ) {
346
- throw new AccessControlError('Invalid access control property.');
451
+ throw new AccessControlError(
452
+ 'Invalid access control property: ' + prop
453
+ );
347
454
  }
348
455
  };
349
456
 
350
457
  const accessAcPropertyValidator = (prop: any) => {
351
458
  if (!Array.isArray(prop)) {
352
- throw new AccessControlError('Invalid access control property.');
459
+ throw new AccessControlError(
460
+ 'Invalid access control property: ' + prop
461
+ );
353
462
  }
354
463
  prop.forEach((value) => {
355
464
  if (!(value instanceof User || value instanceof Group)) {
356
- throw new AccessControlError('Invalid access control property.');
465
+ throw new AccessControlError(
466
+ 'Invalid access control property: ' + prop
467
+ );
357
468
  }
358
469
  });
359
470
  };
360
471
 
361
472
  if ('user' in entity) {
362
473
  if (!(entity.user instanceof User)) {
363
- throw new AccessControlError('Invalid access control property.');
474
+ throw new AccessControlError(
475
+ 'Invalid access control property: user'
476
+ );
364
477
  }
365
478
  }
366
479
 
367
480
  if ('group' in entity) {
368
- if (!(entity.Group instanceof Group)) {
369
- throw new AccessControlError('Invalid access control property.');
481
+ if (!(entity.group instanceof Group)) {
482
+ throw new AccessControlError(
483
+ 'Invalid access control property: group'
484
+ );
370
485
  }
371
486
  }
372
487
 
@@ -390,7 +505,7 @@ export default class Tilmeld implements TilmeldInterface {
390
505
  }
391
506
  }
392
507
  };
393
- validate.tilmeld = true;
508
+ validate.skipOnClone = true;
394
509
 
395
510
  this.nymph.on('query', handleQuery);
396
511
 
@@ -433,16 +548,16 @@ export default class Tilmeld implements TilmeldInterface {
433
548
  return;
434
549
  }
435
550
 
436
- if (user === null) {
551
+ if (user == null || user.guid == null) {
437
552
  selectors.push({
438
553
  type: '|',
439
554
  // Other access control is sufficient.
440
- gte: [['acOther', Tilmeld.READ_ACCESS]],
555
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
441
556
  // The user and group are not set.
442
557
  selector: [
443
558
  {
444
559
  type: '&',
445
- '!defined': ['user', 'group'],
560
+ '!defined': [['user', 'group']],
446
561
  },
447
562
  ],
448
563
  });
@@ -450,63 +565,50 @@ export default class Tilmeld implements TilmeldInterface {
450
565
  const subSelectors: FormattedSelector[] = [
451
566
  {
452
567
  type: '&',
453
- '!defined': ['user', 'group'],
568
+ '!defined': [['user', 'group']],
454
569
  },
455
570
  // It is owned by the user.
456
571
  {
457
572
  type: '&',
458
- ref: [['user', user]],
459
- gte: [['acUser', Tilmeld.READ_ACCESS]],
573
+ ref: [['user', user.guid]],
574
+ gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
460
575
  },
461
576
  ];
462
- const selector: FormattedSelector = {
463
- type: '|',
464
- // Other access control is sufficient.
465
- gte: [['acOther', Tilmeld.READ_ACCESS]],
466
- // The user and group are not set.
467
- selector: subSelectors,
468
- // The user is listed in acRead, acWrite, or acFull.
469
- ref: [
470
- ['acRead', user],
471
- ['acWrite', user],
472
- ['acFull', user],
473
- ],
474
- };
475
577
  const groupRefs: FormattedSelector['ref'] = [];
476
578
  const acRefs: FormattedSelector['ref'] = [];
477
579
  if (user.group != null && user.group.guid != null) {
478
580
  // It belongs to the user's primary group.
479
- groupRefs.push(['group', user.group]);
581
+ groupRefs.push(['group', user.group.guid]);
480
582
  // User's primary group is listed in acRead, acWrite, or acFull.
481
- acRefs.push(['acRead', user.group]);
482
- acRefs.push(['acWrite', user.group]);
483
- acRefs.push(['acFull', user.group]);
583
+ acRefs.push(['acRead', user.group.guid]);
584
+ acRefs.push(['acWrite', user.group.guid]);
585
+ acRefs.push(['acFull', user.group.guid]);
484
586
  }
485
587
  for (let curSecondaryGroup of user.groups ?? []) {
486
588
  if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
487
589
  // It belongs to the user's secondary group.
488
- groupRefs.push(['group', curSecondaryGroup]);
590
+ groupRefs.push(['group', curSecondaryGroup.guid]);
489
591
  // User's secondary group is listed in acRead, acWrite, or acFull.
490
- acRefs.push(['acRead', curSecondaryGroup]);
491
- acRefs.push(['acWrite', curSecondaryGroup]);
492
- acRefs.push(['acFull', curSecondaryGroup]);
592
+ acRefs.push(['acRead', curSecondaryGroup.guid]);
593
+ acRefs.push(['acWrite', curSecondaryGroup.guid]);
594
+ acRefs.push(['acFull', curSecondaryGroup.guid]);
493
595
  }
494
596
  }
495
597
  for (let curDescendantGroup of user.$getDescendantGroupsSync()) {
496
598
  if (curDescendantGroup != null && curDescendantGroup.guid != null) {
497
599
  // It belongs to the user's secondary group.
498
- groupRefs.push(['group', curDescendantGroup]);
600
+ groupRefs.push(['group', curDescendantGroup.guid]);
499
601
  // User's secondary group is listed in acRead, acWrite, or acFull.
500
- acRefs.push(['acRead', curDescendantGroup]);
501
- acRefs.push(['acWrite', curDescendantGroup]);
502
- acRefs.push(['acFull', curDescendantGroup]);
602
+ acRefs.push(['acRead', curDescendantGroup.guid]);
603
+ acRefs.push(['acWrite', curDescendantGroup.guid]);
604
+ acRefs.push(['acFull', curDescendantGroup.guid]);
503
605
  }
504
606
  }
505
607
  // All the group refs.
506
608
  if (groupRefs.length) {
507
609
  subSelectors.push({
508
610
  type: '&',
509
- gte: [['acGroup', Tilmeld.READ_ACCESS]],
611
+ gte: [['acGroup', TilmeldAccessLevels.READ_ACCESS]],
510
612
  selector: [
511
613
  {
512
614
  type: '|',
@@ -522,6 +624,19 @@ export default class Tilmeld implements TilmeldInterface {
522
624
  ref: acRefs,
523
625
  });
524
626
  }
627
+ const selector: FormattedSelector = {
628
+ type: '|',
629
+ // Other access control is sufficient.
630
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
631
+ // The user and group are not set.
632
+ selector: subSelectors,
633
+ // The user is listed in acRead, acWrite, or acFull.
634
+ ref: [
635
+ ['acRead', user.guid],
636
+ ['acWrite', user.guid],
637
+ ['acFull', user.guid],
638
+ ],
639
+ };
525
640
  selectors.push(selector);
526
641
  }
527
642
  }
@@ -555,9 +670,9 @@ export default class Tilmeld implements TilmeldInterface {
555
670
  *
556
671
  * These properties defaults to:
557
672
  *
558
- * - acUser = Tilmeld.FULL_ACCESS
559
- * - acGroup = Tilmeld.READ_ACCESS
560
- * - acOther = Tilmeld.NO_ACCESS
673
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
674
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
675
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
561
676
  *
562
677
  * "acRead", "acWrite", and "acFull" are arrays of users and/or groups that
563
678
  * also have those permissions.
@@ -586,13 +701,13 @@ export default class Tilmeld implements TilmeldInterface {
586
701
  * - None of the above. (Check other AC.)
587
702
  *
588
703
  * @param entity The entity to check.
589
- * @param type The lowest level of permission to consider a pass. One of Tilmeld.READ_ACCESS, Tilmeld.WRITE_ACCESS, or Tilmeld.FULL_ACCESS.
704
+ * @param type The lowest level of permission to consider a pass.
590
705
  * @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
591
706
  * @returns Whether the current user has at least `type` permission for the entity.
592
707
  */
593
708
  public checkPermissions(
594
709
  entity: EntityInterface & AccessControlData,
595
- type = Tilmeld.READ_ACCESS,
710
+ type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
596
711
  user?: (User & UserData) | false
597
712
  ) {
598
713
  // Only works for entities.
@@ -618,7 +733,8 @@ export default class Tilmeld implements TilmeldInterface {
618
733
  // Users and groups are always readable. Editable by Tilmeld admins.
619
734
  if (
620
735
  (entity instanceof User || entity instanceof Group) &&
621
- (type === Tilmeld.READ_ACCESS || userOrEmpty.$gatekeeper('tilmeld/admin'))
736
+ (type === TilmeldAccessLevels.READ_ACCESS ||
737
+ userOrEmpty.$gatekeeper('tilmeld/admin'))
622
738
  ) {
623
739
  return true;
624
740
  }
@@ -629,9 +745,9 @@ export default class Tilmeld implements TilmeldInterface {
629
745
  }
630
746
 
631
747
  // Load access control, since we need it now...
632
- const acUser = entity.acUser ?? Tilmeld.FULL_ACCESS;
633
- const acGroup = entity.acGroup ?? Tilmeld.READ_ACCESS;
634
- const acOther = entity.acOther ?? Tilmeld.NO_ACCESS;
748
+ const acUser = entity.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
749
+ const acGroup = entity.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
750
+ const acOther = entity.acOther ?? TilmeldAccessLevels.NO_ACCESS;
635
751
 
636
752
  if (userOrNull === null) {
637
753
  return acOther >= type;
@@ -647,7 +763,7 @@ export default class Tilmeld implements TilmeldInterface {
647
763
  userOrEmpty.group != null &&
648
764
  typeof userOrEmpty.group.$is === 'function' &&
649
765
  userOrEmpty.group.$is(entity) &&
650
- type === Tilmeld.READ_ACCESS
766
+ type === TilmeldAccessLevels.READ_ACCESS
651
767
  ) {
652
768
  return true;
653
769
  }
@@ -659,9 +775,18 @@ export default class Tilmeld implements TilmeldInterface {
659
775
 
660
776
  // Check access ac properties.
661
777
  const checks = [
662
- { type: Tilmeld.FULL_ACCESS, array: [...(entity.acFull ?? [])] },
663
- { type: Tilmeld.WRITE_ACCESS, array: [...(entity.acWrite ?? [])] },
664
- { type: Tilmeld.READ_ACCESS, array: [...(entity.acRead ?? [])] },
778
+ {
779
+ type: TilmeldAccessLevels.FULL_ACCESS,
780
+ array: [...(entity.acFull ?? [])],
781
+ },
782
+ {
783
+ type: TilmeldAccessLevels.WRITE_ACCESS,
784
+ array: [...(entity.acWrite ?? [])],
785
+ },
786
+ {
787
+ type: TilmeldAccessLevels.READ_ACCESS,
788
+ array: [...(entity.acRead ?? [])],
789
+ },
665
790
  ];
666
791
  for (let curCheck of checks) {
667
792
  if (type <= curCheck.type) {
@@ -697,6 +822,73 @@ export default class Tilmeld implements TilmeldInterface {
697
822
  return acOther >= type;
698
823
  }
699
824
 
825
+ /**
826
+ * Check a UID's permissions for a user.
827
+ *
828
+ * ## THIS ONLY CHECKS AUTOMATICALLY FOR CLIENT REQUESTS.
829
+ *
830
+ * UID functions on the Node.js side are not checked automatically. This
831
+ * function is only run automatically for UID functions run from the client.
832
+ * You should call this function manually if you're running a UID function on
833
+ * the Node.js side and you want it gated.
834
+ *
835
+ * This will check the Tilmeld config and the user's abilities.
836
+ *
837
+ * - READ_ACCESS, the UID is listed in clientReadableUIDs or the user has the
838
+ * "uid/get/nameofuid" ability.
839
+ * - WRITE_ACCESS, the UID is listed in clientEnabledUIDs or the user has the
840
+ * "uid/new/nameofuid" ability.
841
+ * - FULL_ACCESS, the UID is listed in clientSetabledUIDs or the user has the
842
+ * "uid/set/nameofuid" ability.
843
+ *
844
+ * @param name The UID to check.
845
+ * @param type The lowest level of permission to consider a pass.
846
+ * @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
847
+ * @returns Whether the current user has at least `type` permission for the UID.
848
+ */
849
+ public checkClientUIDPermissions(
850
+ name: string,
851
+ type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
852
+ user?: (User & UserData) | false
853
+ ) {
854
+ let userOrEmpty: User & UserData = this.User.factorySync();
855
+ // Calculate the user.
856
+ if (user == null) {
857
+ userOrEmpty = this.User.current(true);
858
+ } else if (user !== false) {
859
+ userOrEmpty = user;
860
+ }
861
+
862
+ if (userOrEmpty.$gatekeeper('system/admin')) {
863
+ return true;
864
+ }
865
+
866
+ if (type === TilmeldAccessLevels.FULL_ACCESS) {
867
+ return (
868
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
869
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
870
+ );
871
+ } else if (type === TilmeldAccessLevels.WRITE_ACCESS) {
872
+ return (
873
+ this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
874
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
875
+ userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
876
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
877
+ );
878
+ } else if (type === TilmeldAccessLevels.READ_ACCESS) {
879
+ return (
880
+ this.config.clientReadableUIDs.indexOf(name) !== -1 ||
881
+ this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
882
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
883
+ userOrEmpty.$gatekeeper(`uid/get/${name}`) ||
884
+ userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
885
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
886
+ );
887
+ }
888
+
889
+ return false;
890
+ }
891
+
700
892
  /**
701
893
  * Fill session user data.
702
894
  *
@@ -779,9 +971,9 @@ export default class Tilmeld implements TilmeldInterface {
779
971
  // to provide the auth token.
780
972
  let fromAuthHeader = false;
781
973
  let authToken: string;
782
- if (this.request.header('HTTP_X_TILMELDAUTH') != null) {
974
+ if (this.request.header('x-tilmeldauth') != null) {
783
975
  fromAuthHeader = true;
784
- authToken = this.request.header('HTTP_X_TILMELDAUTH') as string;
976
+ authToken = this.request.header('x-tilmeldauth') as string;
785
977
  } else if ('TILMELDAUTH' in cookies) {
786
978
  fromAuthHeader = false;
787
979
  authToken = cookies.TILMELDAUTH;
@@ -800,7 +992,7 @@ export default class Tilmeld implements TilmeldInterface {
800
992
  } else {
801
993
  // The request is for something else, so check for a valid XSRF token,
802
994
  // unless the auth token is provided by a header (instead of a cookie).
803
- const xsrfToken = this.request.header('HTTP_X_XSRF_TOKEN');
995
+ const xsrfToken = this.request.header('x-xsrf-token');
804
996
  if (xsrfToken == null && !fromAuthHeader) {
805
997
  return false;
806
998
  }