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