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