@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/CHANGELOG.md +26 -0
- package/README.md +15 -10
- package/dist/Group.d.ts +2 -0
- package/dist/Group.js +68 -59
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +12 -7
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +26 -22
- package/dist/Tilmeld.js +172 -92
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +2 -0
- package/dist/User.js +134 -122
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +12 -7
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +7 -3
- package/dist/conf/defaults.js +41 -109
- package/dist/conf/defaults.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/tilmeldJoiProps.d.ts +12 -0
- package/dist/tilmeldJoiProps.js +20 -0
- package/dist/tilmeldJoiProps.js.map +1 -0
- package/package.json +6 -6
- package/src/Group.test.ts +18 -7
- package/src/Group.ts +88 -64
- package/src/Tilmeld.ts +284 -116
- package/src/User.test.ts +18 -7
- package/src/User.ts +164 -125
- package/src/conf/d.ts +38 -2
- package/src/conf/defaults.ts +29 -68
- package/src/index.ts +3 -1
- package/src/tilmeldJoiProps.ts +46 -0
package/src/Tilmeld.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
public
|
|
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
|
|
35
|
+
public config: Config;
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
38
|
* The currently logged in user.
|
|
32
39
|
*/
|
|
33
|
-
public
|
|
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
|
|
50
|
-
public
|
|
66
|
+
public request: Request | null = null;
|
|
67
|
+
public response: Response | null = null;
|
|
51
68
|
|
|
52
69
|
/**
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
59
|
-
* @returns Whether the user has the given ability.
|
|
77
|
+
* @param config The Tilmeld configuration.
|
|
60
78
|
*/
|
|
61
|
-
public
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
*
|
|
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
|
|
74
|
-
this.
|
|
104
|
+
public init(nymph: Nymph) {
|
|
105
|
+
this.nymph = nymph;
|
|
75
106
|
|
|
76
107
|
// Set up access control hooks when Nymph is called.
|
|
77
|
-
if (
|
|
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
|
|
149
|
+
private initAccessControl() {
|
|
150
|
+
const tilmeld = this;
|
|
151
|
+
|
|
88
152
|
// Check for the skip access control option and add AC selectors.
|
|
89
|
-
const
|
|
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
|
-
!
|
|
99
|
-
((!
|
|
100
|
-
options.class?.factorySync() instanceof User) ||
|
|
101
|
-
(!
|
|
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
|
-
|
|
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][
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 =
|
|
141
|
-
{ class:
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
220
|
-
* - acGroup =
|
|
221
|
-
* - acOther =
|
|
298
|
+
* - acUser = TilmeldAccessLevels.FULL_ACCESS
|
|
299
|
+
* - acGroup = TilmeldAccessLevels.READ_ACCESS
|
|
300
|
+
* - acOther = TilmeldAccessLevels.NO_ACCESS
|
|
222
301
|
*/
|
|
223
|
-
const addAccess = function (
|
|
224
|
-
|
|
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 =
|
|
324
|
+
entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
|
|
243
325
|
}
|
|
244
326
|
if (entity.acGroup == null) {
|
|
245
|
-
entity.acGroup =
|
|
327
|
+
entity.acGroup = TilmeldAccessLevels.READ_ACCESS;
|
|
246
328
|
}
|
|
247
329
|
if (entity.acOther == null) {
|
|
248
|
-
entity.acOther =
|
|
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 (
|
|
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 <
|
|
268
|
-
prop >
|
|
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
|
-
|
|
319
|
-
Nymph.on('beforeGetEntities', getEntities);
|
|
405
|
+
this.nymph.on('query', handleQuery);
|
|
320
406
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
407
|
+
this.nymph.on('beforeSaveEntity', addAccess);
|
|
408
|
+
this.nymph.on('beforeSaveEntity', validate);
|
|
409
|
+
this.nymph.on('beforeSaveEntity', checkPermissionsSaveAndFilterAcChanges);
|
|
324
410
|
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
421
|
+
public addAccessControlSelectors(
|
|
336
422
|
options: Options,
|
|
337
|
-
selectors:
|
|
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
|
|
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',
|
|
450
|
+
gte: [['acOther', TilmeldAccessLevels.READ_ACCESS]],
|
|
364
451
|
// The user and group are not set.
|
|
365
|
-
selector:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
452
|
+
selector: [
|
|
453
|
+
{
|
|
454
|
+
type: '&',
|
|
455
|
+
'!defined': ['user', 'group'],
|
|
456
|
+
},
|
|
457
|
+
],
|
|
369
458
|
});
|
|
370
459
|
} else {
|
|
371
|
-
const subSelectors:
|
|
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',
|
|
468
|
+
ref: [['user', user]],
|
|
469
|
+
gte: [['acUser', TilmeldAccessLevels.READ_ACCESS]],
|
|
381
470
|
},
|
|
382
471
|
];
|
|
383
|
-
const selector:
|
|
472
|
+
const selector: FormattedSelector = {
|
|
384
473
|
type: '|',
|
|
385
474
|
// Other access control is sufficient.
|
|
386
|
-
gte: ['acOther',
|
|
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:
|
|
397
|
-
const acRefs:
|
|
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',
|
|
431
|
-
selector:
|
|
432
|
-
|
|
433
|
-
|
|
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 =
|
|
478
|
-
* - acGroup =
|
|
479
|
-
* - acOther =
|
|
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.
|
|
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
|
|
603
|
+
public checkPermissions(
|
|
513
604
|
entity: EntityInterface & AccessControlData,
|
|
514
|
-
type =
|
|
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 ===
|
|
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 ??
|
|
552
|
-
const acGroup = entity.acGroup ??
|
|
553
|
-
const acOther = entity.acOther ??
|
|
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 ===
|
|
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
|
-
{
|
|
582
|
-
|
|
583
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
869
|
+
if (this.request.header('x-tilmeldauth') != null) {
|
|
702
870
|
fromAuthHeader = true;
|
|
703
|
-
authToken = this.request.header('
|
|
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('
|
|
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
|
|
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
|
|
953
|
+
public logout() {
|
|
786
954
|
this.clearSession();
|
|
787
955
|
if (this.response && !this.response.headersSent) {
|
|
788
956
|
this.response.clearCookie('TILMELDAUTH', {
|