From f6f90712af9e7d7358de9db3d92704f6f3fb8471 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 Mar 2021 15:20:33 +0530 Subject: [PATCH 01/33] stricter tests for the sandbox checkup --- www/checkup/main.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/www/checkup/main.js b/www/checkup/main.js index bcbdf0d7b..5dcd1e8cf 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -42,12 +42,13 @@ define([ return void cb(trimmedSafe !== trimmedUnsafe); }, _alert('Sandbox configuration: httpUnsafeOrigin !== httpSafeOrigin')); + assert(function (cb) { + cb(trimmedSafe === ApiConfig.httpSafeOrigin); + }, "httpSafeOrigin must not have a trailing slash"); + assert(function (cb) { var origin = window.location.origin; - return void cb([ - origin, - origin + '/' - ].indexOf(ApiConfig.httpUnsafeOrigin) !== -1); + return void cb(ApiConfig.httpUnsafeOrigin === origin); }, _alert('Sandbox configuration: loading via httpUnsafeOrigin')); From 91e1063e4a903dffdc5fc0c2728bce138daf8315 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 10:51:47 +0100 Subject: [PATCH 02/33] Fix OO generated ID for image properties --- www/common/onlyoffice/inner.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 84ad474b0..d06e9a534 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1281,13 +1281,15 @@ define([ '#title-user-name { display: none !important; }' + (supportsXLSX() ? '' : '#slot-btn-dt-print { display: none !important; }') + // New OO: - '#asc-gen566 { display: none !important; }' + // Insert image from url + '#asc-gen257 { display: none !important; }' + // Insert image from url 'section[data-tab="ins"] .separator:nth-last-child(2) { display: none !important; }' + // separator '#slot-btn-insequation { display: none !important; }' + // Insert equation //'.toolbar .tabs .ribtab:not(.canedit) { display: none !important; }' + // Switch collaborative mode '#fm-btn-info { display: none !important; }' + // Author name, doc title, etc. in "File" (menu entry) '#panel-info { display: none !important; }' + // Same but content '#image-button-from-url { display: none !important; }' + // Inline image settings: replace with url + '#asc-gen1839 { display: none !important; }' + // Image context menu: replace with url + '#asc-gen5883 { display: none !important; }' + // Rightside image menu: replace with url '#file-menu-panel .devider { display: none !important; }' + // separator in the "File" menu '#left-btn-spellcheck, #left-btn-about { display: none !important; }'+ 'div.btn-users.dropdown-toggle { display: none; !important }'; From e1e1795714f5e13fbf912ac6ed918ee0ad8b032e Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 10:57:06 +0100 Subject: [PATCH 03/33] Fix race condition and display name with noDrive --- www/common/outer/async-store.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 4db6a6142..60dc9ca73 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2835,6 +2835,9 @@ define([ if (store.ready) { return; } // the store is already ready, it is a reconnection store.driveMetadata = info.metadata; if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } + if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) { + store.proxy[Constants.displayNameKey] = store.noDriveName; + } /* // deprecating localStorage migration as of 4.2.0 var drive = rt.proxy.drive; @@ -2950,7 +2953,13 @@ define([ if (!store.network) { var wsUrl = NetConfig.getWebsocketURL(); return void Netflux.connect(wsUrl).then(function (network) { - store.network = network; + // If we already haave a network (race condition), use the + // existing one and forget this one + if (!store.network) { store.network = network; } + else { + network.disconnect(); + network = store.network; + } // We need to know the HistoryKeeper ID to initialize the anon RPC // Join a basic ephemeral channel, get the ID and leave it instantly network.join('0000000000000000000000000000000000').then(function (wc) { From 9df98f0ca5f290d97b3c9e7029c1acb217edf60f Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 Mar 2021 15:48:34 +0530 Subject: [PATCH 04/33] latest changes in changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e8379bd0..0dd0f5091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,11 +32,15 @@ * pinning? * oo rebuild * OnlyOffice v6.2 + * some buttons that we were hiding have new ids and needed to be hidden again * translations * updated catch-phrase (Collaboration suite\nend-to-end-encrypted and open-source * CKEditor * cursor jump when clicking on a comment bubble * keybindings for common styles + * test if this affects scroll position (it shouldn't) + * check that CTRL-space doesn't mess with anything and that it is what Google uses + * test on Mac * nodrive * load anonymous accounts without creating a drive * faster load time, less junk on the server From 5600720a9859e0fa306ad5faacedbe9d68663767 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 Mar 2021 16:31:26 +0530 Subject: [PATCH 05/33] remove an XXX --- www/common/outer/messenger.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index 1ac7a6e17..981d9f8de 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -537,10 +537,9 @@ define([ if (peer === hk) { return; } if (channel.readOnly) { return; } - // XXX review // Sending myData is used to build a "mapId" object which links // netflux IDs to a curvePublic/uid. We use this map in friend chat - // to detect is the other user is online and we also use it in team chat + // to detect if the other user is online and we also use it in team chat // to show if other team members are online (in the roster section). // It is not needed in the pad chat for now and only causes useless // network usage. From 31414ca7b0690fc9c410ba9bb43bb4786f4851f9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 15:09:05 +0100 Subject: [PATCH 06/33] Fix degraded mode not set when multiple tabs on the same pad --- www/common/outer/cursor.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/www/common/outer/cursor.js b/www/common/outer/cursor.js index b6662b75f..8338fef16 100644 --- a/www/common/outer/cursor.js +++ b/www/common/outer/cursor.js @@ -50,6 +50,12 @@ define([ }); }; + var updateDegraded = function (ctx, wc, chan) { + var m = wc.members; + chan.degraded = (m.length-1) >= DEGRADED; + ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients); + }; + var initCursor = function (ctx, obj, client, cb) { var channel = obj.channel; var secret = obj.secret; @@ -92,14 +98,10 @@ define([ // ==> And push the new tab to the list chan.clients.push(client); + updateDegraded(ctx, chan.wc, chan); return void cb(); } - var updateDegraded = function (ctx, wc, chan) { - var m = wc.members; - chan.degraded = (m.length-1) >= DEGRADED; - ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients); - }; var onOpen = function (wc) { ctx.channels[channel] = ctx.channels[channel] || {}; From 3e969dd9a5e52c38aa997f25a8de13e3f16ade2a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 15:09:28 +0100 Subject: [PATCH 07/33] Fix noDrive uid always modified --- www/common/outer/async-store.js | 11 +++++++++-- www/debug/main.js | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 60dc9ca73..d4f49014f 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -586,11 +586,14 @@ define([ var proxy = store.proxy || {}; var disableThumbnails = Util.find(proxy, ['settings', 'general', 'disableThumbnails']); var teams = (store.modules['team'] && store.modules['team'].getTeamsData(app)) || {}; + if (!proxy.uid) { + store.noDriveUid = store.noDriveUid || Hash.createChannelId(); + } var metadata = { // "user" is shared with everybody via the userlist user: { name: proxy[Constants.displayNameKey] || store.noDriveName || "", - uid: proxy.uid || Hash.createChannelId(), // Random uid in nodrive mode + uid: proxy.uid || store.noDriveUid, // Random uid in nodrive mode avatar: Util.find(proxy, ['profile', 'avatar']), profile: Util.find(proxy, ['profile', 'view']), color: getUserColor(), @@ -858,6 +861,7 @@ define([ Store.setDisplayName = function (clientId, value, cb) { if (!store.proxy) { store.noDriveName = value; + broadcast([clientId], "UPDATE_METADATA"); return void cb(); } if (store.modules['profile']) { @@ -2836,7 +2840,10 @@ define([ store.driveMetadata = info.metadata; if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) { - store.proxy[Constants.displayNameKey] = store.noDriveName; + rt.proxy[Constants.displayNameKey] = store.noDriveName; + } + if (!rt.proxy.uid && store.noDriveUid) { + rt.proxy.uid = store.noDriveUid; } /* // deprecating localStorage migration as of 4.2.0 diff --git a/www/debug/main.js b/www/debug/main.js index 9053dfbf6..6f855e1db 100644 --- a/www/debug/main.js +++ b/www/debug/main.js @@ -44,6 +44,7 @@ define([ meta.debugDrive = drive; }; SFCommonO.start({ + noDrive: true, addData:addData }); }); From 9980346ef7947acaa19483e6bd39cb1c28045106 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 15:19:57 +0100 Subject: [PATCH 08/33] Set degraded mode limit in AppConfig --- www/common/application_config_internal.js | 11 +++++++++++ www/common/outer/cursor.js | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 496e65f1f..f91c73f56 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -179,5 +179,16 @@ define(function() { // You can change the value here. // config.maxOwnedTeams = 5; + // The userlist displayed in collaborative documents is stored alongside the document data. + // Everytime someone with edit rights joins a document or modify their user data (display + // name, avatar, color, etc.), they update the "userlist" part of the document. When too many + // editors are in the same document, all these changes increase the risks of conflicts which + // require CPU time to solve. A "degraded" mode can now be set when a certain number of editors + // are in a document at the same time. This mode disables the userlist, the chat and the + // position of other users' cursor. You can configure the number of user from which the session + // will enter into degraded mode. A big number may result in collaborative edition being broken, + // but this number depends on the network and CPU performances of each user's device. + config.degradedLimit = 8; + return config; }); diff --git a/www/common/outer/cursor.js b/www/common/outer/cursor.js index 8338fef16..371a4565e 100644 --- a/www/common/outer/cursor.js +++ b/www/common/outer/cursor.js @@ -2,11 +2,13 @@ define([ '/common/common-util.js', '/common/common-constants.js', '/customize/messages.js', + '/customize/application_config.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Constants, Messages, Crypto) { +], function (Util, Constants, Messages, AppConfig, Crypto) { var Cursor = {}; - var DEGRADED = 3; // XXX Number of users before switching to degraded mode + var DEGRADED = AppConfig.degradedLimit || 8; +console.log(DEGRADED); var convertToUint8 = function (obj) { var l = Object.keys(obj).length; From 86e50e04775aad51c89a92440426233c82b909fd Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 17:05:28 +0100 Subject: [PATCH 09/33] Add a configuration key to disable driveless mode --- www/common/application_config_internal.js | 8 ++++++++ www/common/sframe-common-outer.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index f91c73f56..e119b3e5e 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -190,5 +190,13 @@ define(function() { // but this number depends on the network and CPU performances of each user's device. config.degradedLimit = 8; + // In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad + // in which they could store their pads. The new "driveless" mode allow users to open an existing + // pad without creating a drive in the background. The drive will only be created if they visit + // a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable + // the driveless mode by changing the following value to "false" + config.allowDrivelessMode = true; + config.allowDrivelessMode = true; + return config; }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index df49ee6a3..77aa9ba43 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -222,7 +222,7 @@ define([ } catch (e) { console.error(e); } Cryptpad.ready(waitFor(), { - noDrive: cfg.noDrive, + noDrive: cfg.noDrive && AppConfig.allowDrivelessMode, driveEvents: cfg.driveEvents, cache: Boolean(cfg.cache), currentPad: currentPad From 18e995f63a8a75a145b389099d02160db8567ddd Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 17:08:11 +0100 Subject: [PATCH 10/33] Add the degraded threshold value to the translation key in toolbar --- www/common/outer/cursor.js | 1 - www/common/toolbar.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/www/common/outer/cursor.js b/www/common/outer/cursor.js index 371a4565e..95b2b34c6 100644 --- a/www/common/outer/cursor.js +++ b/www/common/outer/cursor.js @@ -8,7 +8,6 @@ define([ var Cursor = {}; var DEGRADED = AppConfig.degradedLimit || 8; -console.log(DEGRADED); var convertToUint8 = function (obj) { var l = Object.keys(obj).length; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c42aa36cb..aafc356a5 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -212,6 +212,7 @@ MessengerUI, Messages) { var $editUsersList = $('
', {'class': 'cp-toolbar-userlist-others'}) .appendTo($editUsers); + var degradedLimit = Config.degradedLimit || 8; if (!online) { $('').text(Messages.userlist_offline).appendTo($editUsersList); numberOfEditUsers = '?'; @@ -219,8 +220,7 @@ MessengerUI, Messages) { } else if (metadataMgr.isDegraded() === true) { numberOfEditUsers = Math.max(metadataMgr.getChannelMembers().length - 1, 0); numberOfViewUsers = ''; - Messages.toolbar_degraded = "Too many editors are present in the pad. The userlist has been disabled to improve performances"; // XXX - $('').text(Messages.toolbar_degraded).appendTo($editUsersList); + $('').text(Messages._getKey('toolbar_degraded', [degradedLimit])).appendTo($editUsersList); } // Update the buttons From c168393c21559d785fbd8e8938eb457a8bf553e9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 17:14:40 +0100 Subject: [PATCH 11/33] Only allow driveless mode for existing pads --- www/common/sframe-common-outer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 77aa9ba43..e74fb882c 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -221,8 +221,11 @@ define([ } } catch (e) { console.error(e); } + // NOTE: Driveless mode should only work for existing pads, but we can't check that + // before creating the worker because we need the anon RPC to do so. + // We're only going to check if a hash exists in the URL or not. Cryptpad.ready(waitFor(), { - noDrive: cfg.noDrive && AppConfig.allowDrivelessMode, + noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash, driveEvents: cfg.driveEvents, cache: Boolean(cfg.cache), currentPad: currentPad From c6b8b11dc3c18c7b55ef3646970665b969b3067a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 Mar 2021 18:10:27 +0100 Subject: [PATCH 12/33] Fix Destroy and change password when pad not stored --- www/common/cryptpad-common.js | 16 ++++++++++++---- www/common/inner/access.js | 11 +++++++++-- www/common/outer/async-store.js | 2 ++ www/common/proxy-manager.js | 15 ++++++++++++--- www/common/sframe-common-outer.js | 1 + www/secureiframe/main.js | 1 + 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 31c65bb86..f4419d2cb 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1095,6 +1095,7 @@ define([ common.changePadPassword = function (Crypt, Crypto, data, cb) { var href = data.href; + var oldPassword = data.oldPassword; var newPassword = data.password; var teamId = data.teamId; if (!href) { return void cb({ error: 'EINVAL_HREF' }); } @@ -1123,7 +1124,9 @@ define([ var isSharedFolder = parsed.type === 'drive'; - var optsGet = {}; + var optsGet = { + password: oldPassword + }; var optsPut = { password: newPassword, metadata: {}, @@ -1133,7 +1136,7 @@ define([ var cryptgetVal; Nthen(function (waitFor) { - if (parsed.hashData && parsed.hashData.password) { + if (parsed.hashData && parsed.hashData.password && !oldPassword) { common.getPadAttribute('password', waitFor(function (err, password) { optsGet.password = password; }), href); @@ -1418,6 +1421,7 @@ define([ common.changeOOPassword = function (data, _cb) { var cb = Util.once(Util.mkAsync(_cb)); var href = data.href; + var oldPassword = data.oldPassword; var newPassword = data.password; var teamId = data.teamId; if (!href) { return void cb({ error: 'EINVAL_HREF' }); } @@ -1452,12 +1456,16 @@ define([ validateKey: newSecret.keys.validateKey }, }; - var optsGet = {}; + var optsGet = { + password: oldPassword + }; Nthen(function (waitFor) { common.getPadAttribute('', waitFor(function (err, _data) { padData = _data; - optsGet.password = padData.password; + if (!oldPassword) { + optsGet.password = padData.password; + } }), href); common.getAccessKeys(waitFor(function (keys) { optsGet.accessKeys = keys; diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 3bbef91e9..a09f98bfb 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -888,9 +888,16 @@ define([ }); } + var href = data.href; + var hashes = priv.hashes || {}; + var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash; + if (data.fakeHref) { + href = Hash.hashToHref(bestHash, priv.app); + } sframeChan.query(q, { teamId: typeof(owned) !== "boolean" ? owned : undefined, - href: data.href, + href: href, + oldPassword: priv.password, password: newPass }, function (err, data) { $(passwordOk).text(Messages.properties_changePasswordButton); @@ -956,7 +963,7 @@ define([ spinner.spin(); sframeChan.query('Q_DELETE_OWNED', { teamId: typeof(owned) !== "boolean" ? owned : undefined, - channel: data.channel + channel: data.channel || priv.channel }, function (err, obj) { spinner.done(); UI.findCancelButton().click(); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d4f49014f..8ac1a77c3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -331,6 +331,8 @@ define([ teamId = data.teamId; } + // XXX CLEAR CACHE + if (channel === store.driveChannel && !force) { return void cb({error: 'User drive removal blocked!'}); } diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 7093f0ec5..011ec4f36 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -868,7 +868,6 @@ define([ if (fId && Env.folders[fId] && Env.folders[fId].deleting) { delete Env.folders[fId].deleting; } - console.error(obj.error, chan); Feedback.send('ERROR_DELETING_OWNED_PAD=' + chan + '|' + obj.error, true); return void cb(); } @@ -881,6 +880,11 @@ define([ ids.push(fId); } + if (!ids.length) { + toDelete = undefined; + return void cb(); + } + ids.forEach(function (id) { var paths = findFile(Env, id); var _resolved = _resolvePaths(Env, paths); @@ -912,8 +916,13 @@ define([ }); }); }).nThen(function () { - // Remove deleted pads from the drive - _delete(Env, { resolved: toDelete }, cb); + if (!toDelete) { + // Nothing to delete + cb(); + } else { + // Remove deleted pads from the drive + _delete(Env, { resolved: toDelete }, cb); + } // If we were using the access modal, send a refresh command if (data.channel) { Env.Store.refreshDriveUI(); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e74fb882c..687d3a54b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1407,6 +1407,7 @@ define([ }; config.data = { app: parsed.type, + channel: secret.channel, hashes: hashes, password: password, isTemplate: isTemplate, diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index bb7f05cca..be026194e 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -101,6 +101,7 @@ define([ origin: window.location.origin, pathname: window.location.pathname, feedbackAllowed: Utils.Feedback.state, + channel: config.data.channel, hashes: config.data.hashes, password: config.data.password, propChannels: config.data.getPropChannels(), From 3141d7add18b4782891a615665062a19e15c78f1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 14:12:14 +0530 Subject: [PATCH 13/33] remove trailing slashes from configured origins in both the server and the example config file --- config/config.example.js | 2 +- server.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/config/config.example.js b/config/config.example.js index 855a848e3..37fd5ada9 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -54,7 +54,7 @@ module.exports = { * and it may have unintended consequences in practice. * */ - httpUnsafeOrigin: 'http://localhost:3000/', + httpUnsafeOrigin: 'http://localhost:3000', /* httpSafeOrigin is the URL that is used for the 'sandbox' described above. * If you're testing or developing with CryptPad on your local machine then diff --git a/server.js b/server.js index f3ee71d0b..d2df3bba6 100644 --- a/server.js +++ b/server.js @@ -16,15 +16,19 @@ var Env = require("./lib/env").create(config); var app = Express(); +var canonicalizeOrigin = function (s) { + return (s || '').trim().replace(/\/+$/, ''); +}; + (function () { // you absolutely must provide an 'httpUnsafeOrigin' if (typeof(config.httpUnsafeOrigin) !== 'string') { throw new Error("No 'httpUnsafeOrigin' provided"); } - config.httpUnsafeOrigin = config.httpUnsafeOrigin.trim(); + config.httpUnsafeOrigin = canonicalizeOrigin(config.httpUnsafeOrigin); if (typeof(config.httpSafeOrigin) === 'string') { - config.httpSafeOrigin = config.httpSafeOrigin.trim().replace(/\/$/, ''); + config.httpSafeOrigin = canonicalizeOrigin(config.httpSafeOrigin); } // fall back to listening on a local address From 91266a701811ba3dd54cafb02cfe0e061d6adff3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 14:22:24 +0530 Subject: [PATCH 14/33] open new links from the 'about CryptPad' menu in a new tab --- www/common/common-ui-elements.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 562c3757c..08c84d1e6 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1552,6 +1552,8 @@ define([ faqLine, ]); + $(content).find('a').attr('target', '_blank'); + var buttons = [ { className: 'primary', From ed09b92592d070bf4cfbd708dc6f299c682ad191 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 10:39:41 +0100 Subject: [PATCH 15/33] Fix deleted pad restored corrupted from cache --- lib/hk-util.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/hk-util.js b/lib/hk-util.js index 8780addcd..0d8d6224f 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -661,6 +661,8 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { if (txid) { msg[0] = txid; } Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore); }, (err) => { + // Any error but ENOENT: abort + // ENOENT is allowed in case we want to create a new pad if (err && err.code !== 'ENOENT') { if (err.message === "EUNKNOWN") { Log.error("HK_GET_HISTORY", { @@ -680,6 +682,14 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { return; } + // If we're asking for a specific version (lastKnownHash) but we receive an + // ENOENT, this is not a pad creation so we need to abort. + if (err && err.code === 'ENOENT' && lastKnownHash) { + const parsedMsg2 = {error:'EDELETED', channel: channelName, txid: txid}; + Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]); + return; + } + if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) { handleFirstMessage(Env, channelName, metadata); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]); From 4f5cb98fd5af8c7381bb3eb5672cab06fa5afcde Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 12:08:37 +0100 Subject: [PATCH 16/33] Fix OO password change --- www/common/cryptpad-common.js | 8 +++----- www/common/inner/access.js | 16 +++++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index f4419d2cb..8bf01c965 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1435,7 +1435,6 @@ define([ var oldMetadata; var oldRtChannel; var privateData; - var padData; var newSecret; if (parsed.hashData.version >= 2) { @@ -1462,9 +1461,8 @@ define([ Nthen(function (waitFor) { common.getPadAttribute('', waitFor(function (err, _data) { - padData = _data; - if (!oldPassword) { - optsGet.password = padData.password; + if (!oldPassword && _data) { + optsGet.password = _data.password; } }), href); common.getAccessKeys(waitFor(function (keys) { @@ -1472,7 +1470,7 @@ define([ optsPut.accessKeys = keys; })); }).nThen(function (waitFor) { - oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password); + oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password); require([ '/common/cryptget.js', diff --git a/www/common/inner/access.js b/www/common/inner/access.js index a09f98bfb..95450e073 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -894,6 +894,7 @@ define([ if (data.fakeHref) { href = Hash.hashToHref(bestHash, priv.app); } + var isNotStored = Boolean(data.fakeHref); sframeChan.query(q, { teamId: typeof(owned) !== "boolean" ? owned : undefined, href: href, @@ -931,22 +932,27 @@ define([ // Pad password changed: update the href // Use hidden hash if needed (we're an owner of this pad so we know it is stored) var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']); - var href = (priv.readOnly && data.roHref) ? data.roHref : data.href; + if (isNotStored) { useUnsafe = true; } + var _href = (priv.readOnly && data.roHref) ? data.roHref : data.href; if (useUnsafe !== true) { - var newParsed = Hash.parsePadUrl(href); + var newParsed = Hash.parsePadUrl(_href); var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass); var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {}); - href = Hash.hashToHref(newHash, parsed.type); + _href = Hash.hashToHref(newHash, parsed.type); } + var reload = false; + // Trigger a page reload if the href didn't change + if (_href === href) { _href = undefined; } + if (data.warning) { return void UI.alert(Messages.properties_passwordWarning, function () { - common.gotoURL(href); + common.gotoURL(_href); }, {force: true}); } return void UI.alert(Messages.properties_passwordSuccess, function () { if (!isSharedFolder) { - common.gotoURL(href); + common.gotoURL(_href); } }, {force: true}); }); From b8e0d6a1fe15f3d0ee0dc9a51a62e4b2bc2c73cd Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 16:50:33 +0530 Subject: [PATCH 17/33] remove a duplicated attribute, lint compliance --- www/common/application_config_internal.js | 1 - www/common/inner/access.js | 1 - 2 files changed, 2 deletions(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index e119b3e5e..9b3eaed26 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -196,7 +196,6 @@ define(function() { // a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable // the driveless mode by changing the following value to "false" config.allowDrivelessMode = true; - config.allowDrivelessMode = true; return config; }); diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 95450e073..47b04a5c4 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -941,7 +941,6 @@ define([ _href = Hash.hashToHref(newHash, parsed.type); } - var reload = false; // Trigger a page reload if the href didn't change if (_href === href) { _href = undefined; } From 5eddb41d776885f79919891c55984db9a092141a Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 16:51:11 +0530 Subject: [PATCH 18/33] add more background info for some protocol semantics --- lib/hk-util.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/hk-util.js b/lib/hk-util.js index 0d8d6224f..da08fcd8b 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -685,6 +685,14 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { // If we're asking for a specific version (lastKnownHash) but we receive an // ENOENT, this is not a pad creation so we need to abort. if (err && err.code === 'ENOENT' && lastKnownHash) { +/* + This informs clients that the pad they're trying to load was deleted by its owner. + The user in question might be reconnecting or might have loaded the document from their cache. + The owner that deleted it could be another user or the same user from a different device. + Either way, the respectful thing to do is display an error screen informing them that the content + is no longer on the server so they don't abuse the data and so that they don't unintentionally continue + to edit it in a broken state. +*/ const parsedMsg2 = {error:'EDELETED', channel: channelName, txid: txid}; Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]); return; From 0dedaa34cf7ce5b3ba5f3ac8ff81169d1636f29c Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 16:51:25 +0530 Subject: [PATCH 19/33] more changes --- CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd0f5091..644be1c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * websockets * sandbox CSP * login block + * recommend against trailing slashes for configured domains + * remove slashes in server.js anyway * admin page * support responses to closed tickets * collapse very long messages @@ -24,7 +26,9 @@ * display survey URL * support 'KB' in Util.magnitudeOfBytes * degraded mode - * decide on a number + * decide on a number: 8 + * provide an easy way to change it (application_config.js) + * inform users what the limit is (when degraded mode "kicks in") * sheets * fix naming collisions between images in spreadsheets * degraded mode not supported @@ -44,6 +48,16 @@ * nodrive * load anonymous accounts without creating a drive * faster load time, less junk on the server + * `AppConfig.allowDrivelessMode` + * cursor color is randomly generated each time and doesn't persist after creating a drive +* secure iframe now always knows the channel of the related document + * more consistent API with other APPs +* debug app doesn't create a drive +* implement/fix ability to destroy pads whether they exist in your drive or not + + +* Known issues + * change password for documents in your drive when you don't have the most recent password (multi-owner pads) # 4.2.1 From 4762cbf491fcfcabda4dadc750047bf6b68d9636 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 17:54:39 +0530 Subject: [PATCH 20/33] preserve data stored in the hash when navigating to register from login --- www/login/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/login/main.js b/www/login/main.js index 768c012a8..5023e0b19 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -70,7 +70,8 @@ define([ if ($uname.val()) { localStorage.login_user = $uname.val(); } - window.location.href = '/register/'; + var hash = (window.location.hash || '').replace(/\/login\//, '/register/'); + window.location.href = '/register/' + hash; }); Test(function (t) { From 56c095a6c84b3adce5fee4e203c521dec961a66f Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 Mar 2021 17:55:23 +0530 Subject: [PATCH 21/33] don't distribute a default survey for all instances --- www/common/application_config_internal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 9b3eaed26..0f91ea447 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -162,7 +162,7 @@ define(function() { // making it much faster to open new tabs. config.disableWorkers = false; - config.surveyURL = "https://survey.cryptpad.fr/index.php/672782"; + //config.surveyURL = ""; // Teams are always loaded during the initial loading screen (for the first tab only if // SharedWorkers are available). Allowing users to be members of multiple teams can From 9b0f1d186cca20425256e1a5934af9e1eb2d8880 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 14:01:09 +0100 Subject: [PATCH 22/33] Fix password issues --- www/common/sframe-common-outer.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 687d3a54b..8d791c8cd 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -467,7 +467,7 @@ define([ currentPad.href = parsed.getUrl(opts); currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts); } - Cryptpad.getPadAttribute('title', w(function (err, data) { + Cryptpad.getPadAttribute('channel', w(function (err, data) { stored = (!err && typeof (data) === "string"); })); Cryptpad.getPadAttribute('password', w(function (err, val) { @@ -478,6 +478,11 @@ define([ passwordCfg.value = newPadPassword; } + // Pad not stored && password required: always ask for the password + if (!stored && parsed.hashData.password) { + return void askPassword(true, passwordCfg); + } + if (parsed.type === "file") { // `isNewChannel` doesn't work for files (not a channel) // `getFileSize` is not adapted to channels because of metadata @@ -503,10 +508,6 @@ define([ waitFor.abort(); return; } - if (!stored && !parsed.hashData.password) { - // We've received a link without /p/ and it doesn't work without a password: abort - return void todo(); - } // Wrong password or deleted file? askPassword(true, passwordCfg); })); From 067274cc072e76670b4f75c142cf9923550d546c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 22 Mar 2021 13:28:45 +0000 Subject: [PATCH 23/33] Fix register button overflowing modal --- customize.dist/src/less2/include/forms.less | 1 + 1 file changed, 1 insertion(+) diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index ae1096946..e74b67cb0 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -184,6 +184,7 @@ } &.btn-register { margin-top: 10px !important; + white-space: normal; } From 550c5175aa104fa93aaaa2d1b650cc5b713b2f11 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 17:49:33 +0100 Subject: [PATCH 24/33] Fix password change issues with cache --- www/common/common-ui-elements.js | 42 ++++++++++++++++-- www/common/cryptpad-common.js | 13 +++++- www/common/outer/async-store.js | 6 +++ www/common/sframe-common-outer.js | 71 ++++++++++++++++++++++++++----- 4 files changed, 117 insertions(+), 15 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 08c84d1e6..4f6c6c8b9 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2540,6 +2540,7 @@ define([ UIElements.onServerError = function (common, err, toolbar, cb) { //if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; } var priv = common.getMetadataMgr().getPrivateData(); + var sframeChan = common.getSframeChannel(); var msg = err.type; if (err.type === 'EEXPIRED') { msg = Messages.expiredError; @@ -2549,10 +2550,25 @@ define([ if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } } else if (err.type === 'EDELETED') { if (priv.burnAfterReading) { return void cb(); } + + // View users have the wrong seed, thay can't retireve access directly + // Version 1 hashes don't support passwords + if (!priv.readOnly && !priv.oldVersionHash) { + sframeChan.event('EV_SHARE_OPEN', {hidden: true}); // Close share modal + UIElements.displayPasswordPrompt(common, { + fromServerError: true, + loaded: err.loaded, + }); + if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } + (cb || function () {})(); + return; + } + msg = Messages.deletedError; if (err.loaded) { msg += Messages.errorCopy; } + if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } } else if (err.type === 'ERESTRICTED') { msg = Messages.restrictedError; @@ -2561,7 +2577,6 @@ define([ msg = Messages.oo_deletedVersion; if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); } } - var sframeChan = common.getSframeChannel(); sframeChan.event('EV_SHARE_OPEN', {hidden: true}); UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded)); (cb || function () {})(); @@ -2570,7 +2585,10 @@ define([ UIElements.displayPasswordPrompt = function (common, cfg, isError) { var error; if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } + var info = h('p.cp-password-info', Messages.password_info); + var info_loaded = h('p.cp-password-info', Messages.password_info_loaded); + var password = UI.passwordInput({placeholder: Messages.password_placeholder}); var $password = $(password); var button = h('button.btn.btn-primary', Messages.password_submit); @@ -2582,6 +2600,21 @@ define([ var submit = function () { var value = $password.find('.cp-password-input').val(); + + // Password-prompt called from UIElements.onServerError + if (cfg.fromServerError) { + common.getSframeChannel().query('Q_PASSWORD_CHECK', value, function (err, obj) { + if (obj && obj.error) { + console.error(obj.error); + return void UI.warn(Messages.error); + } + // On success, outer will reload the page: this is a wrong password + UIElements.displayPasswordPrompt(common, cfg, true); + }); + return; + } + + // Initial load UI.addLoadingScreen({newProgress: true}); if (window.CryptPad_updateLoadingProgress) { window.CryptPad_updateLoadingProgress({ @@ -2595,6 +2628,8 @@ define([ } }); }; + + $password.find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } }); $(button).on('click', function () { submit(); }); @@ -2602,12 +2637,13 @@ define([ var block = h('div#cp-loading-password-prompt', [ error, info, + cfg.loaded ? info_loaded : undefined, h('p.cp-password-form', [ password, button - ]) + ]), ]); - UI.errorLoadingScreen(block); + UI.errorLoadingScreen(block, Boolean(cfg.loaded), Boolean(cfg.loaded)); $password.find('.cp-password-input').focus(); }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 8bf01c965..1d19089e3 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -481,10 +481,20 @@ define([ }); }; + common.isNewChannel = function (href, password, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + var channel = Hash.hrefToHexChannelId(href, password); + postMessage('IS_NEW_CHANNEL', {channel: channel}, function (obj) { + var error = obj && obj.error; + if (error) { return void cb(error); } + if (!obj) { return void cb('ERROR'); } + cb (null, obj.isNew); + }, {timeout: -1}); + }; // This function is used when we want to open a pad. We first need // to check if it exists. With the cached drive, we need to wait for // the network to be available before we can continue. - common.isNewChannel = function (href, password, _cb) { + common.hasChannelHistory = function (href, password, _cb) { var cb = Util.once(Util.mkAsync(_cb)); var channel = Hash.hrefToHexChannelId(href, password); var error; @@ -2506,6 +2516,7 @@ define([ } if (parsedNew.hashData) { oldHref = newHref; } }; + // XXX if you're in noDrive mode, check if an FS_hash is added and reload if that's the case // Listen for login/logout in other tabs window.addEventListener('storage', function (e) { if (e.key !== Constants.userHashKey) { return; } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 8ac1a77c3..088c5fe29 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1710,6 +1710,10 @@ define([ var onError = function (err) { channel.bcast("PAD_ERROR", err); + if (err && err.type === "EDELETED" && Cache && Cache.clearChannel) { + Cache.clearChannel(data.channel); + } + // If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; } Store.leavePad(null, data, function () {}); @@ -1720,11 +1724,13 @@ define([ postMessage(clientId, "PAD_CACHE"); }, onCacheReady: function () { + channel.hasCache = true; postMessage(clientId, "PAD_CACHE_READY"); }, onReady: function (pad) { var padData = pad.metadata || {}; channel.data = padData; + channel.ready = true; if (padData && padData.validateKey && store.messenger) { store.messenger.storeValidateKey(data.channel, padData.validateKey); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 8d791c8cd..0af8eb9e2 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -78,7 +78,7 @@ define([ }; var AppConfig; var Test; - var password, newPadPassword; + var password, newPadPassword, newPadPasswordForce; var initialPathInDrive; var burnAfterReading; @@ -312,6 +312,7 @@ define([ newPadPassword = Crypto.decrypt(newPad.pw, uKey); } catch (e) { console.error(e); } } + if (newPad.f) { newPadPasswordForce = 1; } if (newPad.d) { Cryptpad.fromFileData = newPad.d; var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href); @@ -319,6 +320,7 @@ define([ delete Cryptpad.fromFileData; } } + } catch (e) { console.error(e, parsed.hashData.newPadOpts); } @@ -349,7 +351,7 @@ define([ } // We now need to check if there is a password and if we know the correct password. - // We'll use getFileSize and isNewChannel to detect incorrect passwords. + // We'll use getFileSize and hasChannelHistory to detect incorrect passwords. // First we'll get the password value from our drive (getPadAttribute), and we'll check // if the channel is valid. If the pad is not stored in our drive, we'll test with an @@ -397,15 +399,15 @@ define([ } }; if (parsed.type === "file") { - // `isNewChannel` doesn't work for files (not a channel) + // `hasChannelHistory` doesn't work for files (not a channel) // `getFileSize` is not adapted to channels because of metadata Cryptpad.getFileSize(currentPad.href, password, function (e, size) { next(e, size === 0); }); return; } - // Not a file, so we can use `isNewChannel` - Cryptpad.isNewChannel(currentPad.href, password, next); + // Not a file, so we can use `hasChannelHistory` + Cryptpad.hasChannelHistory(currentPad.href, password, next); }); sframeChan.event("EV_PAD_PASSWORD", cfg); }; @@ -474,17 +476,25 @@ define([ password = val; }), parsed.getUrl()); }).nThen(function (w) { + // If we've already tested this password and this is a redirect, force + if (typeof(newPadPassword) !== "undefined" && newPadPasswordForce) { + password = newPadPassword; + return void todo(); + } + + // If the pad is not stored and we have a newPadPassword, it probably + // comes from a notification: password prompt pre-filled if (!password && !stored && newPadPassword) { passwordCfg.value = newPadPassword; } // Pad not stored && password required: always ask for the password - if (!stored && parsed.hashData.password) { + if (!stored && parsed.hashData.password && !newPadPasswordForce) { return void askPassword(true, passwordCfg); } if (parsed.type === "file") { - // `isNewChannel` doesn't work for files (not a channel) + // `hasChannelHistory` doesn't work for files (not a channel) // `getFileSize` is not adapted to channels because of metadata Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) { if (size !== 0) { return void todo(); } @@ -493,8 +503,8 @@ define([ })); return; } - // Not a file, so we can use `isNewChannel` - Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) { + // Not a file, so we can use `hasChannelHistory` + Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew) { if (isNew && expire && expire < (+new Date())) { sframeChan.event("EV_EXPIRED_ERROR"); waitFor.abort(); @@ -541,8 +551,7 @@ define([ if (realtime) { // TODO we probably don't need to check again for password-protected pads - // (we use isNewChannel to test the password...) - Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) { + Cryptpad.hasChannelHistory(currentPad.href, password, waitFor(function (e, isNew) { if (e) { return console.error(e); } isNewFile = Boolean(isNew); })); @@ -608,6 +617,7 @@ define([ feedbackAllowed: Utils.Feedback.state, isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, + oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password isHistoryVersion: parsed.hashData && parsed.hashData.versionHash, notifications: notifs, accounts: { @@ -1674,6 +1684,45 @@ define([ }); }); + sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) { + Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) { + if (isNew === false) { + var channel = Hash.hrefToHexChannelId(currentPad.href, pw); + + nThen(function (w) { + // If the pad is stored, update its data + var _secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, pw); + var chan = _secret.channel; + var editH = Utils.Hash.getEditHashFromKeys(_secret); + var viewH = Utils.Hash.getViewHashFromKeys(_secret); + var href = Utils.Hash.hashToHref(editH, parsed.type); + var roHref = Utils.Hash.hashToHref(viewH, parsed.type); + Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl()); + Cryptpad.setPadAttribute('channel', chan, w(), parsed.getUrl()); + Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl()); + Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl()); + }).nThen(function () { + // Get redirect URL + var uHash = Utils.LocalStore.getUserHash(); + var uSecret = Utils.Hash.getSecrets('drive', uHash); + var uKey = uSecret.keys.cryptKey; + var url = Utils.Hash.getNewPadURL(currentPad.href, { + pw: Crypto.encrypt(pw, uKey), + f: 1 + }); + // redirect + window.location.href = url; + document.location.reload(); + }); + + return; + } + cb({ + error: e + }); + }); + }); + if (cfg.messaging) { sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) { Cryptpad.universal.execCommand({ From 815bd5961eea1bf0afa488e5325d59a2817e563f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:00:56 +0100 Subject: [PATCH 25/33] Fix translation key --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4f6c6c8b9..791116247 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2587,7 +2587,7 @@ define([ if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } var info = h('p.cp-password-info', Messages.password_info); - var info_loaded = h('p.cp-password-info', Messages.password_info_loaded); + var info_loaded = h('p.cp-password-info', Messages.errorCopy); var password = UI.passwordInput({placeholder: Messages.password_placeholder}); var $password = $(password); From a0155ad689ba53ddcab8eea9d7beb83c397b665a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:03:41 +0100 Subject: [PATCH 26/33] Fix referenceError introduced in latest commit --- www/common/sframe-common-outer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0af8eb9e2..131958d83 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1687,7 +1687,7 @@ define([ sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) { Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) { if (isNew === false) { - var channel = Hash.hrefToHexChannelId(currentPad.href, pw); + var channel = Utils.Hash.hrefToHexChannelId(currentPad.href, pw); nThen(function (w) { // If the pad is stored, update its data From 8dfebbb38424f9c6b853520ee4e19949d731e99a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:07:58 +0100 Subject: [PATCH 27/33] Fix HTML in translation key --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 791116247..b61cc5f9e 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2587,7 +2587,7 @@ define([ if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } var info = h('p.cp-password-info', Messages.password_info); - var info_loaded = h('p.cp-password-info', Messages.errorCopy); + var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy); var password = UI.passwordInput({placeholder: Messages.password_placeholder}); var $password = $(password); From ba4bef2beb72980c26b74acd15ebf4110ce641f8 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:26:04 +0100 Subject: [PATCH 28/33] Reload when a drive is created in a different worker (driveless mode) --- www/common/cryptpad-common.js | 17 +++++++++++++++++ www/common/outer/async-store.js | 7 +++++++ www/common/outer/store-rpc.js | 1 + 3 files changed, 25 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1d19089e3..7cc19a78c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2518,6 +2518,23 @@ define([ }; // XXX if you're in noDrive mode, check if an FS_hash is added and reload if that's the case // Listen for login/logout in other tabs + if (rdyCfg.noDrive && !localStorage[Constants.fileHashKey]) { + window.addEventListener('storage', function (e) { + if (e.key !== Constants.fileHashKey) { return; } + // New entry added to FS_hash: drive created in another tab, reload + var o = e.oldValue; + var n = e.newValue; + if (!o && n) { + postMessage('HAS_DRIVE', null, function(obj) { + // If we're still in noDrive mode, reload + if (!obj.state) { + LocalStore.loginReload(); + } + // Otherwise this worker is connected, nothing to do + }); + } + }); + } window.addEventListener('storage', function (e) { if (e.key !== Constants.userHashKey) { return; } var o = e.oldValue; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 088c5fe29..83735de7e 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2947,6 +2947,13 @@ define([ */ var initialized = false; + // Are we still in noDrive mode? + Store.hasDrive = function (clientId, data, cb) { + cb({ + state: Boolean(store.proxy) + }); + }; + // If we load CryptPad for the first time from an existing pad, don't create a // drive automatically. var onNoDrive = function (clientId, cb) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index e444f47f5..c4d1d1301 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -15,6 +15,7 @@ define([ MIGRATE_ANON_DRIVE: Store.migrateAnonDrive, PING: function (cId, data, cb) { cb(); }, CACHE_DISABLE: Store.disableCache, + HAS_DRIVE: Store.hasDrive, // RPC UPDATE_PIN_LIMIT: Store.updatePinLimit, GET_PIN_LIMIT: Store.getPinLimit, From fdfd7db3628de5885c9b77cf2fad29f50778d319 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:29:57 +0100 Subject: [PATCH 29/33] Remove autostore modal when the pad has been deleted by another user --- www/common/common-ui-elements.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b61cc5f9e..a02accadf 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2551,6 +2551,10 @@ define([ } else if (err.type === 'EDELETED') { if (priv.burnAfterReading) { return void cb(); } + if (autoStoreModal[priv.channel]) { + autoStoreModal[priv.channel].delete(); + delete autoStoreModal[priv.channel]; + } // View users have the wrong seed, thay can't retireve access directly // Version 1 hashes don't support passwords if (!priv.readOnly && !priv.oldVersionHash) { From 9c8924ee27bdb4fb09343aa5631e478f097f7123 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 18:45:03 +0100 Subject: [PATCH 30/33] Fix change password to a previously used value --- www/common/cryptpad-common.js | 3 ++- www/common/outer/async-store.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 7cc19a78c..d653bd72e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1192,7 +1192,6 @@ define([ } else if (mailbox && typeof(mailbox) === "object") { m = {}; Object.keys(mailbox).forEach(function (ed) { - console.log(mailbox[ed]); try { m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true)); } catch (e) { @@ -1227,6 +1226,7 @@ define([ cryptgetVal = JSON.stringify(parsed); } }), optsGet); + Cache.clearChannel(newSecret.channel, waitFor()); }).nThen(function (waitFor) { optsPut.metadata.restricted = oldMetadata.restricted; optsPut.metadata.allowed = oldMetadata.allowed; @@ -1608,6 +1608,7 @@ define([ } })); })); + Cache.clearChannel(newSecret.channel, waitFor()); }).nThen(function (waitFor) { // The new rt channel is ready // The blob uses its own encryption and doesn't need to be reencrypted diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 83735de7e..f20a545ec 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -452,7 +452,7 @@ define([ account.note = obj.note; cb(obj); }); - }); + }, Cache); }; ////////////////////////////////////////////////////////////////// From d7b4fca703c4393dea3cfa61f38a2e51c06b7e49 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 Mar 2021 19:00:40 +0100 Subject: [PATCH 31/33] Fix allow list and transfer ownership when owned pad not stored --- www/common/inner/access.js | 23 ++++++++++++++++------- www/common/toolbar.js | 7 ++++++- www/secureiframe/inner.js | 3 ++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 47b04a5c4..cb3fc57c9 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -25,10 +25,12 @@ define([ var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); - var channel = data.channel; + var priv = metadataMgr.getPrivateData(); + var channel = data.channel || priv.channel; var owners = data.owners || []; var pending_owners = data.pending_owners || []; var teamOwner = data.teamId; + var title = opts.title; opts = opts || {}; var redrawAll = function () {}; @@ -115,7 +117,7 @@ define([ if (!friend) { return; } common.mailbox.sendTo("RM_OWNER", { channel: channel, - title: data.title, + title: data.title || title, pending: pending }, { channel: friend.notifications, @@ -271,7 +273,7 @@ define([ href: data.href || data.rohref, password: data.password, path: isTemplate ? ['template'] : undefined, - title: data.title || '', + title: data.title || title || "", teamId: obj.id }, waitFor(function (err) { if (err) { return void console.error(err); } @@ -320,6 +322,12 @@ define([ })); } }).nThen(function (waitFor) { + var href = data.href; + var hashes = priv.hashes || {}; + var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash; + if (data.fakeHref) { + href = Hash.hashToHref(bestHash, priv.app); + } sel.forEach(function (el) { var curve = $(el).attr('data-curve'); if (curve === user.curvePublic) { return; } @@ -327,9 +335,9 @@ define([ if (!friend) { return; } common.mailbox.sendTo("ADD_OWNER", { channel: channel, - href: data.href, - password: data.password, - title: data.title + href: href, + password: data.password || priv.password, + title: data.title || title }, { channel: friend.notifications, curvePublic: friend.curvePublic @@ -398,7 +406,8 @@ define([ var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); - var channel = data.channel; + var priv = metadataMgr.getPrivateData(); + var channel = data.channel || priv.channel; var owners = data.owners || []; var restricted = data.restricted || false; var allowed = data.allowed || []; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index aafc356a5..2c208c1c9 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -565,7 +565,12 @@ MessengerUI, Messages) { h('span.cp-button-name', Messages.accessButton) ])); $accessBlock.click(function () { - Common.getSframeChannel().event('EV_ACCESS_OPEN'); + var title = (config.title && config.title.getTitle && config.title.getTitle()) + || (config.title && config.title.defaultName) + || ""; + Common.getSframeChannel().event('EV_ACCESS_OPEN', { + title: title + }); }); toolbar.$bottomM.append($accessBlock); diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index 01635cbaa..1c5af4a57 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -89,9 +89,10 @@ define([ }; // Access modal - create['access'] = function () { + create['access'] = function (data) { require(['/common/inner/access.js'], function (Access) { Access.getAccessModal(common, { + title: data.title, onClose: function () { hideIframe(); } From 1179727d906885ec049e39e18e5b86410645dd24 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 Mar 2021 09:24:07 +0530 Subject: [PATCH 32/33] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 644be1c35..9002860d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ * faster load time, less junk on the server * `AppConfig.allowDrivelessMode` * cursor color is randomly generated each time and doesn't persist after creating a drive + * only affects framework apps for now * secure iframe now always knows the channel of the related document * more consistent API with other APPs * debug app doesn't create a drive From 75f9459950729d04ba8ab5752dceed341226b5dc Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 Mar 2021 09:25:02 +0530 Subject: [PATCH 33/33] leave a FIXME in the server --- lib/hk-util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/hk-util.js b/lib/hk-util.js index da08fcd8b..860435c86 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -677,6 +677,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) { err: err && err.message || err, stack: err && err.stack, }); } + // FIXME err.message isn't useful for users const parsedMsg = {error:err.message, channel: channelName, txid: txid}; Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); return;