@nymphjs/tilmeld 1.0.0-alpha.2 → 1.0.0-alpha.23

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,15 +25,10 @@ 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
  */
34
- public nymph: Nymph;
31
+ public nymph: Nymph = {} as Nymph;
35
32
 
36
33
  /**
37
34
  * The Tilmeld config.
@@ -70,30 +67,32 @@ export default class Tilmeld implements TilmeldInterface {
70
67
  public request: Request | null = null;
71
68
  public response: Response | null = null;
72
69
 
70
+ /**
71
+ * Skip the authenticate step of initialization.
72
+ */
73
+ private skipAuthenticate = false;
74
+
73
75
  /**
74
76
  * Create a new instance of Tilmeld.
75
77
  *
76
78
  * @param config The Tilmeld configuration.
77
79
  */
78
80
  public constructor(config: Partial<Config> & { jwtSecret: string }) {
79
- this.nymph = new Nymph();
80
81
  this.config = { ...defaults, ...config };
81
82
  }
82
83
 
83
84
  /**
84
- * Check to see if the current user has an ability.
85
- *
86
- * If `ability` is undefined, it will check to see if a user is currently
87
- * logged in.
88
- *
89
- * @param ability The ability.
90
- * @returns Whether the user has the given ability.
85
+ * This is used internally by Nymph. Don't call it yourself.
86
+ * @returns A clone of this instance.
91
87
  */
