nodebb-plugin-session-sharing 5.0.0 → 5.1.3
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/commitlint.config.js +24 -1
- package/library.js +70 -61
- package/package.json +5 -3
- package/plugin.json +3 -2
- package/static/templates/admin/plugins/session-sharing.tpl +20 -1
- package/test/index.js +183 -0
package/commitlint.config.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
module.exports = {
|
|
3
|
+
module.exports = {
|
|
4
|
+
extends: ['@commitlint/config-angular'],
|
|
5
|
+
rules: {
|
|
6
|
+
'header-max-length': [1, 'always', 72],
|
|
7
|
+
'type-enum': [
|
|
8
|
+
2,
|
|
9
|
+
'always',
|
|
10
|
+
[
|
|
11
|
+
'breaking',
|
|
12
|
+
'build',
|
|
13
|
+
'chore',
|
|
14
|
+
'ci',
|
|
15
|
+
'docs',
|
|
16
|
+
'feat',
|
|
17
|
+
'fix',
|
|
18
|
+
'perf',
|
|
19
|
+
'refactor',
|
|
20
|
+
'revert',
|
|
21
|
+
'style',
|
|
22
|
+
'test',
|
|
23
|
+
],
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
};
|
package/library.js
CHANGED
|
@@ -97,25 +97,14 @@ SocketPlugins.sessionSharing.showUserIds = async (socket, data) => {
|
|
|
97
97
|
throw new Error('no-uids-supplied');
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
return Promise.all(uids.map(async
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
SocketPlugins.sessionSharing.showUserIds = async (socket, data) => {
|
|
104
|
-
// Retrieve the hash and find matches
|
|
105
|
-
const { uids } = data;
|
|
106
|
-
|
|
107
|
-
if (!uids.length) {
|
|
108
|
-
throw new Error('no-uids-supplied');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return Promise.all(uids.map(async (uid) => db.getSortedSetRangeByScore(plugin.settings.name + ':uid', 0, -1, uid, uid)));
|
|
100
|
+
return Promise.all(uids.map(async uid => db.getSortedSetRangeByScore(plugin.settings.name + ':uid', 0, -1, uid, uid)));
|
|
112
101
|
};
|
|
113
102
|
|
|
114
103
|
SocketPlugins.sessionSharing.findUserByRemoteId = async (socket, data) => {
|
|
115
104
|
if (!data.remoteId) {
|
|
116
105
|
throw new Error('no-remote-id-supplied');
|
|
117
106
|
}
|
|
118
|
-
|
|
107
|
+
|
|
119
108
|
return plugin.getUser(data.remoteId);
|
|
120
109
|
};
|
|
121
110
|
|
|
@@ -202,7 +191,7 @@ plugin.verifyUser = async (token, uid, isNewUser) => {
|
|
|
202
191
|
isNewUser: isNewUser,
|
|
203
192
|
token: token,
|
|
204
193
|
});
|
|
205
|
-
|
|
194
|
+
|
|
206
195
|
// Check ban state of user
|
|
207
196
|
const isBanned = await user.bans.isBanned(uid);
|
|
208
197
|
|
|
@@ -217,15 +206,15 @@ plugin.findOrCreateUser = async (userData) => {
|
|
|
217
206
|
let isNewUser = false;
|
|
218
207
|
let userId = null;
|
|
219
208
|
let queries = [db.sortedSetScore(plugin.settings.name + ':uid', userData.id)];
|
|
220
|
-
|
|
209
|
+
|
|
221
210
|
if (userData.email && userData.email.length) {
|
|
222
211
|
queries = [...queries, db.sortedSetScore('email:uid', userData.email)];
|
|
223
212
|
}
|
|
224
|
-
|
|
213
|
+
|
|
225
214
|
let [uid, mergeUid] = await Promise.all(queries);
|
|
226
215
|
uid = parseInt(uid, 10);
|
|
227
216
|
mergeUid = parseInt(mergeUid, 10);
|
|
228
|
-
|
|
217
|
+
|
|
229
218
|
/* check if found something to work with */
|
|
230
219
|
if (uid && !isNaN(uid)) {
|
|
231
220
|
try {
|
|
@@ -243,13 +232,13 @@ plugin.findOrCreateUser = async (userData) => {
|
|
|
243
232
|
winston.warn('[session-sharing] Error while testing user existance', error);
|
|
244
233
|
}
|
|
245
234
|
}
|
|
246
|
-
|
|
235
|
+
|
|
247
236
|
if (!userId && mergeUid && !isNaN(mergeUid)) {
|
|
248
237
|
winston.info('[session-sharing] Found user via their email, associating this id (' + id + ') with their NodeBB account');
|
|
249
238
|
await db.sortedSetAdd(plugin.settings.name + ':uid', mergeUid, id);
|
|
250
239
|
userId = mergeUid;
|
|
251
240
|
}
|
|
252
|
-
|
|
241
|
+
|
|
253
242
|
/* create the user from payload if necessary */
|
|
254
243
|
winston.debug('createUser?', !userId);
|
|
255
244
|
if (!userId) {
|
|
@@ -281,7 +270,7 @@ plugin.updateUserProfile = async (uid, userData, isNewUser) => {
|
|
|
281
270
|
|
|
282
271
|
return result;
|
|
283
272
|
}, {});
|
|
284
|
-
|
|
273
|
+
|
|
285
274
|
if (Object.keys(obj).length) {
|
|
286
275
|
winston.debug('[session-sharing] Updating profile fields:', obj);
|
|
287
276
|
obj.uid = uid;
|
|
@@ -296,13 +285,13 @@ plugin.updateUserProfile = async (uid, userData, isNewUser) => {
|
|
|
296
285
|
winston.warn('[session-sharing] Unable to update profile information for uid: ' + uid + '(' + error.message + ')');
|
|
297
286
|
}
|
|
298
287
|
}
|
|
299
|
-
|
|
288
|
+
|
|
300
289
|
if (userData.picture) {
|
|
301
290
|
await db.setObjectField('user:' + uid, 'picture', userData.picture);
|
|
302
291
|
}
|
|
303
292
|
};
|
|
304
293
|
|
|
305
|
-
plugin.updateUserGroups = async (uid, userData
|
|
294
|
+
plugin.updateUserGroups = async (uid, userData) => {
|
|
306
295
|
if (!userData.groups || !Array.isArray(userData.groups)) {
|
|
307
296
|
return;
|
|
308
297
|
}
|
|
@@ -310,14 +299,14 @@ plugin.updateUserGroups = async (uid, userData, isNewUser) => {
|
|
|
310
299
|
// Retrieve user groups
|
|
311
300
|
let [userGroups] = await groups.getUserGroupsFromSet('groups:createtime', [uid]);
|
|
312
301
|
// Normalize user group data to just group names
|
|
313
|
-
userGroups = userGroups.map(
|
|
314
|
-
|
|
302
|
+
userGroups = userGroups.map(groupObj => groupObj.name);
|
|
303
|
+
|
|
315
304
|
// Build join and leave arrays
|
|
316
|
-
let join = userData.groups.filter(
|
|
305
|
+
let join = userData.groups.filter(name => !userGroups.includes(name));
|
|
317
306
|
if (plugin.settings.syncGroupList === 'on') {
|
|
318
|
-
join = join.filter(
|
|
307
|
+
join = join.filter(group => plugin.settings.syncGroups.includes(group));
|
|
319
308
|
}
|
|
320
|
-
|
|
309
|
+
|
|
321
310
|
let leave = userGroups.filter((name) => {
|
|
322
311
|
// `registered-users` is always a joined group
|
|
323
312
|
if (name === 'registered-users') {
|
|
@@ -327,7 +316,7 @@ plugin.updateUserGroups = async (uid, userData, isNewUser) => {
|
|
|
327
316
|
return !userData.groups.includes(name);
|
|
328
317
|
});
|
|
329
318
|
if (plugin.settings.syncGroupList === 'on') {
|
|
330
|
-
leave = leave.filter(
|
|
319
|
+
leave = leave.filter(group => plugin.settings.syncGroups.includes(group));
|
|
331
320
|
}
|
|
332
321
|
|
|
333
322
|
await executeJoinLeave(uid, join, leave);
|
|
@@ -340,27 +329,27 @@ async function executeJoinLeave(uid, join, leave) {
|
|
|
340
329
|
return;
|
|
341
330
|
}
|
|
342
331
|
|
|
343
|
-
await Promise.all(join.map(
|
|
332
|
+
await Promise.all(join.map(name => groups.join(name, uid)));
|
|
344
333
|
})(),
|
|
345
334
|
(async () => {
|
|
346
335
|
if (plugin.settings.syncGroupLeave !== 'on') {
|
|
347
336
|
return;
|
|
348
337
|
}
|
|
349
338
|
|
|
350
|
-
await Promise.all(leave.map(
|
|
339
|
+
await Promise.all(leave.map(name => groups.leave(name, uid)));
|
|
351
340
|
})(),
|
|
352
341
|
]);
|
|
353
342
|
}
|
|
354
343
|
|
|
355
344
|
plugin.createUser = async (userData) => {
|
|
356
345
|
winston.verbose('[session-sharing] No user found, creating a new user for this login');
|
|
357
|
-
|
|
346
|
+
|
|
358
347
|
const uid = await user.create(_.pick(userData, profileFields));
|
|
359
348
|
await db.sortedSetAdd(plugin.settings.name + ':uid', uid, userData.id);
|
|
360
349
|
return uid;
|
|
361
350
|
};
|
|
362
351
|
|
|
363
|
-
plugin.addMiddleware = async function (req, res
|
|
352
|
+
plugin.addMiddleware = async function ({ req, res }) {
|
|
364
353
|
const { hostWhitelist, guestRedirect, editOverride, loginOverride, registerOverride } = await meta.settings.get('session-sharing');
|
|
365
354
|
|
|
366
355
|
if (hostWhitelist) {
|
|
@@ -374,18 +363,16 @@ plugin.addMiddleware = async function (req, res, next) {
|
|
|
374
363
|
}
|
|
375
364
|
|
|
376
365
|
if (!whitelisted) {
|
|
377
|
-
return
|
|
366
|
+
return;
|
|
378
367
|
}
|
|
379
368
|
}
|
|
380
369
|
|
|
381
|
-
function handleGuest(req, res
|
|
370
|
+
function handleGuest(req, res) {
|
|
382
371
|
if (guestRedirect && !req.originalUrl.startsWith(nconf.get('relative_path') + '/login?local=1')) {
|
|
383
372
|
// If a guest redirect is specified, follow it
|
|
384
373
|
res.redirect(guestRedirect.replace('%1', encodeURIComponent(req.protocol + '://' + req.get('host') + req.originalUrl)));
|
|
385
374
|
} else if (res.locals.fullRefresh === true) {
|
|
386
375
|
res.redirect(nconf.get('relative_path') + req.url);
|
|
387
|
-
} else {
|
|
388
|
-
next();
|
|
389
376
|
}
|
|
390
377
|
}
|
|
391
378
|
|
|
@@ -402,7 +389,7 @@ plugin.addMiddleware = async function (req, res, next) {
|
|
|
402
389
|
// Let requests through under "update" or "revalidate" behaviour only if they're logging in for the first time
|
|
403
390
|
delete req.session.loginLock; // remove login lock for "update" or "revalidate" logins
|
|
404
391
|
|
|
405
|
-
return
|
|
392
|
+
return;
|
|
406
393
|
}
|
|
407
394
|
|
|
408
395
|
if (editOverride && hasSession && req.originalUrl.match(/\/user\/.*\/edit$/)) {
|
|
@@ -425,28 +412,28 @@ plugin.addMiddleware = async function (req, res, next) {
|
|
|
425
412
|
}
|
|
426
413
|
|
|
427
414
|
await plugin.cleanup({ res: res });
|
|
428
|
-
return handleGuest.call(null, req, res
|
|
415
|
+
return handleGuest.call(null, req, res);
|
|
429
416
|
}
|
|
430
|
-
|
|
417
|
+
|
|
431
418
|
if (Object.keys(req.cookies).length && req.cookies.hasOwnProperty(plugin.settings.cookieName) && req.cookies[plugin.settings.cookieName].length) {
|
|
432
419
|
try {
|
|
433
420
|
const uid = await plugin.process(req.cookies[plugin.settings.cookieName]);
|
|
434
421
|
winston.verbose('[session-sharing] Processing login for uid ' + uid + ', path ' + req.originalUrl);
|
|
435
422
|
req.uid = uid;
|
|
436
|
-
|
|
423
|
+
|
|
437
424
|
if (plugin.settings.behaviour === 'revalidate') {
|
|
438
425
|
res.locals.reroll = false; // disable session rerolling in core
|
|
439
426
|
}
|
|
440
|
-
|
|
427
|
+
|
|
441
428
|
await nbbAuthController.doLogin(req, uid);
|
|
442
|
-
|
|
429
|
+
|
|
443
430
|
req.session.loginLock = true;
|
|
444
431
|
const url = req.session.returnTo || req.originalUrl.replace(nconf.get('relative_path'), '');
|
|
445
432
|
delete req.session.returnTo;
|
|
446
433
|
res.redirect(nconf.get('relative_path') + url);
|
|
447
434
|
} catch (error) {
|
|
448
435
|
let handleAsGuest = false;
|
|
449
|
-
|
|
436
|
+
|
|
450
437
|
switch (error.message) {
|
|
451
438
|
case 'payload-invalid':
|
|
452
439
|
winston.warn('[session-sharing] The passed-in payload was invalid and could not be processed');
|
|
@@ -468,29 +455,22 @@ plugin.addMiddleware = async function (req, res, next) {
|
|
|
468
455
|
});
|
|
469
456
|
|
|
470
457
|
if (data.handleAsGuest) {
|
|
471
|
-
return handleGuest.call(error, req, res
|
|
458
|
+
return handleGuest.call(error, req, res);
|
|
472
459
|
}
|
|
473
460
|
|
|
474
|
-
|
|
461
|
+
throw error;
|
|
475
462
|
}
|
|
476
463
|
} else if (hasSession) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const isAdmin = await user.isAdministrator(req.user.uid);
|
|
480
|
-
|
|
481
|
-
if (plugin.settings.behaviour !== 'update' && (plugin.settings.adminRevalidate === 'on' || !isAdmin)) {
|
|
482
|
-
req.logout();
|
|
483
|
-
res.locals.fullRefresh = true;
|
|
484
|
-
return handleGuest(req, res, next);
|
|
485
|
-
}
|
|
464
|
+
// Has login session but no cookie, can assume "revalidate" behaviour
|
|
465
|
+
const isAdmin = await user.isAdministrator(req.user.uid);
|
|
486
466
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return
|
|
467
|
+
if (plugin.settings.behaviour !== 'update' && (plugin.settings.adminRevalidate === 'on' || !isAdmin)) {
|
|
468
|
+
req.logout();
|
|
469
|
+
res.locals.fullRefresh = true;
|
|
470
|
+
return handleGuest(req, res);
|
|
491
471
|
}
|
|
492
472
|
} else {
|
|
493
|
-
return handleGuest.call(null, req, res
|
|
473
|
+
return handleGuest.call(null, req, res);
|
|
494
474
|
}
|
|
495
475
|
};
|
|
496
476
|
|
|
@@ -503,11 +483,21 @@ plugin.cleanup = async (data) => {
|
|
|
503
483
|
path: '/',
|
|
504
484
|
});
|
|
505
485
|
}
|
|
506
|
-
|
|
486
|
+
|
|
487
|
+
data.res.clearCookie('nbb_token', {
|
|
488
|
+
domain: plugin.settings.cookieDomain,
|
|
489
|
+
expires: new Date(),
|
|
490
|
+
path: '/',
|
|
491
|
+
});
|
|
492
|
+
|
|
507
493
|
return true;
|
|
508
494
|
};
|
|
509
495
|
|
|
510
496
|
plugin.generate = function (req, res) {
|
|
497
|
+
if (!plugin.ready) {
|
|
498
|
+
return res.sendStatus(404);
|
|
499
|
+
}
|
|
500
|
+
|
|
511
501
|
let payload = {};
|
|
512
502
|
payload[plugin.settings['payload:id']] = 1;
|
|
513
503
|
payload[plugin.settings['payload:username']] = 'testUser';
|
|
@@ -579,7 +569,7 @@ plugin.reloadSettings = async (data) => {
|
|
|
579
569
|
};
|
|
580
570
|
|
|
581
571
|
plugin.appendTemplate = async (data) => {
|
|
582
|
-
if (!data.req.session.sessionSharing || !data.req.session.sessionSharing.banned) {
|
|
572
|
+
if (!data.req.session || !data.req.session.sessionSharing || !data.req.session.sessionSharing.banned) {
|
|
583
573
|
return data;
|
|
584
574
|
}
|
|
585
575
|
|
|
@@ -594,4 +584,23 @@ plugin.appendTemplate = async (data) => {
|
|
|
594
584
|
return data;
|
|
595
585
|
};
|
|
596
586
|
|
|
587
|
+
plugin.saveReverseToken = async ({ req, userData: data }) => {
|
|
588
|
+
if (!plugin.ready || plugin.settings.reverseToken !== 'on') {
|
|
589
|
+
return; // no reverse token if secret not set
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const res = req.res;
|
|
593
|
+
const userData = await user.getUserFields(data.uid, ['uid', 'username', 'picture', 'reputation', 'postcount', 'banned']);
|
|
594
|
+
userData.groups = (await groups.getUserGroups([data.uid])).pop();
|
|
595
|
+
const token = jwt.sign(userData, plugin.settings.secret);
|
|
596
|
+
|
|
597
|
+
res.cookie('nbb_token', token, {
|
|
598
|
+
maxAge: meta.getSessionTTLSeconds() * 1000,
|
|
599
|
+
httpOnly: !req.secure,
|
|
600
|
+
domain: plugin.settings.cookieDomain,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
winston.info(`[plugins/session-sharing] Saving reverse cookie for uid ${userData.uid}, session: ${req.session.id}`);
|
|
604
|
+
};
|
|
605
|
+
|
|
597
606
|
module.exports = plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-session-sharing",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.3",
|
|
4
4
|
"description": "Allows login sessions from your app to persist in NodeBB",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@commitlint/cli": "^9.1.2",
|
|
40
40
|
"async": "^2",
|
|
41
41
|
"eslint": "^7.7.0",
|
|
42
|
-
"jsonwebtoken": "
|
|
42
|
+
"jsonwebtoken": "^8.5.1",
|
|
43
43
|
"lint-staged": "^10.0.9",
|
|
44
44
|
"lodash": "^4.17.14"
|
|
45
45
|
},
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
"eslint-config-airbnb-base": "^12.1.0",
|
|
51
51
|
"eslint-plugin-import": "^2.8.0",
|
|
52
52
|
"husky": "^2.4.0",
|
|
53
|
-
"lint-staged": "^10.0.9"
|
|
53
|
+
"lint-staged": "^10.0.9",
|
|
54
|
+
"request": "^2.88.2",
|
|
55
|
+
"request-promise-native": "^1.0.9"
|
|
54
56
|
}
|
|
55
57
|
}
|
package/plugin.json
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
"url": "https://github.com/NodeBB/nodebb-plugin-session-sharing",
|
|
4
4
|
"library": "./library.js",
|
|
5
5
|
"hooks": [
|
|
6
|
-
{ "hook": "
|
|
6
|
+
{ "hook": "response:router.page", "method": "addMiddleware" },
|
|
7
7
|
{ "hook": "static:app.load", "method": "init" },
|
|
8
8
|
{ "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
|
|
9
9
|
{ "hook": "static:user.loggedOut", "method": "cleanup" },
|
|
10
10
|
{ "hook": "filter:config.get", "method": "appendConfig" },
|
|
11
11
|
{ "hook": "filter:middleware.render", "method": "appendTemplate" },
|
|
12
|
-
{ "hook": "action:settings.set", "method": "reloadSettings" }
|
|
12
|
+
{ "hook": "action:settings.set", "method": "reloadSettings" },
|
|
13
|
+
{ "hook": "action:login.continue", "method": "saveReverseToken" }
|
|
13
14
|
],
|
|
14
15
|
"less": [
|
|
15
16
|
"static/style.less"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<span class="mdl-switch__label"><strong>Apply revalidation rules to administrators as well</strong></span>
|
|
56
56
|
</label>
|
|
57
57
|
<p class="help-block">
|
|
58
|
-
Administrators are exempt from the <code>revalidate</code> behaviour because a
|
|
58
|
+
Administrators are exempt from the <code>revalidate</code> behaviour because a
|
|
59
59
|
misconfiguration could lock them out of the admin panel. Enable this option to force
|
|
60
60
|
administrators to also undergo cookie revalidation, and thereby increasing security.
|
|
61
61
|
</p>
|
|
@@ -242,6 +242,25 @@
|
|
|
242
242
|
</div>
|
|
243
243
|
</div>
|
|
244
244
|
</div>
|
|
245
|
+
<div class="row">
|
|
246
|
+
<div class="col-sm-2 col-xs-12 settings-header">Reverse Token</div>
|
|
247
|
+
<div class="col-sm-10 col-xs-12">
|
|
248
|
+
<div class="form-group">
|
|
249
|
+
<div class="checkbox">
|
|
250
|
+
<label for="reverseToken" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
|
251
|
+
<input type="checkbox" class="mdl-switch__input" id="reverseToken" name="reverseToken">
|
|
252
|
+
<span class="mdl-switch__label"><strong>Enabled</strong></span>
|
|
253
|
+
</label>
|
|
254
|
+
</div>
|
|
255
|
+
<p class="help-block">
|
|
256
|
+
If enabled, NodeBB will save a cookie called <code>nbb_token</code> that can be read by other sites on the same domain. It will allow other sites to authenticate a user session based on NodeBB login state.
|
|
257
|
+
</p>
|
|
258
|
+
<p class="help-block">
|
|
259
|
+
Similar to what is accepted by the session-sharing plugin, the reverse token is a signed <a href="//jwt.io">JWT</a> containing the logged in user's username and uid. Guests will not have an `nbb_token`.
|
|
260
|
+
</p>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
245
264
|
</div>
|
|
246
265
|
<div class="col-md-3">
|
|
247
266
|
<div class="panel panel-default">
|
package/test/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* globals describe, it */
|
|
4
|
+
|
|
5
|
+
// MUST BE RUN WITH ENV VAR "TEST_ENV=development"
|
|
6
|
+
// Don't forget to add this plugin to `test_plugins` array in `config.json`
|
|
7
|
+
|
|
8
|
+
const assert = require('assert');
|
|
9
|
+
const url = require('url');
|
|
10
|
+
const util = require('util');
|
|
11
|
+
const request = require('request-promise-native');
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line no-unused-vars
|
|
14
|
+
const db = require.main.require('./test/mocks/databasemock');
|
|
15
|
+
|
|
16
|
+
const nconf = require.main.require('nconf');
|
|
17
|
+
const meta = require.main.require('./src/meta');
|
|
18
|
+
|
|
19
|
+
describe('nodebb-plugin-session-sharing', () => {
|
|
20
|
+
const userJar = request.jar();
|
|
21
|
+
const anonJar = request.jar();
|
|
22
|
+
const { protocol, hostname } = url.parse(nconf.get('url'));
|
|
23
|
+
|
|
24
|
+
describe('debug route', () => {
|
|
25
|
+
it('should 404 when no secret is set', async () => {
|
|
26
|
+
const response = await request(`${nconf.get('url')}/debug/session`, {
|
|
27
|
+
jar: userJar,
|
|
28
|
+
resolveWithFullResponse: true,
|
|
29
|
+
simple: false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.strictEqual(response.statusCode, 404);
|
|
33
|
+
|
|
34
|
+
await meta.settings.setOne('session-sharing', 'secret', 's3cr37c47');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should generate a valid session when called', async () => {
|
|
38
|
+
const body = await request(`${nconf.get('url')}/debug/session`, {
|
|
39
|
+
jar: userJar,
|
|
40
|
+
});
|
|
41
|
+
assert.strictEqual(body, 'OK');
|
|
42
|
+
|
|
43
|
+
const cookies = userJar.getCookies(`${protocol}//${hostname}/`);
|
|
44
|
+
assert(cookies);
|
|
45
|
+
assert(cookies.some(cookie => cookie.key === 'token'));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('token processing middleware', () => {
|
|
50
|
+
it('should automatically log in a user with a valid token', async () => {
|
|
51
|
+
const getSession = util.promisify(db.sessionStore.get.bind(db.sessionStore));
|
|
52
|
+
const response = await request(`${nconf.get('url')}`, {
|
|
53
|
+
resolveWithFullResponse: true,
|
|
54
|
+
jar: userJar,
|
|
55
|
+
});
|
|
56
|
+
assert.strictEqual(response.statusCode, 200);
|
|
57
|
+
|
|
58
|
+
const cookies = userJar.getCookies(`${protocol}//${hostname}/`);
|
|
59
|
+
assert(cookies);
|
|
60
|
+
|
|
61
|
+
const sidCookie = cookies.find(cookie => cookie.key === 'express.sid');
|
|
62
|
+
assert(sidCookie);
|
|
63
|
+
|
|
64
|
+
let sid = sidCookie.value.match(/s%3A([^.]+)/);
|
|
65
|
+
assert(sid && sid[1]);
|
|
66
|
+
sid = sid[1];
|
|
67
|
+
const sessionObj = await getSession(sid);
|
|
68
|
+
|
|
69
|
+
assert(sessionObj && sessionObj.passport && sessionObj.passport.user);
|
|
70
|
+
assert(parseInt(sessionObj.passport.user, 10) > 0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should transparently pass-through as guest without a token', async () => {
|
|
74
|
+
const response = await request(`${nconf.get('url')}`, {
|
|
75
|
+
resolveWithFullResponse: true,
|
|
76
|
+
jar: anonJar,
|
|
77
|
+
});
|
|
78
|
+
assert.strictEqual(response.statusCode, 200);
|
|
79
|
+
|
|
80
|
+
const cookies = anonJar.getCookies(`${protocol}//${hostname}/`);
|
|
81
|
+
assert(cookies.every(cookie => cookie.key !== 'express.sid'));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should redirect a guest to a specified redirection target if configured', async () => {
|
|
85
|
+
await meta.settings.setOne('session-sharing', 'guestRedirect', 'https://example.org');
|
|
86
|
+
const response = await request(`${nconf.get('url')}`, {
|
|
87
|
+
resolveWithFullResponse: true,
|
|
88
|
+
jar: anonJar,
|
|
89
|
+
followRedirect: false,
|
|
90
|
+
simple: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
assert(response.statusCode, 302);
|
|
94
|
+
assert.strictEqual(response.headers.location, 'https://example.org');
|
|
95
|
+
await meta.settings.setOne('session-sharing', 'guestRedirect', '');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should maintain the login if behaviour is "revalidate"', async () => {
|
|
99
|
+
const getSession = util.promisify(db.sessionStore.get.bind(db.sessionStore));
|
|
100
|
+
await meta.settings.setOne('session-sharing', 'behaviour', 'revalidate');
|
|
101
|
+
|
|
102
|
+
await request(`${nconf.get('url')}`, {
|
|
103
|
+
resolveWithFullResponse: true,
|
|
104
|
+
jar: userJar, // now with no token
|
|
105
|
+
followRedirect: false,
|
|
106
|
+
simple: false,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const cookies = userJar.getCookies(`${protocol}//${hostname}/`);
|
|
110
|
+
assert(cookies);
|
|
111
|
+
|
|
112
|
+
const sidCookie = cookies.find(cookie => cookie.key === 'express.sid');
|
|
113
|
+
assert(sidCookie);
|
|
114
|
+
|
|
115
|
+
let sid = sidCookie.value.match(/s%3A([^.]+)/);
|
|
116
|
+
assert(sid && sid[1]);
|
|
117
|
+
sid = sid[1];
|
|
118
|
+
const sessionObj = await getSession(sid);
|
|
119
|
+
assert(sessionObj && sessionObj.passport && sessionObj.passport.user);
|
|
120
|
+
assert(parseInt(sessionObj.passport.user, 10) > 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should log the user out if behaviour is "revalidate" and the token is gone', async () => {
|
|
124
|
+
const getSession = util.promisify(db.sessionStore.get.bind(db.sessionStore));
|
|
125
|
+
|
|
126
|
+
userJar.setCookie('token=', `${protocol}//${hostname}/`); // remove the token
|
|
127
|
+
await request(`${nconf.get('url')}`, {
|
|
128
|
+
resolveWithFullResponse: true,
|
|
129
|
+
jar: userJar, // now with no token
|
|
130
|
+
followRedirect: false,
|
|
131
|
+
simple: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const cookies = userJar.getCookies(`${protocol}//${hostname}/`);
|
|
135
|
+
assert(cookies);
|
|
136
|
+
|
|
137
|
+
const sidCookie = cookies.find(cookie => cookie.key === 'express.sid');
|
|
138
|
+
assert(sidCookie);
|
|
139
|
+
|
|
140
|
+
let sid = sidCookie.value.match(/s%3A([^.]+)/);
|
|
141
|
+
assert(sid && sid[1]);
|
|
142
|
+
sid = sid[1];
|
|
143
|
+
const sessionObj = await getSession(sid);
|
|
144
|
+
|
|
145
|
+
assert(sessionObj && (!sessionObj.passport || !sessionObj.passport.uid));
|
|
146
|
+
|
|
147
|
+
// Restore userJar's session again
|
|
148
|
+
await request(`${nconf.get('url')}/debug/session`, {
|
|
149
|
+
jar: userJar,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('login override', () => {
|
|
155
|
+
it('should redirect a guest to a specified login override if configured', async () => {
|
|
156
|
+
await meta.settings.setOne('session-sharing', 'loginOverride', 'https://example.org/login');
|
|
157
|
+
const response = await request(`${nconf.get('url')}/login`, {
|
|
158
|
+
resolveWithFullResponse: true,
|
|
159
|
+
jar: anonJar,
|
|
160
|
+
followRedirect: false,
|
|
161
|
+
simple: false,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
assert(response.statusCode, 302);
|
|
165
|
+
assert.strictEqual(response.headers.location, 'https://example.org/login');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('register override', () => {
|
|
170
|
+
it('should redirect a guest to a specified register override if configured', async () => {
|
|
171
|
+
await meta.settings.setOne('session-sharing', 'registerOverride', 'https://example.org/register');
|
|
172
|
+
const response = await request(`${nconf.get('url')}/register`, {
|
|
173
|
+
resolveWithFullResponse: true,
|
|
174
|
+
jar: anonJar,
|
|
175
|
+
followRedirect: false,
|
|
176
|
+
simple: false,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
assert(response.statusCode, 302);
|
|
180
|
+
assert.strictEqual(response.headers.location, 'https://example.org/register');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|