From c2c925cacdfa63a4e70faf37c2734162409847af Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 31 Aug 2018 15:34:29 -0400 Subject: [PATCH] closes #6678 --- install/package.json | 4 +-- public/language/en-GB/pages.json | 1 + public/language/en-GB/user.json | 2 ++ public/src/client/account/info.js | 35 ++------------------- public/src/client/account/sessions.js | 44 +++++++++++++++++++++++++++ src/controllers/accounts/helpers.js | 11 +++++++ src/controllers/accounts/sessions.js | 30 ++++++++++++++++++ src/routes/accounts.js | 2 +- 8 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 public/src/client/account/sessions.js diff --git a/install/package.json b/install/package.json index b07bef8fbd..22255a1e68 100644 --- a/install/package.json +++ b/install/package.json @@ -77,9 +77,9 @@ "nodebb-plugin-spam-be-gone": "0.5.4", "nodebb-rewards-essentials": "0.0.11", "nodebb-theme-lavender": "5.0.7", - "nodebb-theme-persona": "9.0.28", + "nodebb-theme-persona": "9.0.29", "nodebb-theme-slick": "1.2.9", - "nodebb-theme-vanilla": "10.1.1", + "nodebb-theme-vanilla": "10.1.2", "nodebb-widget-essentials": "4.0.7", "nodemailer": "^4.6.5", "passport": "^0.4.0", diff --git a/public/language/en-GB/pages.json b/public/language/en-GB/pages.json index f6927e63a2..a9db79d873 100644 --- a/public/language/en-GB/pages.json +++ b/public/language/en-GB/pages.json @@ -60,6 +60,7 @@ "account/best": "Best posts made by %1", "account/blocks": "Blocked users for %1", "account/uploads": "Uploads by %1", + "account/sessions": "Login Sessions", "confirm": "Email Confirmed", diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index b971fc682c..0644beda99 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -169,6 +169,8 @@ "info.moderation-note.success": "Moderation note saved", "info.moderation-note.add": "Add note", + "sessions.description": "This page allows you to view any active sessions on this forum and revoke them if necessary. You can revoke your own session by logging out of your account.", + "consent.title": "Your Rights & Consent", "consent.lead": "This community forum collects and processes your personal information.", "consent.intro": "We use this information strictly to personalise your experience in this community, as well as to associate the posts you make to your user account. During the registration step you were asked to provide a username and email address, you can also optionally provide additional information to complete your user profile on this website.

We retain this information for the life of your user account, and you are able to withdraw consent at any time by deleting your account. At any time you may request a copy of your contribution to this website, via your Rights & Consent page.

