@nymphjs/tilmeld 1.0.0-beta.8 → 1.0.0-beta.80
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 +364 -0
- package/README.md +14 -1
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +16 -1
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +130 -10
- package/dist/Group.js +311 -129
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +77 -2
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +237 -9
- package/dist/Tilmeld.js +569 -201
- package/dist/Tilmeld.js.map +1 -1
- package/dist/User.d.ts +344 -16
- package/dist/User.js +979 -313
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +63 -2
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +250 -3
- package/dist/conf/defaults.js +146 -98
- package/dist/conf/defaults.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +22 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -2
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -2
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -2
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -2
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -26
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +102 -4
- package/src/Group.ts +274 -158
- package/src/Tilmeld.ts +383 -249
- package/src/User.test.ts +80 -4
- package/src/User.ts +1061 -382
- package/src/conf/d.ts +26 -13
- package/src/conf/defaults.ts +151 -104
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/index.ts +2 -0
- package/src/tilmeldJoiProps.ts +9 -9
- package/tsconfig.json +3 -3
- package/typedoc.json +4 -0
package/src/Tilmeld.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Options,
|
|
6
6
|
TilmeldInterface,
|
|
7
7
|
TilmeldAccessLevels,
|
|
8
|
+
ACProperties,
|
|
8
9
|
} from '@nymphjs/nymph';
|
|
9
10
|
import { URL } from 'url';
|
|
10
11
|
import { Request, Response } from 'express';
|
|
@@ -15,6 +16,7 @@ import { AccessControlError } from './errors';
|
|
|
15
16
|
import Group from './Group';
|
|
16
17
|
import User, { UserData } from './User';
|
|
17
18
|
import { AccessControlData } from './Tilmeld.types';
|
|
19
|
+
import { enforceTilmeld } from './enforceTilmeld';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* A user and group system for Nymph.js.
|
|
@@ -69,9 +71,17 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
69
71
|
public response: Response | null = null;
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
|
-
*
|
|
74
|
+
* Used to avoid infinite loop.
|
|
73
75
|
*/
|
|
74
|
-
private
|
|
76
|
+
private alreadyLoggedOutSwitch = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gatekeeper ability cache.
|
|
80
|
+
*
|
|
81
|
+
* Gatekeeper will cache the user's abilities that it calculates, so it can
|
|
82
|
+
* check faster if that user has been checked before.
|
|
83
|
+
*/
|
|
84
|
+
private gatekeeperCache: { [k: string]: true } | null = null;
|
|
75
85
|
|
|
76
86
|
/**
|
|
77
87
|
* Create a new instance of Tilmeld.
|
|
@@ -80,6 +90,24 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
80
90
|
*/
|
|
81
91
|
public constructor(config: Partial<Config> & { jwtSecret: string }) {
|
|
82
92
|
this.config = { ...defaults, ...config };
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
this.config.emailUsernames &&
|
|
96
|
+
(!this.config.userFields.includes('email') ||
|
|
97
|
+
!this.config.regFields.includes('email'))
|
|
98
|
+
) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
'You must include email in userFields and regFields if you use emailUsernames.',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (let field of this.config.regFields) {
|
|
105
|
+
if (!this.config.userFields.includes(field)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`All fields in regFields must also be in userFields: ${field}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
/**
|
|
@@ -92,7 +120,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
92
120
|
tilmeld.request = this.request;
|
|
93
121
|
tilmeld.response = this.response;
|
|
94
122
|
tilmeld.currentUser = this.currentUser;
|
|
95
|
-
tilmeld.
|
|
123
|
+
tilmeld.gatekeeperCache = this.gatekeeperCache;
|
|
96
124
|
|
|
97
125
|
return tilmeld;
|
|
98
126
|
}
|
|
@@ -106,32 +134,45 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
106
134
|
*/
|
|
107
135
|
public init(nymph: Nymph) {
|
|
108
136
|
this.nymph = nymph;
|
|
109
|
-
const tilmeld = this;
|
|
110
137
|
|
|
111
|
-
// Set up access control hooks when Nymph is called.
|
|
112
138
|
if (this.nymph.driver == null) {
|
|
113
139
|
throw new Error("Tilmeld can't be configured before Nymph.");
|
|
114
140
|
}
|
|
115
141
|
|
|
116
142
|
// Configure the classes.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
143
|
+
if (nymph.parent) {
|
|
144
|
+
// This is a cloned Nymph, it should have classes already.
|
|
145
|
+
this.User = this.nymph.getEntityClass(User);
|
|
146
|
+
this.Group = this.nymph.getEntityClass(Group);
|
|
147
|
+
} else {
|
|
148
|
+
// This is a new Nymph, add the classes.
|
|
149
|
+
this.User = this.nymph.addEntityClass(User);
|
|
150
|
+
this.Group = this.nymph.addEntityClass(Group);
|
|
120
151
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
|
|
153
|
+
// Clone the current user so its Nymph instance is correct.
|
|
154
|
+
if (this.currentUser) {
|
|
155
|
+
const currentUserGUID = this.currentUser.guid;
|
|
156
|
+
const currentUserCDate = this.currentUser.cdate;
|
|
157
|
+
const currentUserMDate = this.currentUser.mdate;
|
|
158
|
+
const currentUserTags = this.currentUser.tags;
|
|
159
|
+
const currentUserData = this.currentUser.$getData();
|
|
160
|
+
const currentUserSData = this.currentUser.$getSData();
|
|
161
|
+
|
|
162
|
+
const user = this.User.factorySync();
|
|
163
|
+
user.guid = currentUserGUID;
|
|
164
|
+
user.cdate = currentUserCDate;
|
|
165
|
+
user.mdate = currentUserMDate;
|
|
166
|
+
user.tags = currentUserTags;
|
|
167
|
+
user.$putData(currentUserData, currentUserSData);
|
|
168
|
+
this.currentUser = user;
|
|
169
|
+
this.currentUser.$updateDataProtection();
|
|
170
|
+
this.currentUser.$updateGroupDataProtection();
|
|
126
171
|
}
|
|
127
|
-
(NymphUser as any).skipOnClone = true;
|
|
128
|
-
this.Group = this.nymph.addEntityClass(NymphGroup);
|
|
129
172
|
|
|
173
|
+
// Set up access control hooks when Nymph is called.
|
|
174
|
+
this.nymph.config.debugInfo('tilmeld', 'Initializing access controls.');
|
|
130
175
|
this.initAccessControl();
|
|
131
|
-
if (this.request != null && !this.skipAuthenticate) {
|
|
132
|
-
this.authenticate();
|
|
133
|
-
}
|
|
134
|
-
this.skipAuthenticate = false;
|
|
135
176
|
}
|
|
136
177
|
|
|
137
178
|
/**
|
|
@@ -143,11 +184,17 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
143
184
|
* @param ability The ability.
|
|
144
185
|
* @returns Whether the user has the given ability.
|
|
145
186
|
*/
|
|
146
|
-
public gatekeeper(ability?: string)
|
|
147
|
-
if (this.currentUser == null) {
|
|
187
|
+
public gatekeeper(ability?: string) {
|
|
188
|
+
if (this.currentUser == null || this.gatekeeperCache == null) {
|
|
148
189
|
return false;
|
|
149
190
|
}
|
|
150
|
-
|
|
191
|
+
if (ability == null) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return (
|
|
195
|
+
(ability in this.gatekeeperCache && !!this.gatekeeperCache[ability]) ||
|
|
196
|
+
!!this.currentUser.abilities?.includes('system/admin')
|
|
197
|
+
);
|
|
151
198
|
}
|
|
152
199
|
|
|
153
200
|
private initAccessControl() {
|
|
@@ -155,9 +202,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
155
202
|
const handleQuery = function (
|
|
156
203
|
nymph: Nymph,
|
|
157
204
|
options: Options,
|
|
158
|
-
selectors: FormattedSelector[]
|
|
205
|
+
selectors: FormattedSelector[],
|
|
159
206
|
) {
|
|
160
|
-
const tilmeld = nymph
|
|
207
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
161
208
|
if (
|
|
162
209
|
!options ||
|
|
163
210
|
!('skipAc' in options) ||
|
|
@@ -166,7 +213,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
166
213
|
) {
|
|
167
214
|
if (options?.source === 'client') {
|
|
168
215
|
if (
|
|
169
|
-
!tilmeld.
|
|
216
|
+
(!tilmeld.currentUser ||
|
|
217
|
+
(!tilmeld.currentUser.abilities?.includes('tilmeld/admin') &&
|
|
218
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) && // The user is not an admin.
|
|
170
219
|
((!tilmeld.config.enableUserSearch &&
|
|
171
220
|
options.class?.factorySync() instanceof User) || // The use is searching for a user and searching is disabled.
|
|
172
221
|
(!tilmeld.config.enableGroupSearch &&
|
|
@@ -195,9 +244,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
195
244
|
// Filter entities being deleted for user permissions.
|
|
196
245
|
const checkPermissionsDelete = async function (
|
|
197
246
|
nymph: Nymph,
|
|
198
|
-
entity: EntityInterface
|
|
247
|
+
entity: EntityInterface,
|
|
199
248
|
) {
|
|
200
|
-
const tilmeld = nymph
|
|
249
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
201
250
|
if (
|
|
202
251
|
typeof entity.$tilmeldDeleteSkipAC === 'function' &&
|
|
203
252
|
entity.$tilmeldDeleteSkipAC()
|
|
@@ -215,11 +264,11 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
215
264
|
const checkPermissionsDeleteByID = async function (
|
|
216
265
|
nymph: Nymph,
|
|
217
266
|
guid: string,
|
|
218
|
-
className?: string
|
|
267
|
+
className?: string,
|
|
219
268
|
) {
|
|
220
|
-
const entity = nymph.
|
|
269
|
+
const entity = await nymph.getEntity(
|
|
221
270
|
{ class: nymph.getEntityClass(className ?? 'Entity') },
|
|
222
|
-
{ type: '&', guid: guid }
|
|
271
|
+
{ type: '&', guid: guid },
|
|
223
272
|
);
|
|
224
273
|
if (entity != null) {
|
|
225
274
|
checkPermissionsDelete(nymph, entity);
|
|
@@ -231,9 +280,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
231
280
|
// disallowed changes to AC properties.
|
|
232
281
|
const checkPermissionsSaveAndFilterAcChanges = async function (
|
|
233
282
|
nymph: Nymph,
|
|
234
|
-
entity: EntityInterface & AccessControlData
|
|
283
|
+
entity: EntityInterface & AccessControlData,
|
|
235
284
|
) {
|
|
236
|
-
const tilmeld = nymph
|
|
285
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
237
286
|
if (!entity) {
|
|
238
287
|
return;
|
|
239
288
|
}
|
|
@@ -249,35 +298,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
249
298
|
// allowing a change to ac properties.
|
|
250
299
|
|
|
251
300
|
const originalAc = entity.$getOriginalAcValues();
|
|
252
|
-
const newAc
|
|
253
|
-
user: entity.user ?? undefined,
|
|
254
|
-
group: entity.group ?? undefined,
|
|
255
|
-
acUser: entity.acUser ?? undefined,
|
|
256
|
-
acGroup: entity.acGroup ?? undefined,
|
|
257
|
-
acOther: entity.acOther ?? undefined,
|
|
258
|
-
acRead: entity.acRead ?? undefined,
|
|
259
|
-
acWrite: entity.acWrite ?? undefined,
|
|
260
|
-
acFull: entity.acFull ?? undefined,
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const setAcProperties = (acValues: { [k: string]: any }) => {
|
|
264
|
-
for (let name in acValues) {
|
|
265
|
-
const value = acValues[name];
|
|
266
|
-
if (value == null) {
|
|
267
|
-
delete entity[name];
|
|
268
|
-
} else {
|
|
269
|
-
entity[name] = value;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
};
|
|
301
|
+
const newAc = entity.$getCurrentAcValues();
|
|
273
302
|
|
|
274
|
-
|
|
275
|
-
setAcProperties(originalAc);
|
|
276
|
-
|
|
277
|
-
function areAcPropertiesChanged(
|
|
278
|
-
a: AccessControlData,
|
|
279
|
-
b: AccessControlData
|
|
280
|
-
) {
|
|
303
|
+
function areAcPropertiesChanged(a: ACProperties, b: ACProperties) {
|
|
281
304
|
for (const name of ['user', 'group'] as ('user' | 'group')[]) {
|
|
282
305
|
const aVal = a[name];
|
|
283
306
|
const bVal = b[name];
|
|
@@ -286,7 +309,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
286
309
|
} else if (aVal == null || bVal == null) {
|
|
287
310
|
return true;
|
|
288
311
|
}
|
|
289
|
-
if (
|
|
312
|
+
if (aVal !== bVal) {
|
|
290
313
|
return true;
|
|
291
314
|
}
|
|
292
315
|
}
|
|
@@ -321,12 +344,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
321
344
|
if (aVal.length !== bVal.length) {
|
|
322
345
|
return true;
|
|
323
346
|
}
|
|
324
|
-
if (
|
|
325
|
-
xor(
|
|
326
|
-
aVal.map((entity) => entity.guid),
|
|
327
|
-
bVal.map((entity) => entity.guid)
|
|
328
|
-
).length
|
|
329
|
-
) {
|
|
347
|
+
if (xor(aVal, bVal).length) {
|
|
330
348
|
return true;
|
|
331
349
|
}
|
|
332
350
|
}
|
|
@@ -335,41 +353,42 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
335
353
|
|
|
336
354
|
if (areAcPropertiesChanged(originalAc, newAc)) {
|
|
337
355
|
if (
|
|
338
|
-
!tilmeld.checkPermissions(
|
|
356
|
+
!tilmeld.checkPermissions(
|
|
357
|
+
entity,
|
|
358
|
+
TilmeldAccessLevels.FULL_ACCESS,
|
|
359
|
+
undefined,
|
|
360
|
+
originalAc,
|
|
361
|
+
)
|
|
339
362
|
) {
|
|
340
363
|
// Only allow changes to AC properties if the user has full access.
|
|
341
364
|
throw new AccessControlError(
|
|
342
|
-
'No permission to change access control properties.'
|
|
365
|
+
'No permission to change access control properties.',
|
|
343
366
|
);
|
|
344
367
|
}
|
|
345
368
|
|
|
346
369
|
if (
|
|
347
|
-
(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
!tilmeld.gatekeeper('tilmeld/admin')
|
|
370
|
+
(originalAc.user != null && originalAc.user !== newAc.user) ||
|
|
371
|
+
(originalAc.user == null &&
|
|
372
|
+
newAc.user != null &&
|
|
373
|
+
newAc.user !== tilmeld.currentUser?.guid)
|
|
352
374
|
) {
|
|
353
375
|
throw new AccessControlError(
|
|
354
|
-
'No permission to assign to another user.'
|
|
376
|
+
'No permission to assign to another user.',
|
|
355
377
|
);
|
|
356
378
|
}
|
|
357
379
|
|
|
358
380
|
if (
|
|
359
|
-
newAc.group &&
|
|
360
|
-
|
|
381
|
+
newAc.group != null &&
|
|
382
|
+
newAc.group !== originalAc.group &&
|
|
361
383
|
!(
|
|
362
|
-
newAc.group
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
!tilmeld.gatekeeper('tilmeld/admin')
|
|
384
|
+
newAc.group === tilmeld.currentUser?.$getGid() ||
|
|
385
|
+
(tilmeld.currentUser?.$getGids() ?? []).includes(newAc.group)
|
|
386
|
+
)
|
|
366
387
|
) {
|
|
367
388
|
throw new AccessControlError(
|
|
368
|
-
'No permission to assign to another group.'
|
|
389
|
+
'No permission to assign to another group.',
|
|
369
390
|
);
|
|
370
391
|
}
|
|
371
|
-
|
|
372
|
-
setAcProperties(newAc);
|
|
373
392
|
}
|
|
374
393
|
}
|
|
375
394
|
|
|
@@ -398,9 +417,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
398
417
|
*/
|
|
399
418
|
const addAccess = async function (
|
|
400
419
|
nymph: Nymph,
|
|
401
|
-
entity: EntityInterface & AccessControlData
|
|
420
|
+
entity: EntityInterface & AccessControlData,
|
|
402
421
|
) {
|
|
403
|
-
const tilmeld = nymph
|
|
422
|
+
const tilmeld = enforceTilmeld(nymph);
|
|
404
423
|
const user = tilmeld.currentUser;
|
|
405
424
|
if (
|
|
406
425
|
user != null &&
|
|
@@ -411,12 +430,11 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
411
430
|
if (entity.user == null) {
|
|
412
431
|
entity.user = user;
|
|
413
432
|
}
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
entity.group = user.group;
|
|
433
|
+
if (entity.group == null) {
|
|
434
|
+
const group = user.group;
|
|
435
|
+
if (group != null && group.guid != null) {
|
|
436
|
+
entity.group = group;
|
|
437
|
+
}
|
|
420
438
|
}
|
|
421
439
|
if (entity.acUser == null) {
|
|
422
440
|
entity.acUser = TilmeldAccessLevels.FULL_ACCESS;
|
|
@@ -442,7 +460,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
442
460
|
|
|
443
461
|
const validate = async function (
|
|
444
462
|
_nymph: Nymph,
|
|
445
|
-
entity: EntityInterface & AccessControlData
|
|
463
|
+
entity: EntityInterface & AccessControlData,
|
|
446
464
|
) {
|
|
447
465
|
if (!(entity instanceof User) && !(entity instanceof Group)) {
|
|
448
466
|
const ownershipAcPropertyValidator = (prop: any) => {
|
|
@@ -452,7 +470,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
452
470
|
prop > TilmeldAccessLevels.FULL_ACCESS
|
|
453
471
|
) {
|
|
454
472
|
throw new AccessControlError(
|
|
455
|
-
'Invalid access control property: ' + prop
|
|
473
|
+
'Invalid access control property: ' + prop,
|
|
456
474
|
);
|
|
457
475
|
}
|
|
458
476
|
};
|
|
@@ -460,13 +478,13 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
460
478
|
const accessAcPropertyValidator = (prop: any) => {
|
|
461
479
|
if (!Array.isArray(prop)) {
|
|
462
480
|
throw new AccessControlError(
|
|
463
|
-
'Invalid access control property: ' + prop
|
|
481
|
+
'Invalid access control property: ' + prop,
|
|
464
482
|
);
|
|
465
483
|
}
|
|
466
484
|
prop.forEach((value) => {
|
|
467
485
|
if (!(value instanceof User || value instanceof Group)) {
|
|
468
486
|
throw new AccessControlError(
|
|
469
|
-
'Invalid access control property: ' + prop
|
|
487
|
+
'Invalid access control property: ' + prop,
|
|
470
488
|
);
|
|
471
489
|
}
|
|
472
490
|
});
|
|
@@ -475,7 +493,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
475
493
|
if ('user' in entity) {
|
|
476
494
|
if (!(entity.user instanceof User)) {
|
|
477
495
|
throw new AccessControlError(
|
|
478
|
-
'Invalid access control property: user'
|
|
496
|
+
'Invalid access control property: user',
|
|
479
497
|
);
|
|
480
498
|
}
|
|
481
499
|
}
|
|
@@ -483,7 +501,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
483
501
|
if ('group' in entity) {
|
|
484
502
|
if (!(entity.group instanceof Group)) {
|
|
485
503
|
throw new AccessControlError(
|
|
486
|
-
'Invalid access control property: group'
|
|
504
|
+
'Invalid access control property: group',
|
|
487
505
|
);
|
|
488
506
|
}
|
|
489
507
|
}
|
|
@@ -528,18 +546,10 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
528
546
|
*/
|
|
529
547
|
public addAccessControlSelectors(
|
|
530
548
|
options: Options,
|
|
531
|
-
selectors: FormattedSelector[]
|
|
549
|
+
selectors: FormattedSelector[],
|
|
532
550
|
) {
|
|
533
551
|
const user = this.currentUser;
|
|
534
552
|
|
|
535
|
-
if (
|
|
536
|
-
user != null &&
|
|
537
|
-
(user.abilities?.indexOf('system/admin') ?? -1) !== -1
|
|
538
|
-
) {
|
|
539
|
-
// The user is a system admin, so they can see everything.
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
553
|
if (options == null) {
|
|
544
554
|
throw new Error('No options in argument.');
|
|
545
555
|
} else if (
|
|
@@ -579,33 +589,23 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
579
589
|
];
|
|
580
590
|
const groupRefs: FormattedSelector['ref'] = [];
|
|
581
591
|
const acRefs: FormattedSelector['ref'] = [];
|
|
582
|
-
|
|
592
|
+
const gid = user.$getGid();
|
|
593
|
+
if (gid != null) {
|
|
583
594
|
// It belongs to the user's primary group.
|
|
584
|
-
groupRefs.push(['group',
|
|
595
|
+
groupRefs.push(['group', gid]);
|
|
585
596
|
// User's primary group is listed in acRead, acWrite, or acFull.
|
|
586
|
-
acRefs.push(['acRead',
|
|
587
|
-
acRefs.push(['acWrite',
|
|
588
|
-
acRefs.push(['acFull',
|
|
589
|
-
}
|
|
590
|
-
for (let curSecondaryGroup of user.groups ?? []) {
|
|
591
|
-
if (curSecondaryGroup != null && curSecondaryGroup.guid != null) {
|
|
592
|
-
// It belongs to the user's secondary group.
|
|
593
|
-
groupRefs.push(['group', curSecondaryGroup.guid]);
|
|
594
|
-
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
595
|
-
acRefs.push(['acRead', curSecondaryGroup.guid]);
|
|
596
|
-
acRefs.push(['acWrite', curSecondaryGroup.guid]);
|
|
597
|
-
acRefs.push(['acFull', curSecondaryGroup.guid]);
|
|
598
|
-
}
|
|
597
|
+
acRefs.push(['acRead', gid]);
|
|
598
|
+
acRefs.push(['acWrite', gid]);
|
|
599
|
+
acRefs.push(['acFull', gid]);
|
|
599
600
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
601
|
+
const gids = user.$getGids();
|
|
602
|
+
for (let curGid of gids ?? []) {
|
|
603
|
+
// It belongs to the user's secondary group.
|
|
604
|
+
groupRefs.push(['group', curGid]);
|
|
605
|
+
// User's secondary group is listed in acRead, acWrite, or acFull.
|
|
606
|
+
acRefs.push(['acRead', curGid]);
|
|
607
|
+
acRefs.push(['acWrite', curGid]);
|
|
608
|
+
acRefs.push(['acFull', curGid]);
|
|
609
609
|
}
|
|
610
610
|
// All the group refs.
|
|
611
611
|
if (groupRefs.length) {
|
|
@@ -671,7 +671,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
671
671
|
* - FULL_ACCESS, the user has read, write, and delete access to the entity,
|
|
672
672
|
* as well as being able to manage its access controls and ownership.
|
|
673
673
|
*
|
|
674
|
-
* These properties
|
|
674
|
+
* These properties default to:
|
|
675
675
|
*
|
|
676
676
|
* - acUser = TilmeldAccessLevels.FULL_ACCESS
|
|
677
677
|
* - acGroup = TilmeldAccessLevels.READ_ACCESS
|
|
@@ -686,9 +686,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
686
686
|
* The following conditions will result in different checks, which determine
|
|
687
687
|
* whether the check passes:
|
|
688
688
|
*
|
|
689
|
-
* - The user has the "system/admin" ability. (Always true.)
|
|
690
689
|
* - It is a user or group. (True for READ_ACCESS or Tilmeld admins.)
|
|
691
|
-
* - The entity has no "user" and no "group". (Always true.)
|
|
692
690
|
* - No user is logged in. (Check other AC.)
|
|
693
691
|
* - The entity is the user. (Always true.)
|
|
694
692
|
* - It is the user's primary group. (True for READ_ACCESS.)
|
|
@@ -706,16 +704,17 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
706
704
|
* @param entity The entity to check.
|
|
707
705
|
* @param type The lowest level of permission to consider a pass.
|
|
708
706
|
* @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
|
|
707
|
+
* @param acProperties The acProperties to use instead of getting them from the entity.
|
|
709
708
|
* @returns Whether the current user has at least `type` permission for the entity.
|
|
710
709
|
*/
|
|
711
710
|
public checkPermissions(
|
|
712
|
-
entity: EntityInterface
|
|
711
|
+
entity: EntityInterface,
|
|
713
712
|
type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
|
|
714
|
-
user?: (User & UserData) | false
|
|
713
|
+
user?: (User & UserData) | false,
|
|
714
|
+
acProperties?: ACProperties,
|
|
715
715
|
) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
return false;
|
|
716
|
+
if (!acProperties) {
|
|
717
|
+
acProperties = entity.$getCurrentAcValues() as ACProperties;
|
|
719
718
|
}
|
|
720
719
|
|
|
721
720
|
let userOrNull: (User & UserData) | null = null;
|
|
@@ -729,30 +728,22 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
729
728
|
userOrEmpty = user;
|
|
730
729
|
}
|
|
731
730
|
|
|
732
|
-
if (userOrEmpty.$gatekeeper('system/admin')) {
|
|
733
|
-
return true;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
731
|
// Users and groups are always readable. Editable by Tilmeld admins.
|
|
737
732
|
if (
|
|
738
733
|
(entity instanceof User || entity instanceof Group) &&
|
|
739
734
|
(type === TilmeldAccessLevels.READ_ACCESS ||
|
|
740
|
-
userOrEmpty
|
|
735
|
+
userOrEmpty.abilities?.includes('tilmeld/admin') ||
|
|
736
|
+
userOrEmpty.abilities?.includes('system/admin'))
|
|
741
737
|
) {
|
|
742
738
|
return true;
|
|
743
739
|
}
|
|
744
740
|
|
|
745
|
-
// Entities with no owners are always editable.
|
|
746
|
-
if (entity.user == null && entity.group == null) {
|
|
747
|
-
return true;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
741
|
// Load access control, since we need it now...
|
|
751
|
-
const acUser =
|
|
752
|
-
const acGroup =
|
|
753
|
-
const acOther =
|
|
742
|
+
const acUser = acProperties.acUser ?? TilmeldAccessLevels.FULL_ACCESS;
|
|
743
|
+
const acGroup = acProperties.acGroup ?? TilmeldAccessLevels.READ_ACCESS;
|
|
744
|
+
const acOther = acProperties.acOther ?? TilmeldAccessLevels.NO_ACCESS;
|
|
754
745
|
|
|
755
|
-
if (userOrNull
|
|
746
|
+
if (userOrNull == null) {
|
|
756
747
|
return acOther >= type;
|
|
757
748
|
}
|
|
758
749
|
|
|
@@ -762,45 +753,41 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
762
753
|
}
|
|
763
754
|
|
|
764
755
|
// Check if the entity is the user's group. Always readable.
|
|
756
|
+
const gid = userOrEmpty.$getGid();
|
|
765
757
|
if (
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
userOrEmpty.group.$is(entity) &&
|
|
758
|
+
gid != null &&
|
|
759
|
+
entity.guid === gid &&
|
|
769
760
|
type === TilmeldAccessLevels.READ_ACCESS
|
|
770
761
|
) {
|
|
771
762
|
return true;
|
|
772
763
|
}
|
|
773
764
|
|
|
774
765
|
// Calculate all the groups the user belongs to.
|
|
775
|
-
let
|
|
776
|
-
|
|
777
|
-
allGroups = allGroups.concat(userOrEmpty.$getDescendantGroupsSync());
|
|
766
|
+
let allGids = gid == null ? [] : [gid];
|
|
767
|
+
allGids = allGids.concat(userOrEmpty.$getGids() ?? []);
|
|
778
768
|
|
|
779
769
|
// Check access ac properties.
|
|
780
770
|
const checks = [
|
|
781
771
|
{
|
|
782
772
|
type: TilmeldAccessLevels.FULL_ACCESS,
|
|
783
|
-
array: [...(
|
|
773
|
+
array: [...(acProperties.acFull ?? [])],
|
|
784
774
|
},
|
|
785
775
|
{
|
|
786
776
|
type: TilmeldAccessLevels.WRITE_ACCESS,
|
|
787
|
-
array: [...(
|
|
777
|
+
array: [...(acProperties.acWrite ?? [])],
|
|
788
778
|
},
|
|
789
779
|
{
|
|
790
780
|
type: TilmeldAccessLevels.READ_ACCESS,
|
|
791
|
-
array: [...(
|
|
781
|
+
array: [...(acProperties.acRead ?? [])],
|
|
792
782
|
},
|
|
793
783
|
];
|
|
794
784
|
for (let curCheck of checks) {
|
|
795
785
|
if (type <= curCheck.type) {
|
|
796
|
-
if (
|
|
786
|
+
if (curCheck.array.includes(userOrEmpty.guid)) {
|
|
797
787
|
return true;
|
|
798
788
|
}
|
|
799
|
-
for (let
|
|
800
|
-
if (
|
|
801
|
-
typeof curGroup.$inArray === 'function' &&
|
|
802
|
-
curGroup.$inArray(curCheck.array)
|
|
803
|
-
) {
|
|
789
|
+
for (let curGid of allGids) {
|
|
790
|
+
if (curCheck.array.includes(curGid)) {
|
|
804
791
|
return true;
|
|
805
792
|
}
|
|
806
793
|
}
|
|
@@ -808,18 +795,10 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
808
795
|
}
|
|
809
796
|
|
|
810
797
|
// Check ownership ac properties.
|
|
811
|
-
if (
|
|
812
|
-
entity.user != null &&
|
|
813
|
-
typeof entity.user.$is === 'function' &&
|
|
814
|
-
entity.user.$is(userOrNull)
|
|
815
|
-
) {
|
|
798
|
+
if (acProperties.user != null && acProperties.user === userOrNull.guid) {
|
|
816
799
|
return acUser >= type;
|
|
817
800
|
}
|
|
818
|
-
if (
|
|
819
|
-
entity.group != null &&
|
|
820
|
-
typeof entity.group.$inArray === 'function' &&
|
|
821
|
-
entity.group.$inArray(allGroups)
|
|
822
|
-
) {
|
|
801
|
+
if (acProperties.group != null && allGids.includes(acProperties.group)) {
|
|
823
802
|
return acGroup >= type;
|
|
824
803
|
}
|
|
825
804
|
return acOther >= type;
|
|
@@ -849,10 +828,10 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
849
828
|
* @param user The user to check permissions for. If null, uses the current user. If false, checks for public access.
|
|
850
829
|
* @returns Whether the current user has at least `type` permission for the UID.
|
|
851
830
|
*/
|
|
852
|
-
public checkClientUIDPermissions(
|
|
831
|
+
public async checkClientUIDPermissions(
|
|
853
832
|
name: string,
|
|
854
833
|
type: TilmeldAccessLevels = TilmeldAccessLevels.READ_ACCESS,
|
|
855
|
-
user?: (User & UserData) | false
|
|
834
|
+
user?: (User & UserData) | false,
|
|
856
835
|
) {
|
|
857
836
|
let userOrEmpty: User & UserData = this.User.factorySync();
|
|
858
837
|
// Calculate the user.
|
|
@@ -862,30 +841,30 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
862
841
|
userOrEmpty = user;
|
|
863
842
|
}
|
|
864
843
|
|
|
865
|
-
if (userOrEmpty
|
|
844
|
+
if (userOrEmpty.abilities?.includes('system/admin')) {
|
|
866
845
|
return true;
|
|
867
846
|
}
|
|
868
847
|
|
|
869
848
|
if (type === TilmeldAccessLevels.FULL_ACCESS) {
|
|
870
849
|
return (
|
|
871
|
-
this.config.clientSetabledUIDs.
|
|
872
|
-
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
850
|
+
this.config.clientSetabledUIDs.includes(name) ||
|
|
851
|
+
(await userOrEmpty.$gatekeeper(`uid/set/${name}`))
|
|
873
852
|
);
|
|
874
853
|
} else if (type === TilmeldAccessLevels.WRITE_ACCESS) {
|
|
875
854
|
return (
|
|
876
|
-
this.config.clientEnabledUIDs.
|
|
877
|
-
this.config.clientSetabledUIDs.
|
|
878
|
-
userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
|
|
879
|
-
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
855
|
+
this.config.clientEnabledUIDs.includes(name) ||
|
|
856
|
+
this.config.clientSetabledUIDs.includes(name) ||
|
|
857
|
+
(await userOrEmpty.$gatekeeper(`uid/new/${name}`)) ||
|
|
858
|
+
(await userOrEmpty.$gatekeeper(`uid/set/${name}`))
|
|
880
859
|
);
|
|
881
860
|
} else if (type === TilmeldAccessLevels.READ_ACCESS) {
|
|
882
861
|
return (
|
|
883
|
-
this.config.clientReadableUIDs.
|
|
884
|
-
this.config.clientEnabledUIDs.
|
|
885
|
-
this.config.clientSetabledUIDs.
|
|
886
|
-
userOrEmpty.$gatekeeper(`uid/get/${name}`) ||
|
|
887
|
-
userOrEmpty.$gatekeeper(`uid/new/${name}`) ||
|
|
888
|
-
userOrEmpty.$gatekeeper(`uid/set/${name}`)
|
|
862
|
+
this.config.clientReadableUIDs.includes(name) ||
|
|
863
|
+
this.config.clientEnabledUIDs.includes(name) ||
|
|
864
|
+
this.config.clientSetabledUIDs.includes(name) ||
|
|
865
|
+
(await userOrEmpty.$gatekeeper(`uid/get/${name}`)) ||
|
|
866
|
+
(await userOrEmpty.$gatekeeper(`uid/new/${name}`)) ||
|
|
867
|
+
(await userOrEmpty.$gatekeeper(`uid/set/${name}`))
|
|
889
868
|
);
|
|
890
869
|
}
|
|
891
870
|
|
|
@@ -897,20 +876,13 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
897
876
|
*
|
|
898
877
|
* @param user The user.
|
|
899
878
|
*/
|
|
900
|
-
public fillSession(user: User & UserData) {
|
|
901
|
-
// Read groups right now, since gatekeeper needs them, so
|
|
902
|
-
// $udpateDataProtection will fail to read them (since it runs gatekeeper).
|
|
903
|
-
const _group = user.group;
|
|
904
|
-
const _groups = user.groups;
|
|
879
|
+
public async fillSession(user: User & UserData) {
|
|
905
880
|
this.currentUser = user;
|
|
881
|
+
this.gatekeeperCache = await user.$getGatekeeperCache();
|
|
882
|
+
|
|
906
883
|
// Now update the data protection on the user and all the groups.
|
|
907
884
|
this.currentUser.$updateDataProtection();
|
|
908
|
-
|
|
909
|
-
this.currentUser.group.$updateDataProtection();
|
|
910
|
-
}
|
|
911
|
-
for (let group of this.currentUser.groups ?? []) {
|
|
912
|
-
group.$updateDataProtection();
|
|
913
|
-
}
|
|
885
|
+
this.currentUser.$updateGroupDataProtection();
|
|
914
886
|
}
|
|
915
887
|
|
|
916
888
|
/**
|
|
@@ -919,14 +891,10 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
919
891
|
public clearSession() {
|
|
920
892
|
const user = this.currentUser;
|
|
921
893
|
this.currentUser = null;
|
|
894
|
+
this.gatekeeperCache = null;
|
|
922
895
|
if (user) {
|
|
923
896
|
user.$updateDataProtection();
|
|
924
|
-
|
|
925
|
-
user.group.$updateDataProtection();
|
|
926
|
-
}
|
|
927
|
-
for (let group of user.groups ?? []) {
|
|
928
|
-
group.$updateDataProtection();
|
|
929
|
-
}
|
|
897
|
+
user.$updateGroupDataProtection();
|
|
930
898
|
}
|
|
931
899
|
}
|
|
932
900
|
|
|
@@ -937,6 +905,7 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
937
905
|
* @returns The user on success, null on failure.
|
|
938
906
|
*/
|
|
939
907
|
public async extractToken(token: string) {
|
|
908
|
+
this.nymph.config.debugInfo('tilmeld', 'Extracting and validating token.');
|
|
940
909
|
const extract = this.config.jwtExtract(this.config, token);
|
|
941
910
|
if (extract == null) {
|
|
942
911
|
return null;
|
|
@@ -948,9 +917,9 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
948
917
|
{
|
|
949
918
|
type: '&',
|
|
950
919
|
guid: guid,
|
|
951
|
-
}
|
|
920
|
+
},
|
|
952
921
|
);
|
|
953
|
-
if (!user || !user.guid
|
|
922
|
+
if (!user || !user.guid) {
|
|
954
923
|
return null;
|
|
955
924
|
}
|
|
956
925
|
|
|
@@ -958,40 +927,73 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
958
927
|
}
|
|
959
928
|
|
|
960
929
|
/**
|
|
961
|
-
* Check for
|
|
930
|
+
* Check for TILMELDAUTH and TILMELDSWITCH tokens, and, if set, authenticate
|
|
931
|
+
* from it/them.
|
|
932
|
+
*
|
|
933
|
+
* You can also call this function after setting `response.locals.user` to the
|
|
934
|
+
* user you want to authenticate. You *should* check for `user.enabled` before
|
|
935
|
+
* setting this variable, unless you explicitly want to log in as a disabled
|
|
936
|
+
* user. (The user must be an instance of the User class for this Tilmeld
|
|
937
|
+
* instance.)
|
|
938
|
+
*
|
|
939
|
+
* This function will set `response.locals.user` to the logged in user on
|
|
940
|
+
* successful authentication.
|
|
962
941
|
*
|
|
963
942
|
* @param skipXsrfToken Skip the XSRF token check.
|
|
943
|
+
* @param skipRenew Skip the token renewal step, even if the token is close to expiration.
|
|
964
944
|
* @returns True if a user was authenticated, false on any failure.
|
|
965
945
|
*/
|
|
966
|
-
public authenticate(skipXsrfToken = false) {
|
|
946
|
+
public async authenticate(skipXsrfToken = false, skipRenew = false) {
|
|
967
947
|
if (this.request == null) {
|
|
968
948
|
return false;
|
|
969
949
|
}
|
|
970
950
|
|
|
951
|
+
if (
|
|
952
|
+
this.response &&
|
|
953
|
+
this.response.locals.user &&
|
|
954
|
+
this.response.locals.user instanceof this.User
|
|
955
|
+
) {
|
|
956
|
+
// The user has already been authenticated through some other means.
|
|
957
|
+
const user = this.response.locals.user;
|
|
958
|
+
await this.fillSession(user);
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
|
|
971
962
|
const cookies = this.request.cookies ?? {};
|
|
972
963
|
|
|
973
964
|
// If a client does't support cookies, they can use the X-TILMELDAUTH header
|
|
974
965
|
// to provide the auth token.
|
|
975
966
|
let fromAuthHeader = false;
|
|
976
967
|
let authToken: string;
|
|
968
|
+
let switchToken: string | null;
|
|
977
969
|
if (this.request.header('x-tilmeldauth') != null) {
|
|
978
970
|
fromAuthHeader = true;
|
|
979
971
|
authToken = this.request.header('x-tilmeldauth') as string;
|
|
972
|
+
switchToken =
|
|
973
|
+
(this.request.header('x-tilmeldswitch') as string) ||
|
|
974
|
+
'TILMELDSWITCH' in cookies
|
|
975
|
+
? cookies.TILMELDSWITCH
|
|
976
|
+
: null;
|
|
980
977
|
} else if ('TILMELDAUTH' in cookies) {
|
|
981
978
|
fromAuthHeader = false;
|
|
982
979
|
authToken = cookies.TILMELDAUTH;
|
|
980
|
+
switchToken = 'TILMELDSWITCH' in cookies ? cookies.TILMELDSWITCH : null;
|
|
983
981
|
} else {
|
|
984
982
|
return false;
|
|
985
983
|
}
|
|
986
984
|
|
|
987
|
-
let
|
|
985
|
+
let authExtract: { guid: string; issued: Date; expire: Date } | null;
|
|
986
|
+
let switchExtract: { guid: string; expire: Date } | null;
|
|
988
987
|
if (
|
|
989
988
|
skipXsrfToken ||
|
|
990
989
|
this.request.originalUrl.startsWith(this.config.setupPath)
|
|
991
990
|
) {
|
|
992
991
|
// The request is for the setup app, or we were told to skip the XSRF
|
|
993
992
|
// check, so don't check for the XSRF token.
|
|
994
|
-
|
|
993
|
+
authExtract = this.config.jwtExtract(this.config, authToken);
|
|
994
|
+
switchExtract = switchToken
|
|
995
|
+
? this.config.jwtExtract(this.config, switchToken)
|
|
996
|
+
: null;
|
|
995
997
|
} else {
|
|
996
998
|
// The request is for something else, so check for a valid XSRF token,
|
|
997
999
|
// unless the auth token is provided by a header (instead of a cookie).
|
|
@@ -1000,28 +1002,65 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
1000
1002
|
return false;
|
|
1001
1003
|
}
|
|
1002
1004
|
|
|
1003
|
-
|
|
1005
|
+
authExtract = this.config.jwtExtract(this.config, authToken, xsrfToken);
|
|
1006
|
+
switchExtract = switchToken
|
|
1007
|
+
? this.config.jwtExtract(this.config, switchToken)
|
|
1008
|
+
: null;
|
|
1004
1009
|
}
|
|
1005
1010
|
|
|
1006
|
-
if (
|
|
1007
|
-
this.logout();
|
|
1011
|
+
if (authExtract == null) {
|
|
1012
|
+
await this.logout();
|
|
1008
1013
|
return false;
|
|
1009
1014
|
}
|
|
1010
|
-
const { guid, expire } =
|
|
1015
|
+
const { guid, issued, expire } = authExtract;
|
|
1016
|
+
const { guid: switchGuid, expire: switchExpire } = switchExtract || {
|
|
1017
|
+
guid: null,
|
|
1018
|
+
expire: null,
|
|
1019
|
+
};
|
|
1011
1020
|
|
|
1012
|
-
const user = this.User.
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1021
|
+
const user = await this.User.factory(guid);
|
|
1022
|
+
if (
|
|
1023
|
+
user.guid == null ||
|
|
1024
|
+
!user.enabled ||
|
|
1025
|
+
expire.getTime() <= Date.now() ||
|
|
1026
|
+
issued.getTime() > Date.now() ||
|
|
1027
|
+
(user.revokeTokenDate != null && issued.getTime() <= user.revokeTokenDate)
|
|
1028
|
+
) {
|
|
1029
|
+
await this.logout();
|
|
1015
1030
|
return false;
|
|
1016
1031
|
}
|
|
1017
1032
|
|
|
1018
|
-
if (
|
|
1033
|
+
if (switchGuid && switchExpire && switchExpire.getTime() > Date.now()) {
|
|
1034
|
+
// Load the switch user.
|
|
1035
|
+
const switchUser = await this.User.factory(switchGuid);
|
|
1036
|
+
if (switchUser.guid == null) {
|
|
1037
|
+
await this.logoutSwitch();
|
|
1038
|
+
} else {
|
|
1039
|
+
await this.fillSession(switchUser);
|
|
1040
|
+
|
|
1041
|
+
if (this.response) {
|
|
1042
|
+
this.response.locals.user = switchUser;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
return true;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (
|
|
1050
|
+
!skipRenew &&
|
|
1051
|
+
expire.getTime() < Date.now() + this.config.jwtRenew * 1000
|
|
1052
|
+
) {
|
|
1019
1053
|
// If the user is less than renew time from needing a new token, give them
|
|
1020
1054
|
// a new one.
|
|
1021
|
-
this.login(user, fromAuthHeader);
|
|
1055
|
+
await this.login(user, fromAuthHeader);
|
|
1022
1056
|
} else {
|
|
1023
|
-
this.fillSession(user);
|
|
1057
|
+
await this.fillSession(user);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (this.response) {
|
|
1061
|
+
this.response.locals.user = user;
|
|
1024
1062
|
}
|
|
1063
|
+
|
|
1025
1064
|
return true;
|
|
1026
1065
|
}
|
|
1027
1066
|
|
|
@@ -1029,27 +1068,87 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
1029
1068
|
* Logs the given user into the system.
|
|
1030
1069
|
*
|
|
1031
1070
|
* @param user The user.
|
|
1032
|
-
* @param sendAuthHeader
|
|
1071
|
+
* @param sendAuthHeader Send the auth token as a custom header.
|
|
1072
|
+
* @param sendCookie Send the auth token as a cookie.
|
|
1033
1073
|
* @returns True on success, false on failure.
|
|
1034
1074
|
*/
|
|
1035
|
-
public login(
|
|
1075
|
+
public async login(
|
|
1076
|
+
user: User & UserData,
|
|
1077
|
+
sendAuthHeader: boolean,
|
|
1078
|
+
sendCookie = true,
|
|
1079
|
+
) {
|
|
1036
1080
|
if (user.guid != null && user.enabled) {
|
|
1037
|
-
if (this.response
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1081
|
+
if (this.response) {
|
|
1082
|
+
if (!this.response.headersSent) {
|
|
1083
|
+
const token = this.config.jwtBuilder(this.config, user);
|
|
1084
|
+
|
|
1085
|
+
if (sendCookie) {
|
|
1086
|
+
const appUrl = new URL(this.config.appUrl);
|
|
1087
|
+
this.response.cookie('TILMELDAUTH', token, {
|
|
1088
|
+
domain: this.config.cookieDomain,
|
|
1089
|
+
path: this.config.cookiePath,
|
|
1090
|
+
maxAge: this.config.jwtExpire * 1000,
|
|
1091
|
+
secure: appUrl.protocol === 'https:',
|
|
1092
|
+
httpOnly: false, // Allow JS access (for CSRF protection).
|
|
1093
|
+
sameSite: appUrl.protocol === 'https:' ? 'lax' : 'strict',
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (sendAuthHeader) {
|
|
1098
|
+
this.response.set('X-TILMELDAUTH', token);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
this.response.locals.user = user;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
await this.fillSession(user);
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Adds a switch auth token for the given user.
|
|
1113
|
+
*
|
|
1114
|
+
* This effectively logs the current user in to the system as the given user.
|
|
1115
|
+
*
|
|
1116
|
+
* @param user The user.
|
|
1117
|
+
* @param sendAuthHeader Send the auth token as a custom header.
|
|
1118
|
+
* @param sendCookie Send the auth token as a cookie.
|
|
1119
|
+
* @returns True on success, false on failure.
|
|
1120
|
+
*/
|
|
1121
|
+
public async loginSwitch(
|
|
1122
|
+
user: User & UserData,
|
|
1123
|
+
sendAuthHeader: boolean,
|
|
1124
|
+
sendCookie = true,
|
|
1125
|
+
) {
|
|
1126
|
+
if (user.guid != null) {
|
|
1127
|
+
if (this.response) {
|
|
1128
|
+
if (!this.response.headersSent) {
|
|
1129
|
+
const token = this.config.jwtBuilder(this.config, user, true);
|
|
1130
|
+
|
|
1131
|
+
if (sendCookie) {
|
|
1132
|
+
const appUrl = new URL(this.config.appUrl);
|
|
1133
|
+
this.response.cookie('TILMELDSWITCH', token, {
|
|
1134
|
+
domain: this.config.cookieDomain,
|
|
1135
|
+
path: this.config.cookiePath,
|
|
1136
|
+
maxAge: this.config.jwtSwitchExpire * 1000,
|
|
1137
|
+
secure: appUrl.protocol === 'https:',
|
|
1138
|
+
httpOnly: false, // Allow JS access (for PubSub auth token).
|
|
1139
|
+
sameSite: appUrl.protocol === 'https:' ? 'lax' : 'strict',
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (sendAuthHeader) {
|
|
1144
|
+
this.response.set('X-TILMELDSWITCH', token);
|
|
1145
|
+
}
|
|
1050
1146
|
}
|
|
1147
|
+
|
|
1148
|
+
this.response.locals.user = user;
|
|
1051
1149
|
}
|
|
1052
|
-
|
|
1150
|
+
|
|
1151
|
+
await this.fillSession(user);
|
|
1053
1152
|
return true;
|
|
1054
1153
|
}
|
|
1055
1154
|
return false;
|
|
@@ -1057,15 +1156,50 @@ export default class Tilmeld implements TilmeldInterface {
|
|
|
1057
1156
|
|
|
1058
1157
|
/**
|
|
1059
1158
|
* Logs the current user out of the system.
|
|
1159
|
+
*
|
|
1160
|
+
* @param clearCookie Clear the auth cookie. (Also send a header.)
|
|
1060
1161
|
*/
|
|
1061
|
-
public logout() {
|
|
1162
|
+
public async logout(clearCookie = true) {
|
|
1163
|
+
if (this.request && !this.alreadyLoggedOutSwitch) {
|
|
1164
|
+
const cookies = this.request.cookies ?? {};
|
|
1165
|
+
|
|
1166
|
+
if ('TILMELDSWITCH' in cookies) {
|
|
1167
|
+
this.alreadyLoggedOutSwitch = true;
|
|
1168
|
+
return await this.logoutSwitch(clearCookie);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1062
1172
|
this.clearSession();
|
|
1063
|
-
if (this.response
|
|
1064
|
-
this.response.
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1173
|
+
if (this.response) {
|
|
1174
|
+
if (clearCookie && !this.response.headersSent) {
|
|
1175
|
+
this.response.clearCookie('TILMELDAUTH', {
|
|
1176
|
+
domain: this.config.cookieDomain,
|
|
1177
|
+
path: this.config.cookiePath,
|
|
1178
|
+
});
|
|
1179
|
+
this.response.set('X-TILMELDAUTH', '');
|
|
1180
|
+
}
|
|
1181
|
+
this.response.locals.user = null;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Clears the switch user out of the system.
|
|
1187
|
+
*
|
|
1188
|
+
* @param clearCookie Clear the auth cookie. (Also send a header.)
|
|
1189
|
+
*/
|
|
1190
|
+
public async logoutSwitch(clearCookie = true) {
|
|
1191
|
+
this.clearSession();
|
|
1192
|
+
if (this.response) {
|
|
1193
|
+
if (clearCookie && !this.response.headersSent) {
|
|
1194
|
+
this.response.clearCookie('TILMELDSWITCH', {
|
|
1195
|
+
domain: this.config.cookieDomain,
|
|
1196
|
+
path: this.config.cookiePath,
|
|
1197
|
+
});
|
|
1198
|
+
this.response.set('X-TILMELDSWITCH', '');
|
|
1199
|
+
}
|
|
1200
|
+
this.response.locals.user = null;
|
|
1201
|
+
|
|
1202
|
+
await this.authenticate(false, true);
|
|
1069
1203
|
}
|
|
1070
1204
|
}
|
|
1071
1205
|
|