@nymphjs/tilmeld 1.0.0-alpha.1 → 1.0.0-alpha.5

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
@@ -1,4 +1,11 @@
1
- import Nymph, { EntityInterface, Options, Selector } from '@nymphjs/nymph';
1
+ import {
2
+ Nymph,
3
+ EntityInterface,
4
+ FormattedSelector,
5
+ Options,
6
+ TilmeldInterface,
7
+ TilmeldAccessLevels,
8
+ } from '@nymphjs/nymph';
2
9
  import { Request, Response } from 'express';
3
10
 
4
11
  import { Config, ConfigDefaults as defaults } from './conf';
@@ -16,21 +23,31 @@ import { AccessControlData } from './Tilmeld.types';
16
23
  * @copyright SciActive Inc
17
24
  * @see http://nymph.io/
18
25
  */
19
- export default class Tilmeld {
20
- public static NO_ACCESS = 0;
21
- public static READ_ACCESS = 1;
22
- public static WRITE_ACCESS = 2;
23
- public static FULL_ACCESS = 4;
26
+ export default class Tilmeld implements TilmeldInterface {
27
+ /**
28
+ * The Nymph instance.
29
+ */
30
+ public nymph: Nymph = {} as Nymph;
24
31
 
25
32
  /**
26
33
  * The Tilmeld config.
27
34
  */
28
- public static config: Config;
35
+ public config: Config;
29
36
 
30
37
  /**
31
38
  * The currently logged in user.
32
39
  */
33
- public static currentUser: (User & UserData) | null = null;
40
+ public currentUser: (User & UserData) | null = null;
41
+
42
+ /**
43
+ * The user class for this instance of Tilmeld.
44
+ */
45
+ public User: typeof User = User;
46
+
47
+ /**
48
+ * The group class for this instance of Tilmeld.
49
+ */
50
+ public Group: typeof Group = Group;
34
51
 
35
52
  /**
36
53
  * If you will be performing authentication functions (logging in/out), you
@@ -46,47 +63,98 @@ export default class Tilmeld {
46
63
  * If you want to support cookie based authentication (which still requires an
47
64
  * XSRF token for security), you should enable the cookie parser middleware.
48
65
  */
49
- public static request: Request | null = null;
50
- public static response: Response | null = null;
66
+ public request: Request | null = null;
67
+ public response: Response | null = null;
51
68
 
52
69
  /**
53
- * Check to see if the current user has an ability.
54
- *
55
- * If `ability` is undefined, it will check to see if a user is currently
56
- * logged in.
70
+ * Skip the authenticate step of initialization.
71
+ */
72
+ private skipAuthenticate = false;
73
+
74
+ /**
75
+ * Create a new instance of Tilmeld.
57
76
  *
58
- * @param ability The ability.
59
- * @returns Whether the user has the given ability.
77
+ * @param config The Tilmeld configuration.
60
78
  */
61
- public static gatekeeper(ability?: string): boolean {
62
- if (this.currentUser == null) {
63
- return false;
64
- }
65
- return this.currentUser.$gatekeeper(ability);
79
+ public constructor(config: Partial<Config> & { jwtSecret: string }) {
80
+ this.config = { ...defaults, ...config };
81
+ }
82
+
83
+ /**
84
+ * This is used internally by Nymph. Don't call it yourself.
85
+ * @returns A clone of this instance.
86
+ */
87
+ public clone() {
88
+ const tilmeld = new Tilmeld(this.config);
89
+ tilmeld.request = this.request;
90
+ tilmeld.response = this.response;
91
+ tilmeld.currentUser = this.currentUser;
92
+ tilmeld.skipAuthenticate = true;
93
+
94
+ return tilmeld;
66
95
  }
67
96
 
68
97
  /**
69
98
  * Initialize Tilmeld.
70
99
  *
71
- * @param config The Tilmeld configuration.
100
+ * This is meant to be called internally by Nymph. Don't call this directly.
101
+ *
102
+ * @param nymph The Nymph instance.
72
103
  */
73
- public static init(config: Partial<Config> & { jwtSecret: string }) {
74
- this.config = { ...defaults, ...config };
104
+ public init(nymph: Nymph) {
105
+ this.nymph = nymph;
75
106
 
76
107
  // Set up access control hooks when Nymph is called.
77
- if (Nymph.driver == null) {
108
+ if (this.nymph.driver == null) {
78
109
  throw new Error("Tilmeld can't be configured before Nymph.");
79
110
  }
80
111
 
112
+ // Configure the classes.
113
+ class NymphUser extends User {}
114
+ NymphUser.nymph = this.nymph;
115
+ NymphUser.tilmeld = this;
116
+ (NymphUser as any).skipOnClone = true;
117
+ this.User = NymphUser;
118
+ this.nymph.addEntityClass(NymphUser);
119
+ class NymphGroup extends Group {}
120
+ NymphGroup.nymph = this.nymph;
121
+ NymphGroup.tilmeld = this;
122
+ (NymphUser as any).skipOnClone = true;
123
+ this.Group = NymphGroup;
124
+ this.nymph.addEntityClass(NymphGroup);
125
+
81
126
  this.initAccessControl();
82
- if (this.request != null) {
127
+ if (this.request != null && !this.skipAuthenticate) {
83
128
  this.authenticate();
84
129
  }
130
+ this.skipAuthenticate = false;
131
+ }
132
+
133
+ /**
134
+ * Check to see if the current user has an ability.
135
+ *
136
+ * If `ability` is undefined, it will check to see if a user is currently
137
+ * logged in.
138
+ *
139
+ * @param ability The ability.
140
+ * @returns Whether the user has the given ability.
141
+ */
142
+ public gatekeeper(ability?: string): boolean {
143
+ if (this.currentUser == null) {
144
+ return false;
145
+ }
146
+ return this.currentUser.$gatekeeper(ability);
85
147
  }
86
148
 
87
- private static initAccessControl() {
149
+ private initAccessControl() {
150
+ const tilmeld = this;
151
+
88
152
  // Check for the skip access control option and add AC selectors.
89
- const getEntities = function (options: Options, selectors: Selector[]) {
153
+ const handleQuery = function (
154
+ _nymph: Nymph,
155
+ options: Options,
156
+ selectors: FormattedSelector[]
157
+ ) {
90
158
  if (
91
159
  !options ||
92
160
  !('skipAc' in options) ||
@@ -95,18 +163,20 @@ export default class Tilmeld {
95
163
  ) {
96
164
  if (options?.source === 'client') {
97
165
  if (
98
- !Tilmeld.gatekeeper('tilmeld/admin') &&
99
- ((!Tilmeld.config.enableUserSearch &&
100
- options.class?.factorySync() instanceof User) ||
101
- (!Tilmeld.config.enableGroupSearch &&
102
- options.class?.factorySync() instanceof Group)) &&
103
- (selectors[0] == null ||
104
- selectors[0].type !== '&' ||
105
- (!('guid' in selectors[0]) && !('equal' in selectors[0])) ||
166
+ !tilmeld.gatekeeper('tilmeld/admin') && // The user is not an admin.
167
+ ((!tilmeld.config.enableUserSearch &&
168
+ options.class?.factorySync() instanceof User) || // The use is searching for a user and searching is disabled.
169
+ (!tilmeld.config.enableGroupSearch &&
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.
106
174
  ('guid' in selectors[0] &&
107
- typeof selectors[0].guid !== 'string') ||
175
+ (!selectors[0].guid?.length ||
176
+ !selectors[0].guid?.[0]?.length)) || // Or they're not searching for a specific guid.
108
177
  ('equal' in selectors[0] &&
109
- selectors[0]['equal']?.[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.
110
180
  ) {
111
181
  // If the user is not specifically searching for a GUID or username,
112
182
  // and they're not allowed to search, it should fail.
@@ -114,12 +184,16 @@ export default class Tilmeld {
114
184
  }
115
185
  }
116
186
  // Add access control selectors
117
- Tilmeld.addAccessControlSelectors(options, selectors);
187
+ tilmeld.addAccessControlSelectors(options, selectors);
118
188
  }
119
189
  };
190
+ handleQuery.skipOnClone = true;
120
191
 
121
192
  // Filter entities being deleted for user permissions.
122
- const checkPermissionsDelete = function (entity: EntityInterface) {
193
+ const checkPermissionsDelete = function (
194
+ _nymph: Nymph,
195
+ entity: EntityInterface
196
+ ) {
123
197
  if (
124
198
  typeof entity.$tilmeldDeleteSkipAC === 'function' &&
125
199
  entity.$tilmeldDeleteSkipAC()
@@ -127,28 +201,32 @@ export default class Tilmeld {
127
201
  return;
128
202
  }
129
203
  // Test for permissions.
130
- if (Tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
204
+ if (tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
131
205
  throw new AccessControlError('No permission to delete.');
132
206
  }
133
207
  };
208
+ checkPermissionsDelete.skipOnClone = true;
134
209
 
135
210
  // Filter entities being deleted for user permissions.
136
211
  const checkPermissionsDeleteByID = function (
212
+ nymph: Nymph,
137
213
  guid: string,
138
214
  className?: string
139
215
  ) {
140
- const entity = Nymph.driver.getEntitySync(
141
- { class: Nymph.getEntityClass(className ?? 'Entity') },
216
+ const entity = tilmeld.nymph.driver.getEntitySync(
217
+ { class: tilmeld.nymph.getEntityClass(className ?? 'Entity') },
142
218
  { type: '&', guid: guid }
143
219
  );
144
220
  if (entity != null) {
145
- checkPermissionsDelete(entity);
221
+ checkPermissionsDelete(nymph, entity);
146
222
  }
147
223
  };
224
+ checkPermissionsDeleteByID.skipOnClone = true;
148
225
 
149
226
  // Filter entities being saved for user permissions, and filter any
150
227
  // disallowed changes to AC properties.
151
228
  const checkPermissionsSaveAndFilterAcChanges = function (
229
+ _nymph: Nymph,
152
230
  entity: EntityInterface & AccessControlData
153
231
  ) {
154
232
  if (!entity) {
@@ -190,7 +268,7 @@ export default class Tilmeld {
190
268
 
191
269
  // Restore original AC properties and check permissions.
192
270
  setAcProperties(originalAc);
193
- if (Tilmeld.checkPermissions(entity, Tilmeld.FULL_ACCESS)) {
271
+ if (tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
194
272
  // Only allow changes to AC properties if the user has full access.
195
273
  // TODO: only allow changes to `user` and `group` if tilmeld admin or
196
274
  // group is user's group.
@@ -199,10 +277,11 @@ export default class Tilmeld {
199
277
  }
200
278
 
201
279
  // Test for permissions.
202
- if (!Tilmeld.checkPermissions(entity, Tilmeld.WRITE_ACCESS)) {
280
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.WRITE_ACCESS)) {
203
281
  throw new AccessControlError('No permission to write.');
204
282
  }
205
283
  };
284
+ checkPermissionsSaveAndFilterAcChanges.skipOnClone = true;
206
285
 
207
286
  /*
208
287
  * Add the current user's "user", "group", and access control to new entity.
@@ -216,12 +295,15 @@ export default class Tilmeld {
216
295
  *
217
296
  * Default access control is
218
297
  *
219
- * - acUser = Tilmeld::FULL_ACCESS
220
- * - acGroup = Tilmeld::READ_ACCESS
221
- * - acOther = Tilmeld::NO_ACCESS
298
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
299
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
300
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
222
301
  */
223
- const addAccess = function (entity: EntityInterface & AccessControlData) {
224
- const user = Tilmeld.currentUser;
302
+ const addAccess = function (
303
+ _nymph: Nymph,
304
+ entity: EntityInterface & AccessControlData
305
+ ) {
306
+ const user = tilmeld.currentUser;
225
307
  if (
226
308
  user != null &&
227
309
  entity.guid == null &&
@@ -239,13 +321,13 @@ export default class Tilmeld {
239
321
  entity.group = user.group;
240
322
  }
241
323
  if (entity.acUser == null) {
242
- entity.acUser = Tilmeld.FULL_ACCESS;
324
+ entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
243
325
  }
244
326
  if (entity.acGroup == null) {
245
- entity.acGroup = Tilmeld.READ_ACCESS;
327
+ entity.acGroup = TilmeldAccessLevels.READ_ACCESS;
246
328
  }
247
329
  if (entity.acOther == null) {
248
- entity.acOther = Tilmeld.NO_ACCESS;
330
+ entity.acOther = TilmeldAccessLevels.NO_ACCESS;
249
331
  }
250
332
  if (entity.acRead == null) {
251
333
  entity.acRead = [];
@@ -258,14 +340,18 @@ export default class Tilmeld {
258
340
  }
259
341
  }
260
342
  };
343
+ addAccess.skipOnClone = true;
261
344
 
262
- const validate = function (entity: EntityInterface & AccessControlData) {
345
+ const validate = function (
346
+ _nymph: Nymph,
347
+ entity: EntityInterface & AccessControlData
348
+ ) {
263
349
  if (!(entity instanceof User) && !(entity instanceof Group)) {
264
350
  const ownershipAcPropertyValidator = (prop: any) => {
265
351
  if (
266
352
  typeof prop != 'number' ||
267
- prop < Tilmeld.NO_ACCESS ||
268
- prop > Tilmeld.FULL_ACCESS
353
+ prop < TilmeldAccessLevels.NO_ACCESS ||
354
+ prop > TilmeldAccessLevels.FULL_ACCESS
269
355
  ) {
270
356
  throw new AccessControlError('Invalid access control property.');
271
357
  }
@@ -314,16 +400,16 @@ export default class Tilmeld {
314
400
  }
315
401
  }
316
402
  };
403
+ validate.skipOnClone = true;
317
404
 
318
- Nymph.on('beforeGetEntity', getEntities);
319
- Nymph.on('beforeGetEntities', getEntities);
405
+ this.nymph.on('query', handleQuery);
320
406
 
321
- Nymph.on('beforeSaveEntity', addAccess);
322
- Nymph.on('beforeSaveEntity', validate);
323
- Nymph.on('beforeSaveEntity', checkPermissionsSaveAndFilterAcChanges);
407
+ this.nymph.on('beforeSaveEntity', addAccess);
408
+ this.nymph.on('beforeSaveEntity', validate);
409
+ this.nymph.on('beforeSaveEntity', checkPermissionsSaveAndFilterAcChanges);
324
410
 
325
- Nymph.on('beforeDeleteEntity', checkPermissionsDelete);
326
- Nymph.on('beforeDeleteEntityByID', checkPermissionsDeleteByID);
411
+ this.nymph.on('beforeDeleteEntity', checkPermissionsDelete);
412
+ this.nymph.on('beforeDeleteEntityByID', checkPermissionsDeleteByID);
327
413
  }
328
414
 
329
415
  /**
@@ -332,9 +418,9 @@ export default class Tilmeld {
332
418
  *
333
419
  * @param optionsAndSelectors The options and selectors of the query.
334
420
  */
335
- public static addAccessControlSelectors(
421
+ public addAccessControlSelectors(
336
422
  options: Options,
337
- selectors: Selector[]
423
+ selectors: FormattedSelector[]
338
424
  ) {
339
425
  const user = this.currentUser;
340
426
 
@@ -350,7 +436,8 @@ export default class Tilmeld {
350
436
  throw new Error('No options in argument.');
351
437
  } else if (
352
438
  'class' in options &&
353
- (options.class === User || options.class === Group)
439
+ (options.class?.factorySync() instanceof User ||
440
+ options.class?.factorySync() instanceof Group)
354
441
  ) {
355
442
  // They are requesting a user/group. Always accessible for reading.
356
443
  return;
@@ -360,15 +447,17 @@ export default class Tilmeld {
360
447
  selectors.push({
361
448
  type: '|',
362
449
  // Other access control is sufficient.
363
- gte: ['acOther', Tilmeld.READ_ACCESS],
450
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
364
451
  // The user and group are not set.
365
- selector: {
366
- type: '&',
367
- '!defined': ['user', 'group'],
368
- },
452
+ selector: [
453
+ {
454
+ type: '&',
455
+ '!defined': ['user', 'group'],
456
+ },
457
+ ],
369
458
  });
370
459
  } else {
371
- const subSelectors: Selector[] = [
460
+ const subSelectors: FormattedSelector[] = [
372
461
  {
373
462
  type: '&',
374
463
  '!defined': ['user', 'group'],
@@ -376,14 +465,14 @@ export default class Tilmeld {
376
465
  // It is owned by the user.
377
466
  {
378
467
  type: '&',
379
- ref: ['user', user],
380
- gte: ['acUser', Tilmeld.READ_ACCESS],
468
+ ref: [['user', user]],
469
+ gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
381
470
  },
382
471
  ];
383
- const selector: Selector = {
472
+ const selector: FormattedSelector = {
384
473
  type: '|',
385
474
  // Other access control is sufficient.
386
- gte: ['acOther', Tilmeld.READ_ACCESS],
475
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
387
476
  // The user and group are not set.
388
477
  selector: subSelectors,
389
478
  // The user is listed in acRead, acWrite, or acFull.
@@ -393,8 +482,8 @@ export default class Tilmeld {
393
482
  ['acFull', user],
394
483
  ],
395
484
  };
396
- const groupRefs: Selector['ref'] = [];
397
- const acRefs: Selector['ref'] = [];
485
+ const groupRefs: FormattedSelector['ref'] = [];
486
+ const acRefs: FormattedSelector['ref'] = [];
398
487
  if (user.group != null && user.group.guid != null) {
399
488
  // It belongs to the user's primary group.
400
489
  groupRefs.push(['group', user.group]);
@@ -427,11 +516,13 @@ export default class Tilmeld {
427
516
  if (groupRefs.length) {
428
517
  subSelectors.push({
429
518
  type: '&',
430
- gte: ['acGroup', Tilmeld.READ_ACCESS],
431
- selector: {
432
- type: '|',
433
- ref: groupRefs,
434
- },
519
+ gte: [['acGroup', TilmeldAccessLevels.READ_ACCESS]],
520
+ selector: [
521
+ {
522
+ type: '|',
523
+ ref: groupRefs,
524
+ },
525
+ ],
435
526
  });
436
527
  }
437
528
  // All the acRead, acWrite, and acFull refs.
@@ -474,9 +565,9 @@ export default class Tilmeld {
474
565
  *
475
566
  * These properties defaults to:
476
567
  *
477
- * - acUser = Tilmeld.FULL_ACCESS
478
- * - acGroup = Tilmeld.READ_ACCESS
479
- * - acOther = Tilmeld.NO_ACCESS
568
+ * - acUser = TilmeldAccessLevels.FULL_ACCESS
569
+ * - acGroup = TilmeldAccessLevels.READ_ACCESS
570
+ * - acOther = TilmeldAccessLevels.NO_ACCESS
480
571
  *
481
572
  * "acRead", "acWrite", and "acFull" are arrays of users and/or groups that
482
573
  * also have those permissions.
@@ -505,13 +596,13 @@ export default class Tilmeld {
505
596
  * - None of the above. (Check other AC.)
506
597
  *
507
598
  * @param entity The entity to check.
508
- * @param type The lowest level of permission to consider a pass. One of Tilmeld.READ_ACCESS, Tilmeld.WRITE_ACCESS, or Tilmeld.FULL_ACCESS.
599
+ * @param type The lowest level of permission to consider a pass.
509
600
  * @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
510
601
  * @returns Whether the current user has at least `type` permission for the entity.
511
602
  */
512
- public static checkPermissions(
603
+ public checkPermissions(
513
604
  entity: EntityInterface & AccessControlData,
514
- type = Tilmeld.READ_ACCESS,
605
+ type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
515
606
  user?: (User & UserData) | false
516
607
  ) {
517
608
  // Only works for entities.
@@ -520,11 +611,11 @@ export default class Tilmeld {
520
611
  }
521
612
 
522
613
  let userOrNull: (User & UserData) | null = null;
523
- let userOrEmpty: User & UserData = User.factorySync();
614
+ let userOrEmpty: User & UserData = this.User.factorySync();
524
615
  // Calculate the user.
525
616
  if (user == null) {
526
617
  userOrNull = this.currentUser;
527
- userOrEmpty = User.current(true);
618
+ userOrEmpty = this.User.current(true);
528
619
  } else if (user !== false) {
529
620
  userOrNull = user;
530
621
  userOrEmpty = user;
@@ -537,7 +628,8 @@ export default class Tilmeld {
537
628
  // Users and groups are always readable. Editable by Tilmeld admins.
538
629
  if (
539
630
  (entity instanceof User || entity instanceof Group) &&
540
- (type === Tilmeld.READ_ACCESS || userOrEmpty.$gatekeeper('tilmeld/admin'))
631
+ (type === TilmeldAccessLevels.READ_ACCESS ||
632
+ userOrEmpty.$gatekeeper('tilmeld/admin'))
541
633
  ) {
542
634
  return true;
543
635
  }
@@ -548,9 +640,9 @@ export default class Tilmeld {
548
640
  }
549
641
 
550
642
  // Load access control, since we need it now...
551
- const acUser = entity.acUser ?? Tilmeld.FULL_ACCESS;
552
- const acGroup = entity.acGroup ?? Tilmeld.READ_ACCESS;
553
- const acOther = entity.acOther ?? Tilmeld.NO_ACCESS;
643
+ const acUser = entity.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
644
+ const acGroup = entity.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
645
+ const acOther = entity.acOther ?? TilmeldAccessLevels.NO_ACCESS;
554
646
 
555
647
  if (userOrNull === null) {
556
648
  return acOther >= type;
@@ -566,7 +658,7 @@ export default class Tilmeld {
566
658
  userOrEmpty.group != null &&
567
659
  typeof userOrEmpty.group.$is === 'function' &&
568
660
  userOrEmpty.group.$is(entity) &&
569
- type === Tilmeld.READ_ACCESS
661
+ type === TilmeldAccessLevels.READ_ACCESS
570
662
  ) {
571
663
  return true;
572
664
  }
@@ -578,9 +670,18 @@ export default class Tilmeld {
578
670
 
579
671
  // Check access ac properties.
580
672
  const checks = [
581
- { type: Tilmeld.FULL_ACCESS, array: [...(entity.acFull ?? [])] },
582
- { type: Tilmeld.WRITE_ACCESS, array: [...(entity.acWrite ?? [])] },
583
- { type: Tilmeld.READ_ACCESS, array: [...(entity.acRead ?? [])] },
673
+ {
674
+ type: TilmeldAccessLevels.FULL_ACCESS,
675
+ array: [...(entity.acFull ?? [])],
676
+ },
677
+ {
678
+ type: TilmeldAccessLevels.WRITE_ACCESS,
679
+ array: [...(entity.acWrite ?? [])],
680
+ },
681
+ {
682
+ type: TilmeldAccessLevels.READ_ACCESS,
683
+ array: [...(entity.acRead ?? [])],
684
+ },
584
685
  ];
585
686
  for (let curCheck of checks) {
586
687
  if (type <= curCheck.type) {
@@ -616,12 +717,79 @@ export default class Tilmeld {
616
717
  return acOther >= type;
617
718
  }
618
719
 
720
+ /**
721
+ * Check a UID's permissions for a user.
722
+ *
723
+ * ## THIS ONLY CHECKS AUTOMATICALLY FOR CLIENT REQUESTS.
724
+ *
725
+ * UID functions on the Node.js side are not checked automatically. This
726
+ * function is only run automatically for UID functions run from the client.
727
+ * You should call this function manually if you're running a UID function on
728
+ * the Node.js side and you want it gated.
729
+ *
730
+ * This will check the Tilmeld config and the user's abilities.
731
+ *
732
+ * - READ_ACCESS, the UID is listed in clientReadableUIDs or the user has the
733
+ * "uid/get/nameofuid" ability.
734
+ * - WRITE_ACCESS, the UID is listed in clientEnabledUIDs or the user has the
735
+ * "uid/new/nameofuid" ability.
736
+ * - FULL_ACCESS, the UID is listed in clientSetabledUIDs or the user has the
737
+ * "uid/set/nameofuid" ability.
738
+ *
739
+ * @param name The UID to check.
740
+ * @param type The lowest level of permission to consider a pass.
741
+ * @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
742
+ * @returns Whether the current user has at least `type` permission for the UID.
743
+ */
744
+ public checkClientUIDPermissions(
745
+ name: string,
746
+ type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
747
+ user?: (User & UserData) | false
748
+ ) {
749
+ let userOrEmpty: User & UserData = this.User.factorySync();
750
+ // Calculate the user.
751
+ if (user == null) {
752
+ userOrEmpty = this.User.current(true);
753
+ } else if (user !== false) {
754
+ userOrEmpty = user;
755
+ }
756
+
757
+ if (userOrEmpty.$gatekeeper('system/admin')) {
758
+ return true;
759
+ }
760
+
761
+ if (type === TilmeldAccessLevels.FULL_ACCESS) {
762
+ return (
763
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
764
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
765
+ );
766
+ } else if (type === TilmeldAccessLevels.WRITE_ACCESS) {
767
+ return (
768
+ this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
769
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
770
+ userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
771
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
772
+ );
773
+ } else if (type === TilmeldAccessLevels.READ_ACCESS) {
774
+ return (
775
+ this.config.clientReadableUIDs.indexOf(name) !== -1 ||
776
+ this.config.clientEnabledUIDs.indexOf(name) !== -1 ||
777
+ this.config.clientSetabledUIDs.indexOf(name) !== -1 ||
778
+ userOrEmpty.$gatekeeper(`uid/get/${name}`) ||
779
+ userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
780
+ userOrEmpty.$gatekeeper(`uid/set/${name}`)
781
+ );
782
+ }
783
+
784
+ return false;
785
+ }
786
+
619
787
  /**
620
788
  * Fill session user data.
621
789
  *
622
790
  * @param user The user.
623
791
  */
624
- public static fillSession(user: User & UserData) {
792
+ public fillSession(user: User & UserData) {
625
793
  // Read groups right now, since gatekeeper needs them, so
626
794
  // $udpateDataProtection will fail to read them (since it runs gatekeeper).
627
795
  const _group = user.group;
@@ -640,7 +808,7 @@ export default class Tilmeld {
640
808
  /**
641
809
  * Clear session user data.
642
810
  */
643
- public static clearSession() {
811
+ public clearSession() {
644
812
  const user = this.currentUser;
645
813
  this.currentUser = null;
646
814
  if (user) {
@@ -660,15 +828,15 @@ export default class Tilmeld {
660
828
  * @param token The authentication token.
661
829
  * @returns The user on success, null on failure.
662
830
  */
663
- public static async extractToken(token: string) {
664
- const extract = this.config.jwtExtract(token);
831
+ public async extractToken(token: string) {
832
+ const extract = this.config.jwtExtract(this.config, token);
665
833
  if (extract == null) {
666
834
  return null;
667
835
  }
668
836
  const { guid } = extract;
669
837
 
670
- const user = await Nymph.getEntity(
671
- { class: User },
838
+ const user = await this.nymph.getEntity(
839
+ { class: this.User },
672
840
  {
673
841
  type: '&',
674
842
  guid: guid,
@@ -687,7 +855,7 @@ export default class Tilmeld {
687
855
  * @param skipXsrfToken Skip the XSRF token check.
688
856
  * @returns True if a user was authenticated, false on any failure.
689
857
  */
690
- public static authenticate(skipXsrfToken = false) {
858
+ public authenticate(skipXsrfToken = false) {
691
859
  if (this.request == null) {
692
860
  return false;
693
861
  }
@@ -698,9 +866,9 @@ export default class Tilmeld {
698
866
  // to provide the auth token.
699
867
  let fromAuthHeader = false;
700
868
  let authToken: string;
701
- if (this.request.header('HTTP_X_TILMELDAUTH') != null) {
869
+ if (this.request.header('x-tilmeldauth') != null) {
702
870
  fromAuthHeader = true;
703
- authToken = this.request.header('HTTP_X_TILMELDAUTH') as string;
871
+ authToken = this.request.header('x-tilmeldauth') as string;
704
872
  } else if ('TILMELDAUTH' in cookies) {
705
873
  fromAuthHeader = false;
706
874
  authToken = cookies.TILMELDAUTH;
@@ -715,16 +883,16 @@ export default class Tilmeld {
715
883
  ) {
716
884
  // The request is for the setup app, or we were told to skip the XSRF
717
885
  // check, so don't check for the XSRF token.
718
- extract = this.config.jwtExtract(authToken);
886
+ extract = this.config.jwtExtract(this.config, authToken);
719
887
  } else {
720
888
  // The request is for something else, so check for a valid XSRF token,
721
889
  // unless the auth token is provided by a header (instead of a cookie).
722
- const xsrfToken = this.request.header('HTTP_X_XSRF_TOKEN');
890
+ const xsrfToken = this.request.header('x-xsrf-token');
723
891
  if (xsrfToken == null && !fromAuthHeader) {
724
892
  return false;
725
893
  }
726
894
 
727
- extract = this.config.jwtExtract(authToken, xsrfToken);
895
+ extract = this.config.jwtExtract(this.config, authToken, xsrfToken);
728
896
  }
729
897
 
730
898
  if (extract == null) {
@@ -733,7 +901,7 @@ export default class Tilmeld {
733
901
  }
734
902
  const { guid, expire } = extract;
735
903
 
736
- const user = User.factorySync(guid);
904
+ const user = this.User.factorySync(guid);
737
905
  if (user.guid == null || !user.enabled) {
738
906
  this.logout();
739
907
  return false;
@@ -756,10 +924,10 @@ export default class Tilmeld {
756
924
  * @param sendAuthHeader When true, a custom header with the auth token will be sent.
757
925
  * @returns True on success, false on failure.
758
926
  */
759
- public static login(user: User & UserData, sendAuthHeader: boolean) {
927
+ public login(user: User & UserData, sendAuthHeader: boolean) {
760
928
  if (user.guid != null && user.enabled) {
761
929
  if (this.response && !this.response.headersSent) {
762
- const token = this.config.jwtBuilder(user);
930
+ const token = this.config.jwtBuilder(this.config, user);
763
931
  const appUrl = new URL(this.config.appUrl);
764
932
  this.response.cookie('TILMELDAUTH', token, {
765
933
  domain: this.config.cookieDomain,
@@ -782,7 +950,7 @@ export default class Tilmeld {
782
950
  /**
783
951
  * Logs the current user out of the system.
784
952
  */
785
- public static logout() {
953
+ public logout() {
786
954
  this.clearSession();
787
955
  if (this.response && !this.response.headersSent) {
788
956
  this.response.clearCookie('TILMELDAUTH', {