If you have any questions or concerns, we encourage you to reach out to this forum's administrative team.", diff --git a/public/src/client/account/info.js b/public/src/client/account/info.js index ee6648aab5..6e57439bff 100644 --- a/public/src/client/account/info.js +++ b/public/src/client/account/info.js @@ -1,13 +1,13 @@ 'use strict'; -define('forum/account/info', ['forum/account/header', 'components'], function (header, components) { +define('forum/account/info', ['forum/account/header', 'components', 'forum/account/sessions'], function (header, components, sessions) { var Info = {}; Info.init = function () { header.init(); handleModerationNote(); - prepareSessionRevoking(); + sessions.prepareSessionRevocation(); }; function handleModerationNote() { @@ -34,36 +34,5 @@ define('forum/account/info', ['forum/account/header', 'components'], function (h }); } - function prepareSessionRevoking() { - components.get('user/sessions').on('click', '[data-action]', function () { - var parentEl = $(this).parents('[data-uuid]'); - var uuid = parentEl.attr('data-uuid'); - - if (uuid) { - // This is done via DELETE because a user shouldn't be able to - // revoke his own session! This is what logout is for - $.ajax({ - url: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/session/' + uuid, - method: 'delete', - headers: { - 'x-csrf-token': config.csrf_token, - }, - }).done(function () { - parentEl.remove(); - }).fail(function (err) { - try { - var errorObj = JSON.parse(err.responseText); - if (errorObj.loggedIn === false) { - window.location.href = config.relative_path + '/login?error=' + errorObj.title; - } - app.alertError(errorObj.title); - } catch (e) { - app.alertError('[[error:invalid-data]]'); - } - }); - } - }); - } - return Info; }); diff --git a/public/src/client/account/sessions.js b/public/src/client/account/sessions.js new file mode 100644 index 0000000000..e4b44e98e3 --- /dev/null +++ b/public/src/client/account/sessions.js @@ -0,0 +1,44 @@ +'use strict'; + + +define('forum/account/sessions', ['forum/account/header', 'components'], function (header, components) { + var Sessions = {}; + + Sessions.init = function () { + header.init(); + Sessions.prepareSessionRevocation(); + }; + + Sessions.prepareSessionRevocation = function () { + components.get('user/sessions').on('click', '[data-action]', function () { + var parentEl = $(this).parents('[data-uuid]'); + var uuid = parentEl.attr('data-uuid'); + + if (uuid) { + // This is done via DELETE because a user shouldn't be able to + // revoke his own session! This is what logout is for + $.ajax({ + url: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/session/' + uuid, + method: 'delete', + headers: { + 'x-csrf-token': config.csrf_token, + }, + }).done(function () { + parentEl.remove(); + }).fail(function (err) { + try { + var errorObj = JSON.parse(err.responseText); + if (errorObj.loggedIn === false) { + window.location.href = config.relative_path + '/login?error=' + errorObj.title; + } + app.alertError(errorObj.title); + } catch (e) { + app.alertError('[[error:invalid-data]]'); + } + }); + } + }); + }; + + return Sessions; +}); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 1d2a30272e..7272496cc8 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -65,6 +65,17 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { globalMod: true, admin: true, }, + }, { + id: 'sessions', + route: 'sessions', + name: '[[pages:account/sessions]]', + visibility: { + self: true, + other: false, + moderator: false, + globalMod: true, + admin: true, + }, }, { id: 'consent', route: 'consent', diff --git a/src/controllers/accounts/sessions.js b/src/controllers/accounts/sessions.js index 809cdb6dad..df77a44dbc 100644 --- a/src/controllers/accounts/sessions.js +++ b/src/controllers/accounts/sessions.js @@ -4,9 +4,39 @@ var async = require('async'); var db = require('../../database'); var user = require('../../user'); +var helpers = require('../helpers'); +var accountHelpers = require('./helpers'); var sessionController = {}; +sessionController.get = function (req, res, callback) { + var userData; + + async.waterfall([ + function (next) { + accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next); + }, + function (_userData, next) { + userData = _userData; + if (!userData) { + return callback(); + } + + async.parallel({ + sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID), + }, next); + }, + function (data) { + userData.sessions = data.sessions; + + userData.title = '[[pages:account/sessions]]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: '/user/' + userData.userslug }, { text: '[[pages:account/sessions]]' }]); + + res.render('account/sessions', userData); + }, + ], callback); +}; + sessionController.revoke = function (req, res, next) { if (!req.params.hasOwnProperty('uuid')) { return next(); diff --git a/src/routes/accounts.js b/src/routes/accounts.js index 236a7e3682..61b5e6dd66 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -33,7 +33,7 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/uploads', middleware, accountMiddlewares, controllers.accounts.uploads.get); setupPageRoute(app, '/user/:userslug/consent', middleware, accountMiddlewares, controllers.accounts.consent.get); setupPageRoute(app, '/user/:userslug/blocks', middleware, accountMiddlewares, controllers.accounts.blocks.getBlocks); - + setupPageRoute(app, '/user/:userslug/sessions', middleware, accountMiddlewares, controllers.accounts.sessions.get); app.delete('/api/user/:userslug/session/:uuid', [middleware.exposeUid, middleware.ensureSelfOrGlobalPrivilege], controllers.accounts.sessions.revoke); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get);