@nymphjs/tilmeld 1.0.0-alpha.9 → 1.0.0-beta.1

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAuB;AAEvB,2CAAyB;AAEzB,8DAAsC;AAC7B,qBADF,oBAAU,CACE;AAEnB,0CAAwB;AACxB,oDAA4B;AACnB,gBADF,eAAK,CACE;AAEd,wDAAgC;AACvB,kBADF,iBAAO,CACE;AAEhB,kDAAgC;AAEhC,wEAAgD;AACvC,0BADF,yBAAe,CACE;AAExB,yCAAuB;AACvB,kDAA0B;AACjB,eADF,cAAI,CACE"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,yCAAuB;AAEvB,2CAAyB;AAEzB,8DAAsC;AAC7B,qBADF,oBAAU,CACE;AAEnB,0CAAwB;AACxB,oDAA4B;AACnB,gBADF,eAAK,CACE;AAEd,wDAAgC;AACvB,kBADF,iBAAO,CACE;AAEhB,kDAAgC;AAEhC,wEAAgD;AACvC,0BADF,yBAAe,CACE;AAExB,yCAAuB;AACvB,kDAA0B;AACjB,eADF,cAAI,CACE"}
@@ -2,11 +2,11 @@ import Joi from 'joi';
2
2
  declare const _default: {
3
3
  user: Joi.ObjectSchema<any>;
4
4
  group: Joi.ObjectSchema<any>;
5
- acUser: Joi.NumberSchema;
6
- acGroup: Joi.NumberSchema;
7
- acOther: Joi.NumberSchema;
8
- acRead: Joi.ArraySchema;
9
- acWrite: Joi.ArraySchema;
10
- acFull: Joi.ArraySchema;
5
+ acUser: Joi.NumberSchema<number>;
6
+ acGroup: Joi.NumberSchema<number>;
7
+ acOther: Joi.NumberSchema<number>;
8
+ acRead: Joi.ArraySchema<any[]>;
9
+ acWrite: Joi.ArraySchema<any[]>;
10
+ acFull: Joi.ArraySchema<any[]>;
11
11
  };
12
12
  export default _default;
package/jest.config.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
2
  module.exports = {
3
3
  preset: 'ts-jest',
4
4
  testEnvironment: 'node',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nymphjs/tilmeld",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "NymphJS - Tilmeld User and Group System",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -31,34 +31,32 @@
31
31
  },
32
32
  "license": "Apache-2.0",
33
33
  "dependencies": {
34
- "@nymphjs/nymph": "^1.0.0-alpha.9",
34
+ "@nymphjs/guid": "^1.0.0-beta.1",
35
+ "@nymphjs/nymph": "^1.0.0-beta.1",
35
36
  "crypto-js": "^4.1.1",
36
- "email-templates": "^8.0.8",
37
- "express": "^4.17.1",
38
- "joi": "^17.4.2",
37
+ "email-templates": "^10.0.1",
38
+ "express": "^4.18.2",
39
+ "joi": "^17.7.0",
39
40
  "jsonwebtoken": "^8.5.1",
40
- "locutus": "^2.0.15",
41
+ "locutus": "^2.0.16",
41
42
  "lodash": "^4.17.21",
42
- "nanoid": "^3.1.25",
43
- "nanoid-dictionary": "^4.3.0",
44
43
  "pug": "^3.0.2"
45
44
  },
