diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index b0b7a9304..2eaf65345 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -333,6 +333,10 @@ define(function () { out.settings_pinningError = "Something went wrong"; out.settings_usageAmount = "Your pinned pads occupy {0}MB"; + out.settings_logoutEverywhere = "Expire remote sessions"; + out.settings_logoutEverywhereTitle = "Log out of all other sessions"; + out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices."; + // index.html diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 104b67a9b..b3a660432 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -192,6 +192,7 @@ define([ [ userNameKey, userHashKey, + 'loginToken', ].forEach(function (k) { sessionStorage.removeItem(k); localStorage.removeItem(k); diff --git a/www/common/fsStore.js b/www/common/fsStore.js index a929e84b3..8d6aa31bf 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -134,6 +134,23 @@ define([ return ret; }; + var requestLogin = function () { + // log out so that you don't go into an endless loop... + Cryptpad.logout(); + + // redirect them to log in, and come back when they're done. + sessionStorage.redirectTo = window.location.href; + window.location.href = '/login/'; + }; + + var tryParsing = function (x) { + try { return JSON.parse(x); } + catch (e) { + console.error(e); + return null; + } + }; + var onReady = function (f, proxy, Cryptpad, exp) { var fo = exp.fo = FO.init(proxy.drive, { Cryptpad: Cryptpad @@ -145,6 +162,28 @@ define([ f(void 0, store); } + if (Cryptpad.isLoggedIn()) { +/* This isn't truly secure, since anyone who can read the user's object can + set their local loginToken to match that in the object. However, it exposes + a UI that will work most of the time. */ + var tokenKey = 'loginToken'; + + // every user object should have a persistent, random number + if (typeof(proxy.loginToken) !== 'number') { + proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + } + + var localToken = tryParsing(localStorage.getItem(tokenKey)); + if (localToken === null) { + // if that number hasn't been set to localStorage, do so. + localStorage.setItem(tokenKey, proxy.loginToken); + } else if (localToken !== proxy[tokenKey]) { + // if it has been, and the local number doesn't match that in + // the user object, request that they reauthenticate. + return void requestLogin(); + } + } + if (typeof(proxy.allowUserFeedback) !== 'boolean') { proxy.allowUserFeedback = true; } @@ -157,13 +196,7 @@ define([ // if the user is logged in, but does not have signing keys... if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) { - // log out so that you don't go into an endless loop... - Cryptpad.logout(); - - // redirect them to log in, and come back when they're done. - sessionStorage.redirectTo = window.location.href; - window.location.href = '/login/'; - return; + return void requestLogin(); } proxy.on('change', [Cryptpad.displayNameKey], function (o, n) { diff --git a/www/settings/main.js b/www/settings/main.js index 2a2a7cd3d..1e79371fd 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -252,6 +252,42 @@ define([ return $div; }; + var createLogoutEverywhere = function (obj) { + var proxy = obj.proxy; + var $div = $('