92
- public gatekeeper(ability?: string): boolean {
93
- if (this.currentUser == null) {
94
- return false;
95
- }
96
- return this.currentUser.$gatekeeper(ability);
88
+ public clone() {
89
+ const tilmeld = new Tilmeld(this.config);
90
+ tilmeld.request = this.request;
91
+ tilmeld.response = this.response;
92
+ tilmeld.currentUser = this.currentUser;
93
+ tilmeld.skipAuthenticate = true;
94
+
95
+ return tilmeld;
97
96
  }
98
97
 
99
98
  /**
@@ -115,28 +114,47 @@ export default class Tilmeld implements TilmeldInterface {
115
114
  class NymphUser extends User {}
116
115
  NymphUser.nymph = this.nymph;
117
116
  NymphUser.tilmeld = this;
117
+ (NymphUser as any).skipOnClone = true;
118
118
  this.User = NymphUser;
119
- this.nymph.setEntityClass(NymphUser.class, NymphUser);
119
+ this.nymph.addEntityClass(NymphUser);
120
120
  class NymphGroup extends Group {}
121
121
  NymphGroup.nymph = this.nymph;
122
122
  NymphGroup.tilmeld = this;
123
+ (NymphUser as any).skipOnClone = true;
123
124
  this.Group = NymphGroup;
124
- this.nymph.setEntityClass(NymphGroup.class, NymphGroup);
125
+ this.nymph.addEntityClass(NymphGroup);
125
126
 
126
127
  this.initAccessControl();
127
- if (this.request != null) {
128
+ if (this.request != null && !this.skipAuthenticate) {
128
129
  this.authenticate();
129
130
  }
131
+ this.skipAuthenticate = false;
130
132
  }
131
133
 
132
- private initAccessControl() {
133
- const tilmeld = this;
134
+ /**
135
+ * Check to see if the current user has an ability.
136
+ *
137
+ * If `ability` is undefined, it will check to see if a user is currently
138
+ * logged in.
139
+ *
140
+ * @param ability The ability.
141
+ * @returns Whether the user has the given ability.
142
+ */
143
+ public gatekeeper(ability?: string): boolean {
144
+ if (this.currentUser == null) {
145
+ return false;
146
+ }
147
+ return this.currentUser.$gatekeeper(ability);
148
+ }
134
149
 
150
+ private initAccessControl() {
135
151
  // Check for the skip access control option and add AC selectors.
136
152
  const handleQuery = function (
153
+ nymph: Nymph,
137
154
  options: Options,
138
155
  selectors: FormattedSelector[]
139
156
  ) {
157
+ const tilmeld = nymph.tilmeld as Tilmeld;
140
158
  if (
141
159
  !options ||
142
160
  !('skipAc' in options) ||
@@ -145,20 +163,20 @@ export default class Tilmeld implements TilmeldInterface {
145
163
  ) {
146
164
  if (options?.source === 'client') {
147
165
  if (
148
- !tilmeld.gatekeeper('tilmeld/admin') &&
166
+ !tilmeld.gatekeeper('tilmeld/admin') && // The user is not an admin.
149
167
  ((!tilmeld.config.enableUserSearch &&
150
- options.class?.factorySync() instanceof User) ||
168
+ options.class?.factorySync() instanceof User) || // The use is searching for a user and searching is disabled.
151
169
  (!tilmeld.config.enableGroupSearch &&
152
- options.class?.factorySync() instanceof Group)) &&
153
- (selectors[0] == null ||
154
- selectors[0].type !== '&' ||
155
- (!('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.
156
174
  ('guid' in selectors[0] &&
157
- selectors[0].guid?.length === 1 &&
158
- 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.
159
177
  ('equal' in selectors[0] &&
160
- selectors[0].equal?.length === 1 &&
161
- 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.
162
180
  ) {
163
181
  // If the user is not specifically searching for a GUID or username,
164
182
  // and they're not allowed to search, it should fail.
@@ -169,9 +187,14 @@ export default class Tilmeld implements TilmeldInterface {
169
187
  tilmeld.addAccessControlSelectors(options, selectors);
170
188
  }
171
189
  };
190
+ handleQuery.skipOnClone = true;
172
191
 
173
192
  // Filter entities being deleted for user permissions.
174
- const checkPermissionsDelete = function (entity: EntityInterface) {
193
+ const checkPermissionsDelete = function (
194
+ nymph: Nymph,
195
+ entity: EntityInterface
196
+ ) {
197
+ const tilmeld = nymph.tilmeld as Tilmeld;
175
198
  if (
176
199
  typeof entity.$tilmeldDeleteSkipAC === 'function' &&
177
200
  entity.$tilmeldDeleteSkipAC()
@@ -179,30 +202,35 @@ export default class Tilmeld implements TilmeldInterface {
179
202
  return;
180
203
  }
181
204
  // Test for permissions.
182
- if (tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
205
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
183
206
  throw new AccessControlError('No permission to delete.');
184
207
  }
185
208
  };
209
+ checkPermissionsDelete.skipOnClone = true;
186
210
 
187
211
  // Filter entities being deleted for user permissions.
188
212
  const checkPermissionsDeleteByID = function (
213
+ nymph: Nymph,
189
214
  guid: string,
190
215
  className?: string
191
216
  ) {
192
- const entity = tilmeld.nymph.driver.getEntitySync(
193
- { class: tilmeld.nymph.getEntityClass(className ?? 'Entity') },
217
+ const entity = nymph.driver.getEntitySync(
218
+ { class: nymph.getEntityClass(className ?? 'Entity') },
194
219
  { type: '&', guid: guid }
195
220
  );
196
221
  if (entity != null) {
197
- checkPermissionsDelete(entity);
222
+ checkPermissionsDelete(nymph, entity);
198
223
  }
199
224
  };
225
+ checkPermissionsDeleteByID.skipOnClone = true;
200
226
 
201
227
  // Filter entities being saved for user permissions, and filter any
202
228
  // disallowed changes to AC properties.
203
229
  const checkPermissionsSaveAndFilterAcChanges = function (
230
+ nymph: Nymph,
204
231
  entity: EntityInterface & AccessControlData
205
232
  ) {
233
+ const tilmeld = nymph.tilmeld as Tilmeld;
206
234
  if (!entity) {
207
235
  return;
208
236
  }
@@ -218,15 +246,15 @@ export default class Tilmeld implements TilmeldInterface {
218
246
  // allowing a change to ac properties.
219
247
 
220
248
  const originalAc = entity.$getOriginalAcValues();
221
- const newAc = {
222
- user: entity.user ?? null,
223
- group: entity.group ?? null,
224
- acUser: entity.acUser ?? null,
225
- acGroup: entity.acGroup ?? null,
226
- acOther: entity.acOther ?? null,
227
- acRead: entity.acRead ?? null,
228
- acWrite: entity.acWrite ?? null,
229
- 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,
230
258
  };
231
259
 
232
260
  const setAcProperties = (acValues: { [k: string]: any }) => {
@@ -242,19 +270,112 @@ export default class Tilmeld implements TilmeldInterface {
242
270
 
243
271
  // Restore original AC properties and check permissions.
244
272
  setAcProperties(originalAc);
245
- if (tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
246
- // Only allow changes to AC properties if the user has full access.
247
- // TODO: only allow changes to `user` and `group` if tilmeld admin or
248
- // 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
+
249
369
  setAcProperties(newAc);
250
370
  }
251
371
  }
252
372
 
253
373
  // Test for permissions.
254
- if (!tilmeld.checkPermissions(entity, Tilmeld.WRITE_ACCESS)) {
374
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.WRITE_ACCESS)) {
255
375
  throw new AccessControlError('No permission to write.');
256
376
  }
257
377
  };
378
+ checkPermissionsSaveAndFilterAcChanges.skipOnClone = true;
258
379
 
259
380
  /*
260
381
  * Add the current user's "user", "group", and access control to new entity.
@@ -268,11 +389,15 @@ export default class Tilmeld implements TilmeldInterface {
268
389
  *
269
390
  * Default access control is
270
391
  *
271
- * - acUser = Tilmeld::FULL_ACCESS
272
- * - acGroup = Tilmeld::READ_ACCESS
273
- * - acOther = Tilmeld::NO_ACCESS
392
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
393
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
394
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
274
395
  */
275
- 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;
276
401
  const user = tilmeld.currentUser;
277
402
  if (
278
403
  user != null &&
@@ -291,13 +416,13 @@ export default class Tilmeld implements TilmeldInterface {
291
416
  entity.group = user.group;
292
417
  }
293
418
  if (entity.acUser == null) {
294
- entity.acUser = Tilmeld.FULL_ACCESS;
419
+ entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
295
420
  }
296
421
  if (entity.acGroup == null) {
297
- entity.acGroup = Tilmeld.READ_ACCESS;
422
+ entity.acGroup = TilmeldAccessLevels.READ_ACCESS;
298
423
  }
299
424
  if (entity.acOther == null) {
300
- entity.acOther = Tilmeld.NO_ACCESS;
425
+ entity.acOther = TilmeldAccessLevels.NO_ACCESS;
301
426
  }
302
427
  if (entity.acRead == null) {
303
428
  entity.acRead = [];
@@ -310,39 +435,53 @@ export default class Tilmeld implements TilmeldInterface {
310
435
  }
311
436
  }
312
437
  };
438
+ addAccess.skipOnClone = true;
313
439
 
314
- const validate = function (entity: EntityInterface & AccessControlData) {
440
+ const validate = function (
441
+ _nymph: Nymph,
442
+ entity: EntityInterface & AccessControlData
443
+ ) {
315
444
  if (!(entity instanceof User) && !(entity instanceof Group)) {
316
445
  const ownershipAcPropertyValidator = (prop: any) => {
317
446
  if (
318
447
  typeof prop != 'number' ||
319
- prop < Tilmeld.NO_ACCESS ||
320
- prop > Tilmeld.FULL_ACCESS
448
+ prop < TilmeldAccessLevels.NO_ACCESS ||
449
+ prop > TilmeldAccessLevels.FULL_ACCESS
321
450
  ) {
322
- throw new AccessControlError('Invalid access control property.');
451
+ throw new AccessControlError(
452
+ 'Invalid access control property: ' + prop
453
+ );
323
454
  }
324
455
  };
325
456
 
326
457
  const accessAcPropertyValidator = (prop: any) => {
327
458
  if (!Array.isArray(prop)) {
328
- throw new AccessControlError('Invalid access control property.');
459
+ throw new AccessControlError(
460
+ 'Invalid access control property: ' + prop
461
+ );
329
462
  }
330
463
  prop.forEach((value) => {
331
464
  if (!(value instanceof User || value instanceof Group)) {
332
- throw new AccessControlError('Invalid access control property.');
465
+ throw new AccessControlError(
466
+ 'Invalid access control property: ' + prop
467
+ );
333
468
  }
334
469
  });
335
470
  };
336
471
 
337
472
  if ('user' in entity) {
338
473
  if (!(entity.user instanceof User)) {
339
- throw new AccessControlError('Invalid access control property.');
474
+ throw new AccessControlError(
475
+ 'Invalid access control property: user'
476
+ );
340
477
  }
341
478
  }
342
479
 
343
480
  if ('group' in entity) {
344
- if (!(entity.Group instanceof Group)) {
345
- throw new AccessControlError('Invalid access control property.');
481
+ if (!(entity.group instanceof Group)) {
482
+ throw new AccessControlError(
483
+ 'Invalid access control property: group'
484
+ );
346
485
  }
347
486
  }
348
487
 
@@ -366,6 +505,7 @@ export default class Tilmeld implements TilmeldInterface {
366
505
  }
367
506
  }
368
507
  };
508
+ validate.skipOnClone = true;
369
509
 
370
510
  this.nymph.on('query', handleQuery);
371
511
 
@@ -408,16 +548,16 @@ export default class Tilmeld implements TilmeldInterface {
408
548
  return;
409
549
  }
410
550
 
411
- if (user === null) {
551
+ if (user == null || user.guid == null) {
412
552
  selectors.push({
413
553
  type: '|',
414
554
  // Other access control is sufficient.
415
- gte: [['acOther', Tilmeld.READ_ACCESS]],
555
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
416
556
  // The user and group are not set.
417
557
  selector: [
418
558
  {
419
559
  type: '&',
420
- '!defined': ['user', 'group'],
560
+ '!defined': [['user', 'group']],
421
561
  },
422
562
  ],
423
563
  });
@@ -425,63 +565,50 @@ export default class Tilmeld implements TilmeldInterface {
425
565
  const subSelectors: FormattedSelector[] = [
426
566
  {
427
567
  type: '&',
428
- '!defined': ['user', 'group'],
568
+ '!defined': [['user', 'group']],
429
569
  },
430
570
  // It is owned by the user.
431
571
  {
432
572
  type: '&',
433
- ref: [['user', user]],
434
- gte: [['acUser', Tilmeld.READ_ACCESS]],
573
+ ref: [['user', user.guid]],
574
+ gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
435
575
  },
436
576
  ];
437
- const selector: FormattedSelector = {
438
- type: '|',
439
- // Other access control is sufficient.
440
- gte: [['acOther', Tilmeld.READ_ACCESS]],
441
- // The user and group are not set.
442
- selector: subSelectors,
443
- // The user is listed in acRead, acWrite, or acFull.
444
- ref: [
445
- ['acRead', user],
446
- ['acWrite', user],
447
- ['acFull', user],
448
- ],
449
- };
450
577
  const groupRefs: FormattedSelector['ref'] = [];
451
578
  const acRefs: FormattedSelector['ref'] = [];
452
579
  if (user.group != null && user.group.guid != null) {
453
580
  // It belongs to the user's primary group.
454
- groupRefs.push(['group', user.group]);
581
+ groupRefs.push(['group', user.group.guid]);
455
582
  // User's primary group is listed in acRead, acWrite, or acFull.
456
- acRefs.push(['acRead', user.group]);
457
- acRefs.push(['acWrite', user.group]);
458
- 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]);
459
586
  }
460
587
  for (let curSecondaryGroup of user.groups ?? []) {
461
588
  if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
462
589
  // It belongs to the user's secondary group.
463
- groupRefs.push(['group', curSecondaryGroup]);
590
+ groupRefs.push(['group', curSecondaryGroup.guid]);
464
591
  // User's secondary group is listed in acRead, acWrite, or acFull.
465
- acRefs.push(['acRead', curSecondaryGroup]);
466
- acRefs.push(['acWrite', curSecondaryGroup]);
467
- acRefs.push(['acFull', curSecondaryGroup]);
592
+ acRefs.push(['acRead', curSecondaryGroup.guid]);
593
+ acRefs.push(['acWrite', curSecondaryGroup.guid]);
594
+ acRefs.push(['acFull', curSecondaryGroup.guid]);
468
595
  }
469
596
  }
470
597
  for (let curDescendantGroup of user.$getDescendantGroupsSync()) {
471
598
  if (curDescendantGroup != null && curDescendantGroup.guid != null) {
472
599
  // It belongs to the user's secondary group.
473
- groupRefs.push(['group', curDescendantGroup]);
600
+ groupRefs.push(['group', curDescendantGroup.guid]);
474
601
  // User's secondary group is listed in acRead, acWrite, or acFull.
475
- acRefs.push(['acRead', curDescendantGroup]);
476
- acRefs.push(['acWrite', curDescendantGroup]);
477
- acRefs.push(['acFull', curDescendantGroup]);
602
+ acRefs.push(['acRead', curDescendantGroup.guid]);
603
+ acRefs.push(['acWrite', curDescendantGroup.guid]);
604
+ acRefs.push(['acFull', curDescendantGroup.guid]);
478
605
  }
479
606
  }
480
607
  // All the group refs.
481
608
  if (groupRefs.length) {
482
609
  subSelectors.push({
483
610
  type: '&',
484
- gte: [['acGroup', Tilmeld.READ_ACCESS]],
611
+ gte: [['acGroup', TilmeldAccessLevels.READ_ACCESS]],
485
612
  selector: [
486
613
  {
487
614
  type: '|',
@@ -497,6 +624,19 @@ export default class Tilmeld implements TilmeldInterface {
497
624
  ref: acRefs,
498
625
  });
499
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
+ };
500
640
  selectors.push(selector);
501
641
  }
502
642
  }
@@ -530,9 +670,9 @@ export default class Tilmeld implements TilmeldInterface {
530
670
  *
531
671
  * These properties defaults to:
532
672
  *
533
- * - acUser = Tilmeld.FULL_ACCESS
534
- * - acGroup = Tilmeld.READ_ACCESS
535
- * - acOther = Tilmeld.NO_ACCESS
673
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
674
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
675
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
536
676
  *
537
677
  * "acRead", "acWrite", and "acFull" are arrays of users and/or groups that
538
678
  * also have those permissions.
@@ -561,13 +701,13 @@ export default class Tilmeld implements TilmeldInterface {
561
701
  * - None of the above. (Check other AC.)
562
702
  *
563
703
  * @param entity The entity to check.
564
- * @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.
565
705
  * @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
566
706
  * @returns Whether the current user has at least `type` permission for the entity.
567
707
  */
568
708
  public checkPermissions(
569
709
  entity: EntityInterface & AccessControlData,
570
- type = Tilmeld.READ_ACCESS,
710
+ type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
571
711
  user?: (User & UserData) | false
572
712
  ) {
573
713
  // Only works for entities.
@@ -593,7 +733,8 @@ export default class Tilmeld implements TilmeldInterface {
593
733
  // Users and groups are always readable. Editable by Tilmeld admins.
594
734
  if (
595
735
  (entity instanceof User || entity instanceof Group) &&
596
- (type === Tilmeld.READ_ACCESS || userOrEmpty.$gatekeeper('tilmeld/admin'))
736
+ (type === TilmeldAccessLevels.READ_ACCESS ||
737
+ userOrEmpty.$gatekeeper('tilmeld/admin'))
597
738
  ) {
598
739
  return true;
599
740
  }
@@ -604,9 +745,9 @@ export default class Tilmeld implements TilmeldInterface {
604
745
  }
605
746
 
606
747
  // Load access control, since we need it now...
607
- const acUser = entity.acUser ?? Tilmeld.FULL_ACCESS;
608
- const acGroup = entity.acGroup ?? Tilmeld.READ_ACCESS;
609
- 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;
610
751
 
611
752
  if (userOrNull === null) {
612
753
  return acOther >= type;
@@ -622,7 +763,7 @@ export default class Tilmeld implements TilmeldInterface {
622
763
  userOrEmpty.group != null &&
623
764
  typeof userOrEmpty.group.$is === 'function' &&
624
765
  userOrEmpty.group.$is(entity) &&
625
- type === Tilmeld.READ_ACCESS
766
+ type === TilmeldAccessLevels.READ_ACCESS
626
767
  ) {
627
768
  return true;
628
769
  }
@@ -634,9 +775,18 @@ export default class Tilmeld implements TilmeldInterface {
634
775
 
635
776
  // Check access ac properties.
636
777
  const checks = [
637
- { type: Tilmeld.FULL_ACCESS, array: [...(entity.acFull ?? [])] },
638
- { type: Tilmeld.WRITE_ACCESS, array: [...(entity.acWrite ?? [])] },
639
- { 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
+ },
640
790
  ];
641
791
  for (let curCheck of checks) {
642
792
  if (type <= curCheck.type) {
@@ -672,6 +822,73 @@ export default class Tilmeld implements TilmeldInterface {
672
822
  return acOther >= type;
673
823
  }
674
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
+
675
892
  /**
676
893
  * Fill session user data.
677
894
  *
@@ -754,9 +971,9 @@ export default class Tilmeld implements TilmeldInterface {
754
971
  // to provide the auth token.
755
972
  let fromAuthHeader = false;
756
973
  let authToken: string;
757
- if (this.request.header('HTTP_X_TILMELDAUTH') != null) {
974
+ if (this.request.header('x-tilmeldauth') != null) {
758
975
  fromAuthHeader = true;
759
- authToken = this.request.header('HTTP_X_TILMELDAUTH') as string;
976
+ authToken = this.request.header('x-tilmeldauth') as string;
760
977
  } else if ('TILMELDAUTH' in cookies) {
761
978
  fromAuthHeader = false;
762
979
  authToken = cookies.TILMELDAUTH;
@@ -775,7 +992,7 @@ export default class Tilmeld implements TilmeldInterface {
775
992
  } else {
776
993
  // The request is for something else, so check for a valid XSRF token,
777
994
  // unless the auth token is provided by a header (instead of a cookie).
778
- const xsrfToken = this.request.header('HTTP_X_XSRF_TOKEN');
995
+ const xsrfToken = this.request.header('x-xsrf-token');
779
996
  if (xsrfToken == null && !fromAuthHeader) {
780
997
  return false;
781
998
  }