46
45
  "devDependencies": {
47
- "@nymphjs/client": "^1.0.0-alpha.9",
48
- "@nymphjs/client-node": "^1.0.0-alpha.9",
49
- "@nymphjs/driver-sqlite3": "^1.0.0-alpha.9",
46
+ "@nymphjs/client": "^1.0.0-beta.1",
47
+ "@nymphjs/client-node": "^1.0.0-beta.1",
48
+ "@nymphjs/driver-sqlite3": "^1.0.0-beta.1",
50
49
  "@tsconfig/recommended": "^1.0.1",
51
- "@types/crypto-js": "^4.0.2",
52
- "@types/email-templates": "^8.0.4",
53
- "@types/express": "^4.17.13",
54
- "@types/jest": "^27.0.1",
55
- "@types/jsonwebtoken": "^8.5.5",
50
+ "@types/crypto-js": "^4.1.1",
51
+ "@types/email-templates": "^10.0.1",
52
+ "@types/express": "^4.17.14",
53
+ "@types/jest": "^29.2.3",
54
+ "@types/jsonwebtoken": "^8.5.9",
56
55
  "@types/locutus": "^0.0.6",
57
- "@types/lodash": "^4.14.172",
58
- "@types/nanoid-dictionary": "^4.2.0",
59
- "jest": "^27.0.6",
60
- "ts-jest": "^27.0.5",
61
- "typescript": "^4.3.5"
56
+ "@types/lodash": "^4.14.189",
57
+ "jest": "^29.3.1",
58
+ "ts-jest": "^29.0.3",
59
+ "typescript": "^4.9.3"
62
60
  },
63
- "gitHead": "a665338b2e36ae10e48f187879984f359fe963a4"
61
+ "gitHead": "648d901c504d57f2d439cd751b60af0ebd4d63b8"
64
62
  }
package/src/Group.ts CHANGED
@@ -746,7 +746,7 @@ export default class Group extends AbleObject<GroupData> {
746
746
  }
747
747
 
