diff --git a/.gitignore b/.gitignore index 76bc0ea38..139fab33c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ data npm-debug.log pins/ blob/ +privileged.conf diff --git a/config.example.js b/config.example.js index 626d81908..76bba6eae 100644 --- a/config.example.js +++ b/config.example.js @@ -180,6 +180,31 @@ module.exports = { */ suppressRPCErrors: false, + + /* WARNING: EXPERIMENTAL + * + * CryptPad features experimental support for encrypted file upload. + * Our encryption format is still liable to change. As such, we do not + * guarantee that files uploaded now will be supported in the future + */ + + /* Setting this value to anything other than true will cause file upload + * attempts to be rejected outright. + */ + enableUploads: true, + + /* If you have enabled file upload, you have the option of restricting it + * to a list of users identified by their public keys. If this value is set + * to true, your server will query a file (cryptpad/privileged.conf) when + * users connect via RPC. Only users whose public keys can be found within + * the file will be allowed to upload. + * + * privileged.conf uses '#' for line comments, and splits keys by newline. + * This is a temporary measure until a better quota system is in place. + * registered users' public keys can be found on the settings page. + */ + restrictUploads: true, + /* it is recommended that you serve cryptpad over https * the filepaths below are used to configure your certificates */ diff --git a/customize.dist/about.html b/customize.dist/about.html index 2b13aca95..b0b719033 100644 --- a/customize.dist/about.html +++ b/customize.dist/about.html @@ -114,7 +114,7 @@ - + diff --git a/customize.dist/contact.html b/customize.dist/contact.html index eff9c1869..aa81fac60 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -111,7 +111,7 @@ - + diff --git a/customize.dist/index.html b/customize.dist/index.html index f6ab10fa6..d246d84d0 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -233,7 +233,7 @@ - + diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index a7751db84..00a266236 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -132,7 +132,7 @@ - + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index b15a2b5da..4cf4b101d 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -39,5 +39,5 @@ - + diff --git a/customize.dist/terms.html b/customize.dist/terms.html index edfd77f86..68eb51599 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -115,7 +115,7 @@ - + diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index b0b7a9304..9219ab457 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_logoutEverywhereTitle = "Log out everywhere"; + out.settings_logoutEverywhere = "Log out of all other web sessions"; + out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices."; + // index.html diff --git a/package.json b/package.json index ed76592b6..696091528 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.5.0", + "version": "1.6.0", "dependencies": { "express": "~4.10.1", "ws": "^1.0.1", diff --git a/readme.md b/readme.md index 60da12331..ed08b0eb6 100644 --- a/readme.md +++ b/readme.md @@ -54,6 +54,9 @@ These settings can be found in your configuration file in the `contentSecurity` ## Maintenance +Before upgrading your CryptPad instance to the latest version, we recommend that you check what has changed since your last update. +You can do so by checking which version you have (see package.json), and comparing it against newer [release notes](https://github.com/xwiki-labs/cryptpad/releases). + To get access to the most recent codebase: ``` diff --git a/rpc.js b/rpc.js index 471236589..db3e98faa 100644 --- a/rpc.js +++ b/rpc.js @@ -409,6 +409,30 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) { }); }; +var getPrivilegedUserList = function (cb) { + Fs.readFile('./privileged.conf', 'utf8', function (e, body) { + if (e) { + if (e.code === 'ENOENT') { + return void cb(void 0, []); + } + return void (e.code); + } + var list = body.split(/\n/) + .map(function (line) { + return line.replace(/#.*$/, '').trim(); + }) + .filter(function (x) { return x; }); + cb(void 0, list); + }); +}; + +var isPrivilegedUser = function (publicKey, cb) { + getPrivilegedUserList(function (e, list) { + if (e) { return void cb(false); } + cb(list.indexOf(publicKey) !== -1); + }); +}; + var getLimit = function (cb) { cb = cb; // TODO }; @@ -625,6 +649,11 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void Respond('INVALID_MSG'); } + var deny = function () { + Respond('E_ACCESS_DENIED'); + }; + + var handleMessage = function (privileged) { switch (msg[0]) { case 'COOKIE': return void Respond(void 0); case 'RESET': @@ -662,25 +691,54 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) Respond(void 0, dict); }); + + // restricted to privileged users... case 'UPLOAD': + if (!privileged) { return deny(); } return void upload(blobStagingPath, Sessions, safeKey, msg[1], function (e, len) { Respond(e, len); }); case 'UPLOAD_STATUS': + if (!privileged) { return deny(); } return void upload_status(blobStagingPath, Sessions, safeKey, function (e, stat) { Respond(e, stat); }); case 'UPLOAD_COMPLETE': + if (!privileged) { return deny(); } return void upload_complete(blobStagingPath, blobPath, Sessions, safeKey, function (e, hash) { Respond(e, hash); }); case 'UPLOAD_CANCEL': + if (!privileged) { return deny(); } return void upload_cancel(blobStagingPath, Sessions, safeKey, function (e) { Respond(e); }); default: return void Respond('UNSUPPORTED_RPC_CALL', msg); } + }; + + // reject uploads unless explicitly enabled + if (config.enableUploads !== true) { + return void handleMessage(false); + } + + // restrict upload capability unless explicitly disabled + if (config.restrictUploads === false) { + return void handleMessage(true); + } + + // if session has not been authenticated, do so + var session = Sessions[publicKey]; + if (typeof(session.privilege) !== 'boolean') { + return void isPrivilegedUser(publicKey, function (yes) { + session.privilege = yes; + handleMessage(yes); + }); + } + + // if authenticated, proceed + handleMessage(session.privilege); }; Store.create({ diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5194d9021..b58209029 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -196,6 +196,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..623152a35 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -134,6 +134,14 @@ define([ return ret; }; + 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 +153,37 @@ define([ f(void 0, store); } + var requestLogin = function (Cryptpad) { + // 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/'; + }; + + 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/index.html b/www/settings/index.html index c24748490..752d04468 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -105,7 +105,7 @@ - + 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 = $('
', { 'class': 'logoutEverywhere', }); + $('