nodebb-plugin-session-sharing 4.7.1 → 5.1.0

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/library.js CHANGED
@@ -1,26 +1,24 @@
1
1
  'use strict';
2
2
 
3
- var meta = require.main.require('./src/meta');
4
- var user = require.main.require('./src/user');
5
- var groups = require.main.require('./src/groups');
6
- var SocketPlugins = require.main.require('./src/socket.io/plugins');
3
+ const winston = module.parent.require('winston');
4
+ const nconf = module.parent.require('nconf');
7
5
 
8
- var winston = module.parent.require('winston');
9
- var nconf = module.parent.require('nconf');
6
+ const _ = require('lodash');
10
7
 
11
- var _ = require('lodash');
12
- var async = require('async');
8
+ const meta = require.main.require('./src/meta');
9
+ const user = require.main.require('./src/user');
10
+ const groups = require.main.require('./src/groups');
11
+ const SocketPlugins = require.main.require('./src/socket.io/plugins');
12
+ const db = require.main.require('./src/database');
13
+ const plugins = require.main.require('./src/plugins');
13
14
 
14
- var db = require.main.require('./src/database');
15
- var plugins = require.main.require('./src/plugins');
15
+ const jwt = require('jsonwebtoken');
16
16
 
17
- var jwt = require('jsonwebtoken');
18
-
19
- var controllers = require('./lib/controllers');
20
- var nbbAuthController = require.main.require('./src/controllers/authentication');
17
+ const controllers = require('./lib/controllers');
18
+ const nbbAuthController = require.main.require('./src/controllers/authentication');
21
19
 
22
20
  /* all the user profile fields that can be passed to user.updateProfile */