748
748
  public async $delete() {
749
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
749
+ let tilmeld = this.$nymph.tilmeld as Tilmeld;
750
750
  if (!tilmeld.gatekeeper('tilmeld/admin')) {
751
751
  throw new BadDataError("You don't have the authority to delete groups.");
752
752
  }
@@ -755,6 +755,7 @@ export default class Group extends AbleObject<GroupData> {
755
755
  const nymph = this.$nymph;
756
756
  const tnymph = await nymph.startTransaction(transaction);
757
757
  this.$nymph = tnymph;
758
+ tilmeld = this.$nymph.tilmeld as Tilmeld;
758
759
 
759
760
  // Delete descendants.
760
761
  const descendants = await this.$getDescendants();
package/src/Tilmeld.ts CHANGED
@@ -6,7 +6,9 @@ import {
6
6
  TilmeldInterface,
7
7
  TilmeldAccessLevels,
8
8
  } from '@nymphjs/nymph';
9
+ import { URL } from 'url';
9
10
  import { Request, Response } from 'express';
11
+ import { xor } from 'lodash';
10
12
 
11
13
  import { Config, ConfigDefaults as defaults } from './conf';
12
14
  import { AccessControlError } from './errors';
@@ -201,7 +203,7 @@ export default class Tilmeld implements TilmeldInterface {
201
203
  return;
202
204
  }
203
205
  // Test for permissions.
204
- if (tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
206
+ if (!tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
205
207
  throw new AccessControlError('No permission to delete.');
206
208
  }
207
209
  };
@@ -245,15 +247,15 @@ export default class Tilmeld implements TilmeldInterface {
245
247
  // allowing a change to ac properties.
246
248
 
247
249
  const originalAc = entity.$getOriginalAcValues();
248
- const newAc = {
249
- user: entity.user ?? null,
250
- group: entity.group ?? null,
251
- acUser: entity.acUser ?? null,
252
- acGroup: entity.acGroup ?? null,
253
- acOther: entity.acOther ?? null,
254
- acRead: entity.acRead ?? null,
255
- acWrite: entity.acWrite ?? null,
256
- acFull: entity.acFull ?? null,
250
+ const newAc: AccessControlData = {
251
+ user: entity.user ?? undefined,
252
+ group: entity.group ?? undefined,
253
+ acUser: entity.acUser ?? undefined,
254
+ acGroup: entity.acGroup ?? undefined,
255
+ acOther: entity.acOther ?? undefined,
256
+ acRead: entity.acRead ?? undefined,
257
+ acWrite: entity.acWrite ?? undefined,
258
+ acFull: entity.acFull ?? undefined,
257
259
  };
258
260
 
259
261
  const setAcProperties = (acValues: { [k: string]: any }) => {
@@ -269,10 +271,102 @@ export default class Tilmeld implements TilmeldInterface {
269
271
 
270
272
  // Restore original AC properties and check permissions.
271
273
  setAcProperties(originalAc);
272
- if (tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)) {
273
- // Only allow changes to AC properties if the user has full access.
274
- // TODO: only allow changes to `user` and `group` if tilmeld admin or
275
- // group is user's group.
274
+
275
+ function areAcPropertiesChanged(
276
+ a: AccessControlData,
277
+ b: AccessControlData
278
+ ) {
279
+ for (const name of ['user', 'group'] as ('user' | 'group')[]) {
280
+ const aVal = a[name];
281
+ const bVal = b[name];
282
+ if (aVal == null && bVal == null) {
283
+ continue;
284
+ } else if (aVal == null || bVal == null) {
285
+ return true;
286
+ }
287
+ if (!aVal.$is(bVal)) {
288
+ return true;
289
+ }
290
+ }
291
+ for (const name of ['acUser', 'acGroup', 'acOther'] as (
292
+ | 'acUser'
293
+ | 'acGroup'
294
+ | 'acOther'
295
+ )[]) {
296
+ const aVal = a[name];
297
+ const bVal = b[name];
298
+ if (aVal == null && bVal == null) {
299
+ continue;
300
+ } else if (aVal == null || bVal == null) {
301
+ return true;
302
+ }
303
+ if (aVal !== bVal) {
304
+ return true;
305
+ }
306
+ }
307
+ for (const name of ['acRead', 'acWrite', 'acFull'] as (
308
+ | 'acRead'
309
+ | 'acWrite'
310
+ | 'acFull'
311
+ )[]) {
312
+ const aVal = a[name];
313
+ const bVal = b[name];
314
+ if (aVal == null && bVal == null) {
315
+ continue;
316
+ } else if (aVal == null || bVal == null) {
317
+ return true;
318
+ }
319
+ if (aVal.length !== bVal.length) {
320
+ return true;
321
+ }
322
+ if (
323
+ xor(
324
+ aVal.map((entity) => entity.guid),
325
+ bVal.map((entity) => entity.guid)
326
+ ).length
327
+ ) {
328
+ return true;
329
+ }
330
+ }
331
+ return false;
332
+ }
333
+
334
+ if (areAcPropertiesChanged(originalAc, newAc)) {
335
+ if (
336
+ !tilmeld.checkPermissions(entity, TilmeldAccessLevels.FULL_ACCESS)
337
+ ) {
338
+ // Only allow changes to AC properties if the user has full access.
339
+ throw new AccessControlError(
340
+ 'No permission to change access control properties.'
341
+ );
342
+ }
343
+
344
+ if (
345
+ ((originalAc.user && !originalAc.user.$is(newAc.user)) ||
346
+ (!originalAc.user &&
347
+ newAc.user &&
348
+ !newAc.user.$is(tilmeld.currentUser))) &&
349
+ !tilmeld.gatekeeper('tilmeld/admin')
350
+ ) {
351
+ throw new AccessControlError(
352
+ 'No permission to assign to another user.'
353
+ );
354
+ }
355
+
356
+ if (
357
+ newAc.group &&
358
+ !newAc.group.$is(originalAc.group) &&
359
+ !(
360
+ newAc.group.$is(tilmeld.currentUser?.group) ||
361
+ newAc.group.$inArray(tilmeld.currentUser?.groups ?? [])
362
+ ) &&
363
+ !tilmeld.gatekeeper('tilmeld/admin')
364
+ ) {
365
+ throw new AccessControlError(
366
+ 'No permission to assign to another group.'
367
+ );
368
+ }
369
+
276
370
  setAcProperties(newAc);
277
371
  }
278
372
  }
@@ -455,7 +549,7 @@ export default class Tilmeld implements TilmeldInterface {
455
549
  return;
456
550
  }
457
551
 
458
- if (user === null) {
552
+ if (user == null || user.guid == null) {
459
553
  selectors.push({
460
554
  type: '|',
461
555
  // Other access control is sufficient.
@@ -464,7 +558,7 @@ export default class Tilmeld implements TilmeldInterface {
464
558
  selector: [
465
559
  {
466
560
  type: '&',
467
- '!defined': ['user', 'group'],
561
+ '!defined': [['user', 'group']],
468
562
  },
469
563
  ],
470
564
  });
@@ -472,56 +566,43 @@ export default class Tilmeld implements TilmeldInterface {
472
566
  const subSelectors: FormattedSelector[] = [
473
567
  {
474
568
  type: '&',
475
- '!defined': ['user', 'group'],
569
+ '!defined': [['user', 'group']],
476
570
  },
477
571
  // It is owned by the user.
478
572
  {
479
573
  type: '&',
480
- ref: [['user', user]],
574
+ ref: [['user', user.guid]],
481
575
  gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
482
576
  },
483
577
  ];
484
- const selector: FormattedSelector = {
485
- type: '|',
486
- // Other access control is sufficient.
487
- gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
488
- // The user and group are not set.
489
- selector: subSelectors,
490
- // The user is listed in acRead, acWrite, or acFull.
491
- ref: [
492
- ['acRead', user],
493
- ['acWrite', user],
494
- ['acFull', user],
495
- ],
496
- };
497
578
  const groupRefs: FormattedSelector['ref'] = [];
498
579
  const acRefs: FormattedSelector['ref'] = [];
499
580
  if (user.group != null && user.group.guid != null) {
500
581
  // It belongs to the user's primary group.
501
- groupRefs.push(['group', user.group]);
582
+ groupRefs.push(['group', user.group.guid]);
502
583
  // User's primary group is listed in acRead, acWrite, or acFull.
503
- acRefs.push(['acRead', user.group]);
504
- acRefs.push(['acWrite', user.group]);
505
- acRefs.push(['acFull', user.group]);
584
+ acRefs.push(['acRead', user.group.guid]);
585
+ acRefs.push(['acWrite', user.group.guid]);
586
+ acRefs.push(['acFull', user.group.guid]);
506
587
  }
507
588
  for (let curSecondaryGroup of user.groups ?? []) {
508
589
  if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
509
590
  // It belongs to the user's secondary group.
510
- groupRefs.push(['group', curSecondaryGroup]);
591
+ groupRefs.push(['group', curSecondaryGroup.guid]);
511
592
  // User's secondary group is listed in acRead, acWrite, or acFull.
512
- acRefs.push(['acRead', curSecondaryGroup]);
513
- acRefs.push(['acWrite', curSecondaryGroup]);
514
- acRefs.push(['acFull', curSecondaryGroup]);
593
+ acRefs.push(['acRead', curSecondaryGroup.guid]);
594
+ acRefs.push(['acWrite', curSecondaryGroup.guid]);
595
+ acRefs.push(['acFull', curSecondaryGroup.guid]);
515
596
  }
516
597
  }
517
598
  for (let curDescendantGroup of user.$getDescendantGroupsSync()) {
518
599
  if (curDescendantGroup != null && curDescendantGroup.guid != null) {
519
600
  // It belongs to the user's secondary group.
520
- groupRefs.push(['group', curDescendantGroup]);
601
+ groupRefs.push(['group', curDescendantGroup.guid]);
521
602
  // User's secondary group is listed in acRead, acWrite, or acFull.
522
- acRefs.push(['acRead', curDescendantGroup]);
523
- acRefs.push(['acWrite', curDescendantGroup]);
524
- acRefs.push(['acFull', curDescendantGroup]);
603
+ acRefs.push(['acRead', curDescendantGroup.guid]);
604
+ acRefs.push(['acWrite', curDescendantGroup.guid]);
605
+ acRefs.push(['acFull', curDescendantGroup.guid]);
525
606
  }
526
607
  }
527
608
  // All the group refs.
@@ -544,6 +625,19 @@ export default class Tilmeld implements TilmeldInterface {
544
625
  ref: acRefs,
545
626
  });
546
627
  }
628
+ const selector: FormattedSelector = {
629
+ type: '|',
630
+ // Other access control is sufficient.
631
+ gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
632
+ // The user and group are not set.
633
+ selector: subSelectors,
634
+ // The user is listed in acRead, acWrite, or acFull.
635
+ ref: [
636
+ ['acRead', user.guid],
637
+ ['acWrite', user.guid],
638
+ ['acFull', user.guid],
639
+ ],
640
+ };
547
641
  selectors.push(selector);
548
642
  }
549
643
  }