23
- var profileFields = [
21
+ const profileFields = [
24
22
  'username',
25
23
  'email',
26
24
  'fullname',
@@ -31,7 +29,7 @@ var profileFields = [
31
29
  'signature',
32
30
  'aboutme',
33
31
  ];
34
- var payloadKeys = profileFields.concat([
32
+ const payloadKeys = profileFields.concat([
35
33
  'id', // the uniq identifier of that account
36
34
  'firstName', // for backwards compatibillity
37
35
  'lastName', // dto.
@@ -39,7 +37,7 @@ var payloadKeys = profileFields.concat([
39
37
  'groups',
40
38
  ]);
41
39
 
42
- var plugin = {
40
+ const plugin = {
43
41
  ready: false,
44
42
  settings: {
45
43
  name: 'appId',
@@ -50,6 +48,7 @@ var plugin = {
50
48
  adminRevalidate: 'off',
51
49
  noRegistration: 'off',
52
50
  payloadParent: undefined,
51
+ allowBannedUsers: false,
53
52
  },
54
53
  };
55
54
 
@@ -57,7 +56,7 @@ payloadKeys.forEach(function (key) {
57
56
  plugin.settings['payload:' + key] = key;
58
57
  });
59
58
 
60
- plugin.init = function (params, callback) {
59
+ plugin.init = async (params) => {
61
60
  var router = params.router;
62
61
  var hostMiddleware = params.middleware;
63
62
 
@@ -71,10 +70,10 @@ plugin.init = function (params, callback) {
71
70
  router.get('/debug/session', plugin.generate);
72
71
  }
73
72
 
74
- plugin.reloadSettings(callback);
73
+ await plugin.reloadSettings();
75
74
  };
76
75
 
77
- plugin.appendConfig = function (config, callback) {
76
+ plugin.appendConfig = async (config) => {
78
77
  config.sessionSharing = {
79
78
  logoutRedirect: plugin.settings.logoutRedirect,
80
79
  loginOverride: plugin.settings.loginOverride,
@@ -83,45 +82,30 @@ plugin.appendConfig = function (config, callback) {
83
82
  hostWhitelist: plugin.settings.hostWhitelist,
84
83
  };
85
84
 
86
- callback(null, config);
85
+ return config;
87
86
  };
88
87
 
89
88
  /* Websocket Listeners */
90
89
 
91
90
  SocketPlugins.sessionSharing = {};
92
91
 
93
- SocketPlugins.sessionSharing.showUserIds = function (socket, data, callback) {
92
+ SocketPlugins.sessionSharing.showUserIds = async (socket, data) => {
94
93
  // Retrieve the hash and find matches
95
- var uids = data.uids;
96
- var payload = [];
97
-
98
- payload.length = uids.length;
99
-
100
- if (uids.length) {
101
- async.map(uids, function (uid, next) {
102
- db.getSortedSetRangeByScore(plugin.settings.name + ':uid', 0, -1, uid, uid, next);
103
- }, function (err, remoteIds) {
104
- if (err) {
105
- return callback(err);
106
- }
94
+ const { uids } = data;
107
95
 
108
- remoteIds.forEach(function (remoteId, idx) {
109
- payload[idx] = remoteId;
110
- });
111
-
112
- callback(null, payload);
113
- });
114
- } else {
115
- callback(new Error('no-uids-supplied'));
96
+ if (!uids.length) {
97
+ throw new Error('no-uids-supplied');
116
98
  }
99
+
100
+ return Promise.all(uids.map(async uid => db.getSortedSetRangeByScore(plugin.settings.name + ':uid', 0, -1, uid, uid)));
117
101
  };
118
102
 
119
- SocketPlugins.sessionSharing.findUserByRemoteId = function (socket, data, callback) {
120
- if (data.remoteId) {
121
- plugin.getUser(data.remoteId, callback);
122
- } else {
123
- callback(new Error('no-remote-id-supplied'));
103
+ SocketPlugins.sessionSharing.findUserByRemoteId = async (socket, data) => {
104
+ if (!data.remoteId) {
105
+ throw new Error('no-remote-id-supplied');
124
106
  }
107
+
108
+ return plugin.getUser(data.remoteId);
125
109
  };
126
110
 
127
111
  /* End Websocket Listeners */
@@ -129,32 +113,28 @@ SocketPlugins.sessionSharing.findUserByRemoteId = function (socket, data, callba
129
113
  /*
130
114
  * Given a remoteId, show user data
131
115
  */
132
- plugin.getUser = function (remoteId, callback) {
133
- async.waterfall([
134
- async.apply(db.sortedSetScore, plugin.settings.name + ':uid', remoteId),
135
- function (uid, next) {
136
- if (uid) {
137
- user.getUserFields(uid, ['username', 'userslug', 'picture'], next);
138
- } else {
139
- setImmediate(next);
140
- }
141
- },
142
- ], callback);
116
+ plugin.getUser = async (remoteId) => {
117
+ const uid = await db.sortedSetScore(plugin.settings.name + ':uid', remoteId);
118
+
119
+ if (!uid) {
120
+ return;
121
+ }
122
+
123
+ return user.getUserFields(uid, ['username', 'userslug', 'picture']);
143
124
  };
144
125
 
145
- plugin.process = function (token, callback) {
146
- async.waterfall([
147
- async.apply(jwt.verify, token, plugin.settings.secret),
148
- async.apply(plugin.normalizePayload),
149
- async.apply(plugin.findOrCreateUser),
150
- async.apply(plugin.updateUserProfile),
151
- async.apply(plugin.updateUserGroups),
152
- async.apply(plugin.verifyUser, token),
153
- ], callback);
126
+ plugin.process = async (token) => {
127
+ const payload = await jwt.verify(token, plugin.settings.secret);
128
+ const userData = await plugin.normalizePayload(payload);
129
+ const [uid, isNewUser] = await plugin.findOrCreateUser(userData);
130
+ await plugin.updateUserProfile(uid, userData, isNewUser);
131
+ await plugin.updateUserGroups(uid, userData);
132
+ await plugin.verifyUser(token, uid, isNewUser);
133
+ return uid;
154
134
  };
155
135
 
156
- plugin.normalizePayload = function (payload, callback) {
157
- var userData = {};
136
+ plugin.normalizePayload = async (payload) => {
137
+ const userData = {};
158
138
 
159
139
  if (plugin.settings.payloadParent) {
160
140
  payload = payload[plugin.settings.payloadParent];
@@ -162,11 +142,11 @@ plugin.normalizePayload = function (payload, callback) {
162
142
 
163
143
  if (typeof payload !== 'object') {
164
144
  winston.warn('[session-sharing] the payload is not an object', payload);
165
- return callback(new Error('payload-invalid'));
145
+ throw new Error('payload-invalid');
166
146
  }
167
147
 
168
148
  payloadKeys.forEach(function (key) {
169
- var propName = plugin.settings['payload:' + key];
149
+ const propName = plugin.settings['payload:' + key];
170
150
  if (payload[propName]) {
171
151
  userData[key] = payload[propName];
172
152
  }
@@ -174,7 +154,7 @@ plugin.normalizePayload = function (payload, callback) {
174
154
 
175
155
  if (!userData.id) {
176
156
  winston.warn('[session-sharing] No user id was given in payload');
177
- return callback(new Error('payload-invalid'));
157
+ throw new Error('payload-invalid');
178
158
  }
179
159
 
180
160
  userData.fullname = (userData.fullname || [userData.firstName, userData.lastName].join(' ')).trim();
@@ -188,7 +168,7 @@ plugin.normalizePayload = function (payload, callback) {
188
168
 
189
169
  if (!userData.username) {
190
170
  winston.warn('[session-sharing] No valid username could be determined');
191
- return callback(new Error('payload-invalid'));
171
+ throw new Error('payload-invalid');
192
172
  }
193
173
 
194
174
  if (userData.hasOwnProperty('groups') && !Array.isArray(userData.groups)) {
@@ -197,218 +177,185 @@ plugin.normalizePayload = function (payload, callback) {
197
177
  }
198
178
 
199
179
  winston.verbose('[session-sharing] Payload verified');
200
- plugins.fireHook('filter:sessionSharing.normalizePayload', {
180
+ const data = await plugins.hooks.fire('filter:sessionSharing.normalizePayload', {
201
181
  payload: payload,
202
182
  userData: userData,
203
- }, function (err, data) {
204
- callback(err, data.userData);
205
183
  });
184
+
185
+ return data.userData;
206
186
  };
207
187
 
208
- plugin.verifyUser = function (token, uid, isNewUser, callback) {
209
- plugins.fireHook('static:sessionSharing.verifyUser', {
188
+ plugin.verifyUser = async (token, uid, isNewUser) => {
189
+ await plugins.hooks.fire('static:sessionSharing.verifyUser', {
210
190
  uid: uid,
211
191
  isNewUser: isNewUser,
212
192
  token: token,
213
- }, function (err) {
214
- if (err) {
215
- return callback(err);
216
- }
217
- // Check ban state of user, reject if banned
218
- user.bans.isBanned(uid, function (err, banned) {
219
- callback(err || banned ? new Error('banned') : null, uid);
220
- });
221
193
  });
194
+
195
+ // Check ban state of user
196
+ const isBanned = await user.bans.isBanned(uid);
197
+
198
+ // Reject if banned and settings dont allow banned users to login
199
+ if (isBanned && !plugin.settings.allowBannedUsers) {
200
+ throw new Error('banned');
201
+ }
222
202
  };
223
203
 
224
- plugin.findOrCreateUser = function (userData, callback) {
225
- var queries = {};
204
+ plugin.findOrCreateUser = async (userData) => {
205
+ const { id } = userData;
206
+ let isNewUser = false;
207
+ let userId = null;
208
+ let queries = [db.sortedSetScore(plugin.settings.name + ':uid', userData.id)];
209
+
226
210
  if (userData.email && userData.email.length) {
227
- queries.mergeUid = async.apply(db.sortedSetScore, 'email:uid', userData.email);
228
- }
229
- queries.uid = async.apply(db.sortedSetScore, plugin.settings.name + ':uid', userData.id);
230
-
231
- async.parallel(queries, function (err, checks) {
232
- if (err) { return callback(err); }
233
-
234
- async.waterfall([
235
- /* check if found something to work with */
236
- function (next) {
237
- if (checks.uid && !isNaN(parseInt(checks.uid, 10))) {
238
- var uid = parseInt(checks.uid, 10);
239
- /* check if the user with the given id actually exists */
240
- return user.exists(uid, function (err, exists) {
241
- /* ignore errors, but assume the user doesn't exist */
242
- if (err) {
243
- winston.warn('[session-sharing] Error while testing user existance', err);
244
- return next(null, null);
245
- }
246
- if (exists) {
247
- return next(null, uid);
248
- }
249
- /* reference is outdated, user got deleted */
250
- db.sortedSetRemove(plugin.settings.name + ':uid', userData.id, function (err) {
251
- next(err, null);
252
- });
253
- });
254
- }
255
- if (checks.mergeUid && !isNaN(parseInt(checks.mergeUid, 10))) {
256
- winston.info('[session-sharing] Found user via their email, associating this id (' + userData.id + ') with their NodeBB account');
257
- return db.sortedSetAdd(plugin.settings.name + ':uid', checks.mergeUid, userData.id, function (err) {
258
- next(err, parseInt(checks.mergeUid, 10));
259
- });
260
- }
261
- setImmediate(next, null, null);
262
- },
263
- /* create the user from payload if necessary */
264
- function (uid, next) {
265
- winston.debug('createUser?', !uid);
266
- if (!uid) {
267
- if (plugin.settings.noRegistration === 'on') {
268
- return next(new Error('no-match'));
269
- }
270
- return plugin.createUser(userData, function (err, uid) {
271
- next(err, uid, userData, true);
272
- });
273
- }
274
- setImmediate(next, null, uid, userData, false);
275
- },
276
- ], callback);
277
- });
211
+ queries = [...queries, db.sortedSetScore('email:uid', userData.email)];
212
+ }
213
+
214
+ let [uid, mergeUid] = await Promise.all(queries);
215
+ uid = parseInt(uid, 10);
216
+ mergeUid = parseInt(mergeUid, 10);
217
+
218
+ /* check if found something to work with */
219
+ if (uid && !isNaN(uid)) {
220
+ try {
221
+ /* check if the user with the given id actually exists */
222
+ const exists = await user.exists(uid);
223
+
224
+ if (exists) {
225
+ userId = uid;
226
+ } else {
227
+ /* reference is outdated, user got deleted */
228
+ await db.sortedSetRemove(plugin.settings.name + ':uid', id);
229
+ }
230
+ } catch (error) {
231
+ /* ignore errors, but assume the user doesn't exist */
232
+ winston.warn('[session-sharing] Error while testing user existance', error);
233
+ }
234
+ }
235
+
236
+ if (!userId && mergeUid && !isNaN(mergeUid)) {
237
+ winston.info('[session-sharing] Found user via their email, associating this id (' + id + ') with their NodeBB account');
238
+ await db.sortedSetAdd(plugin.settings.name + ':uid', mergeUid, id);
239
+ userId = mergeUid;
240
+ }
241
+
242
+ /* create the user from payload if necessary */
243
+ winston.debug('createUser?', !userId);
244
+ if (!userId) {
245
+ if (plugin.settings.noRegistration === 'on') {
246
+ throw new Error('no-match');
247
+ }
248
+
249
+ userId = await plugin.createUser(userData);
250
+ isNewUser = true;
251
+ }
252
+
253
+ return [userId, isNewUser];
278
254
  };
279
255
 
280
- plugin.updateUserProfile = function (uid, userData, isNewUser, callback) {
256
+ plugin.updateUserProfile = async (uid, userData, isNewUser) => {
281
257
  winston.debug('consider updateProfile?', isNewUser || plugin.settings.updateProfile === 'on');
258
+ let userObj = {};
282
259
 
283
260
  /* even update the profile on a new account, since some fields are not initialized by NodeBB */
284
261
  if (!isNewUser && plugin.settings.updateProfile !== 'on') {
285
- return setImmediate(callback, null, uid, userData, isNewUser);
262
+ return;
286
263
  }
287
264
 
288
- async.waterfall([
289
- function (next) {
290
- user.getUserFields(uid, profileFields, next);
291
- },
292
- function (existingFields, next) {
293
- var obj = {};
265
+ const existingFields = await user.getUserFields(uid, profileFields);
266
+ const obj = profileFields.reduce((result, field) => {
267
+ if (typeof userData[field] !== 'undefined' && existingFields[field] !== userData[field]) {
268
+ result[field] = userData[field];
269
+ }
294
270
 
295
- profileFields.forEach(function (field) {
296
- if (typeof userData[field] !== 'undefined' && existingFields[field] !== userData[field]) {
297
- obj[field] = userData[field];
298
- }
299
- });
271
+ return result;
272
+ }, {});
300
273
 
301
- if (Object.keys(obj).length) {
302
- winston.debug('[session-sharing] Updating profile fields:', obj);
303
- obj.uid = uid;
304
- return user.updateProfile(uid, obj, function (err, userObj) {
305
- if (err) {
306
- winston.warn('[session-sharing] Unable to update profile information for uid: ' + uid + '(' + err.message + ')');
307
- }
308
-
309
- // If it errors out, not that big of a deal, continue anyway.
310
- next(null, userObj || existingFields);
311
- });
312
- }
313
- setImmediate(next, null, {});
314
- },
315
- function (userObj, next) {
316
- if (userData.picture) {
317
- return db.setObjectField('user:' + uid, 'picture', userData.picture, next);
274
+ if (Object.keys(obj).length) {
275
+ winston.debug('[session-sharing] Updating profile fields:', obj);
276
+ obj.uid = uid;
277
+ try {
278
+ userObj = await user.updateProfile(uid, obj);
279
+
280
+ // If it errors out, not that big of a deal, continue anyway.
281
+ if (!userObj) {
282
+ userObj = existingFields;
318
283
  }
284
+ } catch (error) {
285
+ winston.warn('[session-sharing] Unable to update profile information for uid: ' + uid + '(' + error.message + ')');
286
+ }
287
+ }
319
288
 
320
- setImmediate(next, null);
321
- },
322
- ], function (err) {
323
- return callback(err, uid, userData, isNewUser);
324
- });
289
+ if (userData.picture) {
290
+ await db.setObjectField('user:' + uid, 'picture', userData.picture);
291
+ }
325
292
  };
326
293
 
327
- plugin.updateUserGroups = function (uid, userData, isNewUser, callback) {
294
+ plugin.updateUserGroups = async (uid, userData) => {
328
295
  if (!userData.groups || !Array.isArray(userData.groups)) {
329
- return setImmediate(callback, null, uid, isNewUser);
296
+ return;
330
297
  }
331
298
 
332
- async.waterfall([
333
- // Retrieve user groups
334
- async.apply(groups.getUserGroupsFromSet, 'groups:createtime', [uid]),
335
- function (groups, next) {
336
- // Normalize user group data to just group names
337
- groups = groups[0].map(function (groupObj) {
338
- return groupObj.name;
339
- });
340
-
341
- // Build join and leave arrays
342
- var join = userData.groups.filter(function (name) {
343
- return !groups.includes(name);
344
- });
345
-
346
- if (plugin.settings.syncGroupList === 'on') {
347
- join = join.filter(group => plugin.settings.syncGroups.includes(group));
348
- }
349
-
350
- var leave = groups.filter(function (name) {
351
- // `registered-users` is always a joined group
352
- if (name === 'registered-users') {
353
- return false;
354
- }
299
+ // Retrieve user groups
300
+ let [userGroups] = await groups.getUserGroupsFromSet('groups:createtime', [uid]);
301
+ // Normalize user group data to just group names
302
+ userGroups = userGroups.map(groupObj => groupObj.name);
355
303
 
356
- return !userData.groups.includes(name);
357
- });
304
+ // Build join and leave arrays
305
+ let join = userData.groups.filter(name => !userGroups.includes(name));
306
+ if (plugin.settings.syncGroupList === 'on') {
307
+ join = join.filter(group => plugin.settings.syncGroups.includes(group));
308
+ }
358
309
 
359
- if (plugin.settings.syncGroupList === 'on') {
360
- leave = leave.filter(group => plugin.settings.syncGroups.includes(group));
361
- }
310
+ let leave = userGroups.filter((name) => {
311
+ // `registered-users` is always a joined group
312
+ if (name === 'registered-users') {
313
+ return false;
314
+ }
362
315
 
363
- executeJoinLeave(uid, join, leave, next);
364
- },
365
- ], function (err) {
366
- return callback(err, uid, isNewUser);
316
+ return !userData.groups.includes(name);
367
317
  });
318
+ if (plugin.settings.syncGroupList === 'on') {
319
+ leave = leave.filter(group => plugin.settings.syncGroups.includes(group));
320
+ }
321
+
322
+ await executeJoinLeave(uid, join, leave);
368
323
  };
369
324
 
370
- function executeJoinLeave(uid, join, leave, callback) {
371
- async.parallel([
372
- function (next) {
325
+ async function executeJoinLeave(uid, join, leave) {
326
+ await Promise.all([
327
+ (async () => {
373
328
  if (plugin.settings.syncGroupJoin !== 'on') {
374
- return setImmediate(next);
329
+ return;
375
330
  }
376
331
 
377
- async.each(join, function (name, next) {
378
- groups.join(name, uid, next);
379
- }, next);
380
- },
381
- function (next) {
332
+ await Promise.all(join.map(name => groups.join(name, uid)));
333
+ })(),
334
+ (async () => {
382
335
  if (plugin.settings.syncGroupLeave !== 'on') {
383
- return setImmediate(next);
336
+ return;
384
337
  }
385
338
 
386
- async.each(leave, function (name, next) {
387
- groups.leave(name, uid, next);
388
- }, next);
389
- },
390
- ], callback);
339
+ await Promise.all(leave.map(name => groups.leave(name, uid)));
340
+ })(),
341
+ ]);
391
342
  }
392
343
 
393
- plugin.createUser = function (userData, callback) {
344
+ plugin.createUser = async (userData) => {
394
345
  winston.verbose('[session-sharing] No user found, creating a new user for this login');
395
346
 
396
- user.create(_.pick(userData, profileFields), function (err, uid) {
397
- if (err) { return callback(err); }
398
-
399
- db.sortedSetAdd(plugin.settings.name + ':uid', uid, userData.id, function (err) {
400
- callback(err, uid);
401
- });
402
- });
347
+ const uid = await user.create(_.pick(userData, profileFields));
348
+ await db.sortedSetAdd(plugin.settings.name + ':uid', uid, userData.id);
349
+ return uid;
403
350
  };
404
351
 
405
- plugin.addMiddleware = async function (req, res, next) {
352
+ plugin.addMiddleware = async function ({ req, res }) {
406
353
  const { hostWhitelist, guestRedirect, editOverride, loginOverride, registerOverride } = await meta.settings.get('session-sharing');
407
354
 
408
355
  if (hostWhitelist) {
409
- var hosts = hostWhitelist.split(',') || [hostWhitelist];
410
- var whitelisted = false;
411
- for (var host of hosts) {
356
+ const hosts = hostWhitelist.split(',') || [hostWhitelist];
357
+ let whitelisted = false;
358
+ for (const host of hosts) {
412
359
  if (req.headers.host.includes(host)) {
413
360
  whitelisted = true;
414
361
  break;
@@ -416,35 +363,33 @@ plugin.addMiddleware = async function (req, res, next) {
416
363
  }
417
364
 
418
365
  if (!whitelisted) {
419
- return next();
366
+ return;
420
367
  }
421
368
  }
422
369
 
423
- function handleGuest(req, res, next) {
370
+ function handleGuest(req, res) {
424
371
  if (guestRedirect && !req.originalUrl.startsWith(nconf.get('relative_path') + '/login?local=1')) {
425
372
  // If a guest redirect is specified, follow it
426
373
  res.redirect(guestRedirect.replace('%1', encodeURIComponent(req.protocol + '://' + req.get('host') + req.originalUrl)));
427
374
  } else if (res.locals.fullRefresh === true) {
428
375
  res.redirect(nconf.get('relative_path') + req.url);
429
- } else {
430
- next();
431
376
  }
432
377
  }
433
378
 
434
379
  // Only respond to page loads by guests, not api or asset calls
435
- var hasSession = req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && parseInt(req.user.uid, 10) > 0;
436
- var hasLoginLock = req.session.hasOwnProperty('loginLock');
380
+ const hasSession = req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && parseInt(req.user.uid, 10) > 0;
381
+ const hasLoginLock = req.session.hasOwnProperty('loginLock');
437
382
 
438
383
  if (
439
384
  !plugin.ready || // plugin not ready
440
385
  (plugin.settings.behaviour === 'trust' && hasSession) || // user logged in + "trust" behaviour
441
- (plugin.settings.behaviour === 'revalidate' && hasLoginLock) ||
386
+ ((plugin.settings.behaviour === 'revalidate' || plugin.settings.behaviour === 'update') && hasLoginLock) ||
442
387
  req.originalUrl.startsWith(nconf.get('relative_path') + '/api') // api routes
443
388
  ) {
444
- // Let requests through under "revalidate" behaviour only if they're logging in for the first time
445
- delete req.session.loginLock; // remove login lock for "revalidate" logins
389
+ // Let requests through under "update" or "revalidate" behaviour only if they're logging in for the first time
390
+ delete req.session.loginLock; // remove login lock for "update" or "revalidate" logins
446
391
 
447
- return next();
392
+ return;
448
393
  }
449
394
 
450
395
  if (editOverride && hasSession && req.originalUrl.match(/\/user\/.*\/edit$/)) {
@@ -458,89 +403,78 @@ plugin.addMiddleware = async function (req, res, next) {
458
403
  }
459
404
 
460
405
  // Hook into ip blacklist functionality in core
461
- meta.blacklist.test(req.ip, function (err) {
462
- if (err) {
463
- if (hasSession) {
464
- req.logout();
465
- res.locals.fullRefresh = true;
406
+ try {
407
+ await meta.blacklist.test(req.ip);
408
+ } catch (error) {
409
+ if (hasSession) {
410
+ req.logout();
411
+ res.locals.fullRefresh = true;
412
+ }
413
+
414
+ await plugin.cleanup({ res: res });
415
+ return handleGuest.call(null, req, res);
416
+ }
417
+
418
+ if (Object.keys(req.cookies).length && req.cookies.hasOwnProperty(plugin.settings.cookieName) && req.cookies[plugin.settings.cookieName].length) {
419
+ try {
420
+ const uid = await plugin.process(req.cookies[plugin.settings.cookieName]);
421
+ winston.verbose('[session-sharing] Processing login for uid ' + uid + ', path ' + req.originalUrl);
422
+ req.uid = uid;
423
+
424
+ if (plugin.settings.behaviour === 'revalidate') {
425
+ res.locals.reroll = false; // disable session rerolling in core
466
426
  }
467
427
 
468
- plugin.cleanup({ res: res });
469
- return handleGuest.call(null, req, res, next);
470
- }
471
- if (Object.keys(req.cookies).length && req.cookies.hasOwnProperty(plugin.settings.cookieName) && req.cookies[plugin.settings.cookieName].length) {
472
- return plugin.process(req.cookies[plugin.settings.cookieName], function (err, uid) {
473
- if (err) {
474
- var handleAsGuest = false;
475
-
476
- switch (err.message) {
477
- case 'banned':
478
- winston.info('[session-sharing] uid ' + uid + ' is banned, not logging them in');
479
- req.session.sessionSharing = {
480
- banned: true,
481
- uid: uid,
482
- };
483
- break;
484
- case 'payload-invalid':
485
- winston.warn('[session-sharing] The passed-in payload was invalid and could not be processed');
486
- break;
487
- case 'no-match':
488
- winston.info('[session-sharing] Payload valid, but local account not found. Assuming guest.');
489
- handleAsGuest = true;
490
- break;
491
- default:
492
- winston.warn('[session-sharing] Error encountered while parsing token: ' + err.message);
493
- break;
494
- }
495
-
496
- return plugins.fireHook('filter:sessionSharing.error', {
497
- error: err,
498
- uid: uid,
499
- res: res,
500
- settings: plugin.settings,
501
- handleAsGuest: handleAsGuest,
502
- }, function (err, data) {
503
- if (data.handleAsGuest) {
504
- return handleGuest.call(err, req, res, next);
505
- }
506
-
507
- next(err);
508
- });
509
- }
510
-
511
- winston.verbose('[session-sharing] Processing login for uid ' + uid + ', path ' + req.originalUrl);
512
- req.uid = uid;
513
-
514
- if (plugin.settings.behaviour === 'revalidate') {
515
- res.locals.reroll = false; // disable session rerolling in core
516
- }
517
-
518
- nbbAuthController.doLogin(req, uid, function () {
519
- req.session.loginLock = true;
520
- const url = req.session.returnTo || req.originalUrl.replace(nconf.get('relative_path'), '');
521
- delete req.session.returnTo;
522
- res.redirect(nconf.get('relative_path') + url);
523
- });
524
- });
525
- } else if (hasSession) {
526
- // Has login session but no cookie, can assume "revalidate" behaviour
527
- user.isAdministrator(req.user.uid, function (err, isAdmin) {
528
- if (plugin.settings.adminRevalidate === 'on' || !isAdmin) {
529
- req.logout();
530
- res.locals.fullRefresh = true;
531
- handleGuest(req, res, next);
532
- } else {
533
- // Admins can bypass
534
- return next(err);
535
- }
428
+ await nbbAuthController.doLogin(req, uid);
429
+
430
+ req.session.loginLock = true;
431
+ const url = req.session.returnTo || req.originalUrl.replace(nconf.get('relative_path'), '');
432
+ delete req.session.returnTo;
433
+ res.redirect(nconf.get('relative_path') + url);
434
+ } catch (error) {
435
+ let handleAsGuest = false;
436
+
437
+ switch (error.message) {
438
+ case 'payload-invalid':
439
+ winston.warn('[session-sharing] The passed-in payload was invalid and could not be processed');
440
+ break;
441
+ case 'no-match':
442
+ winston.info('[session-sharing] Payload valid, but local account not found. Assuming guest.');
443
+ handleAsGuest = true;
444
+ break;
445
+ default:
446
+ winston.warn('[session-sharing] Error encountered while parsing token: ' + error.message);
447
+ break;
448
+ }
449
+
450
+ const data = await plugins.hooks.fire('filter:sessionSharing.error', {
451
+ error,
452
+ res: res,
453
+ settings: plugin.settings,
454
+ handleAsGuest: handleAsGuest,
536
455
  });
537
- } else {
538
- handleGuest.call(null, req, res, next);
456
+
457
+ if (data.handleAsGuest) {
458
+ return handleGuest.call(error, req, res);
459
+ }
460
+
461
+ throw error;
539
462
  }
540
- });
463
+ } else if (hasSession) {
464
+ // Has login session but no cookie, can assume "revalidate" behaviour
465
+ const isAdmin = await user.isAdministrator(req.user.uid);
466
+
467
+ if (plugin.settings.behaviour !== 'update' && (plugin.settings.adminRevalidate === 'on' || !isAdmin)) {
468
+ req.logout();
469
+ res.locals.fullRefresh = true;
470
+ return handleGuest(req, res);
471
+ }
472
+ } else {
473
+ return handleGuest.call(null, req, res);
474
+ }
541
475
  };
542
476
 
543
- plugin.cleanup = function (data, callback) {
477
+ plugin.cleanup = async (data) => {
544
478
  if (plugin.settings.cookieDomain) {
545
479
  winston.verbose('[session-sharing] Clearing cookie');
546
480
  data.res.clearCookie(plugin.settings.cookieName, {
@@ -550,15 +484,21 @@ plugin.cleanup = function (data, callback) {
550
484
  });
551
485
  }
552
486
 
553
- if (typeof callback === 'function') {
554
- callback();
555
- } else {
556
- return true;
557
- }
487
+ data.res.clearCookie('nbb_token', {
488
+ domain: plugin.settings.cookieDomain,
489
+ expires: new Date(),
490
+ path: '/',
491
+ });
492
+
493
+ return true;
558
494
  };
559
495
 
560
496
  plugin.generate = function (req, res) {
561
- var payload = {};
497
+ if (!plugin.ready) {
498
+ return res.sendStatus(404);
499
+ }
500
+
501
+ let payload = {};
562
502
  payload[plugin.settings['payload:id']] = 1;
563
503
  payload[plugin.settings['payload:username']] = 'testUser';
564
504
  payload[plugin.settings['payload:email']] = 'testUser@example.org';
@@ -573,13 +513,13 @@ plugin.generate = function (req, res) {
573
513
  payload[plugin.settings['payload:groups']] = ['test-group'];
574
514
 
575
515
  if (plugin.settings.payloadParent || plugin.settings['payload:parent']) {
576
- var parentKey = plugin.settings.payloadParent || plugin.settings['payload:parent'];
577
- var newPayload = {};
516
+ const parentKey = plugin.settings.payloadParent || plugin.settings['payload:parent'];
517
+ const newPayload = {};
578
518
  newPayload[parentKey] = payload;
579
519
  payload = newPayload;
580
520
  }
581
521
 
582
- var token = jwt.sign(payload, plugin.settings.secret);
522
+ const token = jwt.sign(payload, plugin.settings.secret);
583
523
  res.cookie(plugin.settings.cookieName, token, {
584
524
  maxAge: 1000 * 60 * 60 * 24 * 21,
585
525
  httpOnly: true,
@@ -589,74 +529,77 @@ plugin.generate = function (req, res) {
589
529
  res.sendStatus(200);
590
530
  };
591
531
 
592
- plugin.addAdminNavigation = function (header, callback) {
532
+ plugin.addAdminNavigation = async (header) => {
593
533
  header.plugins.push({
594
534
  route: '/plugins/session-sharing',
595
535
  icon: 'fa-user-secret',
596
536
  name: 'Session Sharing',
597
537
  });
598
538
 
599
- callback(null, header);
539
+ return header;
600
540
  };
601
541
 
602
- plugin.reloadSettings = function (callback) {
603
- // If callback is not a function then it is the action hook from core
604
- if (typeof callback !== 'function' && callback.plugin !== 'session-sharing') {
542
+ plugin.reloadSettings = async (data) => {
543
+ // If data argument is truthy, then it is the action hook from core
544
+ if (data && data.plugin !== 'session-sharing') {
605
545
  return;
606
546
  }
607
547
 
608
- meta.settings.get('session-sharing', function (err, settings) {
609
- if (err) {
610
- return callback(err);
611
- }
612
-
613
- if (!settings.hasOwnProperty('secret') || !settings.secret.length) {
614
- winston.error('[session-sharing] JWT Secret not found, session sharing disabled.');
615
- return callback();
616
- }
617
-
618
- // If "payload:parent" is found, but payloadParent is not, update the latter and delete the former
619
- if (!settings.payloadParent && settings['payload:parent']) {
620
- winston.verbose('[session-sharing] Migrating payload:parent to payloadParent');
621
- settings.payloadParent = settings['payload:parent'];
622
- db.setObjectField('settings:session-sharing', 'payloadParent', settings.payloadParent);
623
- db.deleteObjectField('settings:session-sharing', 'payload:parent');
624
- }
548
+ const settings = await meta.settings.get('session-sharing');
549
+ if (!settings.hasOwnProperty('secret') || !settings.secret.length) {
550
+ winston.error('[session-sharing] JWT Secret not found, session sharing disabled.');
551
+ return;
552
+ }
625
553
 
626
- if (!settings['payload:username'] && !settings['payload:firstName'] && !settings['payload:lastName'] && !settings['payload:fullname']) {
627
- settings['payload:username'] = 'username';
628
- }
554
+ // If "payload:parent" is found, but payloadParent is not, update the latter and delete the former
555
+ if (!settings.payloadParent && settings['payload:parent']) {
556
+ winston.verbose('[session-sharing] Migrating payload:parent to payloadParent');
557
+ settings.payloadParent = settings['payload:parent'];
558
+ await db.setObjectField('settings:session-sharing', 'payloadParent', settings.payloadParent);
559
+ await db.deleteObjectField('settings:session-sharing', 'payload:parent');
560
+ }
629
561
 
630
- winston.info('[session-sharing] Settings OK');
631
- plugin.settings = _.defaults(_.pickBy(settings, Boolean), plugin.settings);
632
- plugin.ready = true;
562
+ if (!settings['payload:username'] && !settings['payload:firstName'] && !settings['payload:lastName'] && !settings['payload:fullname']) {
563
+ settings['payload:username'] = 'username';
564
+ }
633
565
 
634
- if (typeof callback === 'function') {
635
- callback();
636
- }
637
- });
566
+ winston.info('[session-sharing] Settings OK');
567
+ plugin.settings = _.defaults(_.pickBy(settings, Boolean), plugin.settings);
568
+ plugin.ready = true;
638
569
  };
639
570
 
640
- plugin.appendTemplate = (data, callback) => {
641
- if (data.req.session.sessionSharing && data.req.session.sessionSharing.banned) {
642
- user.getLatestBanInfo(data.req.session.sessionSharing.uid, (err, info) => {
643
- if (err) {
644
- return callback(err);
645
- }
571
+ plugin.appendTemplate = async (data) => {
572
+ if (!data.req.session.sessionSharing || !data.req.session.sessionSharing.banned) {
573
+ return data;
574
+ }
646
575
 
647
- data.templateData.sessionSharingBan = {
648
- ban: info,
649
- banned: true,
650
- };
576
+ const info = await user.getLatestBanInfo(data.req.session.sessionSharing.uid);
651
577
 
652
- delete data.req.session.sessionSharing;
653
- callback(null, data);
654
- });
578
+ data.templateData.sessionSharingBan = {
579
+ ban: info,
580
+ banned: true,
581
+ };
655
582
 
656
- return;
583
+ delete data.req.session.sessionSharing;
584
+ return data;
585
+ };
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
657
590
  }
658
591
 
659
- setImmediate(callback, null, data);
592
+ const res = req.res;
593
+ const userData = await user.getUserFields(data.uid, ['uid', 'username', 'picture', 'reputation', 'postcount', 'banned']);
594
+ const token = jwt.sign(userData, plugin.settings.secret);
595
+
596
+ res.cookie('nbb_token', token, {
597
+ maxAge: meta.getSessionTTLSeconds() * 1000,
598
+ httpOnly: !req.secure,
599
+ domain: plugin.settings.cookieDomain,
600
+ });
601
+
602
+ winston.info(`[plugins/session-sharing] Saving reverse cookie for uid ${userData.uid}, session: ${req.session.id}`);
660
603
  };
661
604
 
662
605
  module.exports = plugin;