From d43cb509dc3e687a55f7e3f9f40d9bf49840eb00 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 2 Mar 2021 16:05:18 +0100 Subject: [PATCH 1/6] Checkup: test sandbox domain and login block --- customize.dist/login.js | 5 +- .../src/less2/pages/page-checkup.less | 4 + www/checkup/index.html | 1 + www/checkup/inner.html | 11 ++ www/checkup/inner.js | 5 + www/checkup/main.js | 122 +++++++++++++++++- 6 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 www/checkup/inner.html create mode 100644 www/checkup/inner.js diff --git a/customize.dist/login.js b/customize.dist/login.js index 0e932c4f6..c0daaec91 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -22,6 +22,7 @@ define([ Feedback, LocalStore, Messages, nThen, Block, Hash) { var Exports = { Cred: Cred, + Block: Block, // this is depended on by non-customizable files // be careful when modifying login.js requiredBytes: 192, @@ -92,7 +93,7 @@ define([ }; - var loginOptionsFromBlock = function (blockInfo) { + var loginOptionsFromBlock = Exports.loginOptionsFromBlock = function (blockInfo) { var opt = {}; var parsed = Hash.getSecrets('pad', blockInfo.User_hash); opt.channelHex = parsed.channel; @@ -102,7 +103,7 @@ define([ return opt; }; - var loadUserObject = function (opt, cb) { + var loadUserObject = Exports.loadUserObject = function (opt, cb) { var config = { websocketURL: NetConfig.getWebsocketURL(), channel: opt.channelHex, diff --git a/customize.dist/src/less2/pages/page-checkup.less b/customize.dist/src/less2/pages/page-checkup.less index a4ece61a4..2839ec9ea 100644 --- a/customize.dist/src/less2/pages/page-checkup.less +++ b/customize.dist/src/less2/pages/page-checkup.less @@ -53,5 +53,9 @@ html, body { background-color: @cp_alerts-danger-bg; color: @cp_alerts-danger-text; } + + iframe { + display: none; + } } diff --git a/www/checkup/index.html b/www/checkup/index.html index afa469f7f..164793466 100644 --- a/www/checkup/index.html +++ b/www/checkup/index.html @@ -6,4 +6,5 @@ + diff --git a/www/checkup/inner.html b/www/checkup/inner.html new file mode 100644 index 000000000..4554943d5 --- /dev/null +++ b/www/checkup/inner.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/www/checkup/inner.js b/www/checkup/inner.js new file mode 100644 index 000000000..690bfde24 --- /dev/null +++ b/www/checkup/inner.js @@ -0,0 +1,5 @@ +define([ + 'jquery', +], function ($) { + console.log('inner loaded'); +}); diff --git a/www/checkup/main.js b/www/checkup/main.js index 171699a22..3caed1a84 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -4,12 +4,19 @@ define([ '/assert/assertions.js', '/common/hyperscript.js', '/customize/messages.js', + '/common/dom-ready.js', + '/bower_components/nthen/index.js', '/common/sframe-common-outer.js', + '/customize/login.js', + '/common/common-hash.js', + '/common/common-util.js', + '/common/pinpad.js', '/bower_components/tweetnacl/nacl-fast.min.js', 'less!/customize/src/less2/pages/page-checkup.less', -], function ($, ApiConfig, Assertions, h, Messages /*, SFCommonO*/) { +], function ($, ApiConfig, Assertions, h, Messages, DomReady, + nThen, SFCommonO, Login, Hash, Util, Pinpad) { var assert = Assertions(); var trimSlashes = function (s) { @@ -41,7 +48,7 @@ define([ var checkAvailability = function (url, cb) { $.ajax({ url: url, - date: {}, + data: {}, complete: function (xhr) { cb(xhr.status === 200); }, @@ -52,10 +59,115 @@ define([ checkAvailability(trimmedUnsafe, cb); }, _alert("Main domain is not available")); + // Try loading an iframe on the safe domain assert(function (cb) { - console.log(trimmedSafe); - checkAvailability(trimmedSafe, cb); - }, _alert("Sandbox domain is not available")); // FIXME Blocked by CSP. try loading it via sframe ? + var to; + nThen(function (waitFor) { + DomReady.onReady(waitFor()); + }).nThen(function (waitFor) { + to = setTimeout(function () { + console.error('TIMEOUT loading iframe on the safe domain') + cb(false); + }, 5000); + SFCommonO.initIframe(waitFor); + }).nThen(function () { + // Iframe is loaded + clearTimeout(to); + cb(true); + }); + }, _alert("Sandbox domain is not available")); + + // Write/ready access to /block/ + assert(function (cb) { + var bytes = new Uint8Array(Login.requiredBytes); + + var opt = Login.allocateBytes(bytes); + + var blockUrl = Login.Block.getBlockUrl(opt.blockKeys); + var blockRequest = Login.Block.serialize("{}", opt.blockKeys); + var removeRequest = Login.Block.remove(opt.blockKeys); + console.log('Test block URL:', blockUrl); + + var userHash = '/2/drive/edit/000000000000000000000000'; + var secret = Hash.getSecrets('drive', userHash); + opt.keys = secret.keys; + opt.channelHex = secret.channel; + + var RT, rpc, exists; + + nThen(function (waitFor) { + Util.fetch(blockUrl, waitFor(function (err, block) { + if (err) { return; } // No block found + exists = true; + })); + }).nThen(function (waitFor) { + // Create proxy + Login.loadUserObject(opt, waitFor(function (err, rt) { + if (err) { + waitFor.abort(); + console.error("Can't create new channel. This may also be a websocket issue."); + return void cb(false); + } + RT = rt; + var proxy = rt.proxy; + proxy.edPublic = opt.edPublic; + proxy.edPrivate = opt.edPrivate; + proxy.curvePublic = opt.curvePublic; + proxy.curvePrivate = opt.curvePrivate; + rt.realtime.onSettle(waitFor()); + })); + }).nThen(function (waitFor) { + // Init RPC + Pinpad.create(RT.network, RT.proxy, waitFor(function (e, _rpc) { + if (e) { + waitFor.abort(); + console.error("Can't initialize RPC", e); // INVALID_KEYS + return void cb(false); + } + rpc = _rpc; + })); + }).nThen(function (waitFor) { + // Write block + if (exists) { return; } + rpc.writeLoginBlock(blockRequest, waitFor(function (e) { + if (e) { + waitFor.abort(); + console.error("Can't write login block", e); + return void cb(false); + } + })); + }).nThen(function (waitFor) { + // Read block + Util.fetch(blockUrl, waitFor(function (e, block) { + if (e) { + waitFor.abort(); + console.error("Can't read login block", e); + return void cb(false); + } + })); + }).nThen(function (waitFor) { + // Remove block + rpc.removeLoginBlock(removeRequest, waitFor(function (e) { + if (e) { + waitFor.abort(); + console.error("Can't remove login block", e); + console.error(blockRequest); + return void cb(false); + } + })); + }).nThen(function (waitFor) { + rpc.removeOwnedChannel(secret.channel, waitFor(function (e) { + if (e) { + waitFor.abort(); + console.error("Can't remove channel", e); + return void cb(false); + } + })); + }).nThen(function () { + cb(true); + }); + + }, _alert("Login Block is not working (write/read/remove)")); var row = function (cells) { return h('tr', cells.map(function (cell) { From b0e0a8dc75385015788422c76a23594ebfe96c0e Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 2 Mar 2021 16:45:52 +0100 Subject: [PATCH 2/6] Add spinner to the checkup page and test websockets --- .../src/less2/pages/page-checkup.less | 6 +++ www/assert/assertions.js | 5 +- www/checkup/index.html | 1 + www/checkup/main.js | 50 +++++++++++++++++-- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/customize.dist/src/less2/pages/page-checkup.less b/customize.dist/src/less2/pages/page-checkup.less index 2839ec9ea..2a05600c7 100644 --- a/customize.dist/src/less2/pages/page-checkup.less +++ b/customize.dist/src/less2/pages/page-checkup.less @@ -20,6 +20,12 @@ html, body { padding-top: 15px; } + .pending { + border: 1px solid white; + .fa { + margin-right: 20px; + } + } .success { border: 1px solid green; } diff --git a/www/assert/assertions.js b/www/assert/assertions.js index 8ea0a2638..a1f6944fb 100644 --- a/www/assert/assertions.js +++ b/www/assert/assertions.js @@ -21,8 +21,10 @@ define([], function () { }); }; - assert.run = function (cb) { + assert.run = function (cb, progress) { + progress = progress || function () {}; var count = ASSERTS.length; + var total = ASSERTS.length; var done = function (err) { count--; if (err) { failMessages.push(err); } @@ -38,6 +40,7 @@ define([], function () { ASSERTS.forEach(function (f, index) { f(function (err) { //console.log("test " + index); + progress(index, total); done(err, index); }, index); }); diff --git a/www/checkup/index.html b/www/checkup/index.html index 164793466..04a9502d3 100644 --- a/www/checkup/index.html +++ b/www/checkup/index.html @@ -6,5 +6,6 @@ +
diff --git a/www/checkup/main.js b/www/checkup/main.js index 3caed1a84..5a6630c6f 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -11,12 +11,15 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/pinpad.js', - + '/common/outer/network-config.js', + '/bower_components/netflux-websocket/netflux-client.js', '/bower_components/tweetnacl/nacl-fast.min.js', + 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/customize/src/less2/pages/page-checkup.less', ], function ($, ApiConfig, Assertions, h, Messages, DomReady, - nThen, SFCommonO, Login, Hash, Util, Pinpad) { + nThen, SFCommonO, Login, Hash, Util, Pinpad, + NetConfig, Netflux) { var assert = Assertions(); var trimSlashes = function (s) { @@ -77,7 +80,28 @@ define([ }); }, _alert("Sandbox domain is not available")); - // Write/ready access to /block/ + // Test Websocket + var evWSError = Util.mkEvent(true); + assert(function (cb) { + var ws = new WebSocket(NetConfig.getWebsocketURL()); + var to = setTimeout(function () { + console.error('Websocket TIMEOUT'); + evWSError.fire(); + cb('TIMEOUT (5 seconds)'); + }, 5000); + ws.onopen = function () { + clearTimeout(to); + cb(true); + }; + ws.onerror = function (err) { + clearTimeout(to); + console.error('Websocket error', err); + evWSError.fire(); + cb('WebSocket error: check your console'); + }; + }, _alert("Websocket is not available")); + + // Test login block assert(function (cb) { var bytes = new Uint8Array(Login.requiredBytes); @@ -101,6 +125,11 @@ define([ exists = true; })); }).nThen(function (waitFor) { + // If WebSockets aren't working, don't wait forever here + evWSError.reg(function () { + waitFor.abort(); + cb("No WebSocket (test number 6)"); + }); // Create proxy Login.loadUserObject(opt, waitFor(function (err, rt) { if (err) { @@ -167,7 +196,7 @@ define([ cb(true); }); - }, _alert("Login Block is not working (write/read/remove)")); + }, _alert("Login block is not working (write/read/remove)")); var row = function (cells) { return h('tr', cells.map(function (cell) { @@ -185,6 +214,8 @@ define([ ]); }; + var completed = 0; + var $progress = $('#cp-progress'); assert.run(function (state) { var errors = state.errors; var failed = errors.length; @@ -206,6 +237,17 @@ define([ h('div.failures', errors.map(failureReport)), ]); + $progress.remove(); $('body').prepend(report); + }, function (i, total) { + console.log('test '+ i +' completed'); + completed++; + Messages.assert_numberOfTestsCompleted = "{0} / {1} tests completed."; + $progress.html('').append(h('div.report.pending.summary', [ + h('p', [ + h('i.fa.fa-spinner.fa-pulse'), + h('span', Messages._getKey('assert_numberOfTestsCompleted', [completed, total])) + ]) + ])); }); }); From eb7f7aaa89c9c27f6d1750bec1dd999f534d14b4 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 2 Mar 2021 17:47:05 +0100 Subject: [PATCH 3/6] Delete block when deleting account --- www/common/cryptpad-common.js | 119 +++++++++++++++++++++----------- www/common/outer/async-store.js | 6 +- www/settings/app-settings.less | 2 +- www/settings/inner.js | 103 +++++++++++++-------------- www/settings/main.js | 2 +- 5 files changed, 137 insertions(+), 95 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e33c5f429..665464e16 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -232,17 +232,6 @@ define([ }; postMessage("MIGRATE_ANON_DRIVE", data, cb); }; - // Settings - common.deleteAccount = function (cb) { - postMessage("DELETE_ACCOUNT", null, function (obj) { - if (obj.state) { - Feedback.send('DELETE_ACCOUNT_AUTOMATIC'); - } else { - Feedback.send('DELETE_ACCOUNT_MANUAL'); - } - cb(obj); - }); - }; // Drive common.userObjectCommand = function (data, cb) { postMessage("DRIVE_USEROBJECT", data, cb); @@ -1674,30 +1663,16 @@ define([ }; - common.changeUserPassword = function (Crypt, edPublic, data, cb) { - if (!edPublic) { - return void cb({ - error: 'E_NOT_LOGGED_IN' - }); - } + var getBlockKeys = function (data, cb) { var accountName = LocalStore.getAccountName(); - var hash = LocalStore.getUserHash(); - if (!hash) { - return void cb({ - error: 'E_NOT_LOGGED_IN' - }); - } - - var password = data.password; // To remove your old block - var newPassword = data.newPassword; // To create your new block - var secret = Hash.getSecrets('drive', hash); - var newHash, newHref, newSecret, blockKeys; - var oldIsOwned = false; + var password = data.password; + var Cred, Block, Login; + var blockKeys; + var hash = LocalStore.getUserHash(); + if (!hash) { return void cb({ error: 'E_NOT_LOGGED_IN' }); } var blockHash = LocalStore.getBlockHash(); - var oldBlockKeys; - var Cred, Block, Login; Nthen(function (waitFor) { require([ '/common/common-credential.js', @@ -1710,30 +1685,92 @@ define([ })); }).nThen(function (waitFor) { // confirm that the provided password is correct - Cred.deriveFromPassphrase(accountName, password, Login.requiredBytes, waitFor(function (bytes) { + Cred.deriveFromPassphrase(accountName, password, Login.requiredBytes, + waitFor(function (bytes) { var allocated = Login.allocateBytes(bytes); - oldBlockKeys = allocated.blockKeys; + blockKeys = allocated.blockKeys; if (blockHash) { if (blockHash !== allocated.blockHash) { + // incorrect password console.log("provided password did not yield the correct blockHash"); - // incorrect password probably waitFor.abort(); - return void cb({ - error: 'INVALID_PASSWORD', - }); + return void cb({ error: 'INVALID_PASSWORD', }); } - // the user has already created a block, so you should compare against that } else { // otherwise they're a legacy user, and we should check against the User_hash if (hash !== allocated.userHash) { + // incorrect password console.log("provided password did not yield the correct userHash"); waitFor.abort(); - return void cb({ - error: 'INVALID_PASSWORD', - }); + return void cb({ error: 'INVALID_PASSWORD', }); } } })); + }).nThen(function () { + cb({ + Cred: Cred, + Block: Block, + Login: Login, + blockKeys: blockKeys + }); + }); + }; + common.deleteAccount = function (data, cb) { + data = data || {}; + + // Confirm that the provided password is corrct and get the block keys + getBlockKeys(data, function (obj) { + if (obj && obj.error) { return void cb(obj); } + var blockKeys = obj.blockKeys; + var removeData = obj.Block.remove(blockKeys); + + postMessage("DELETE_ACCOUNT", { + removeData: removeData + }, function (obj) { + if (obj.state) { + Feedback.send('DELETE_ACCOUNT_AUTOMATIC'); + } else { + Feedback.send('DELETE_ACCOUNT_MANUAL'); + } + cb(obj); + }); + }); + }; + common.changeUserPassword = function (Crypt, edPublic, data, cb) { + if (!edPublic) { + return void cb({ + error: 'E_NOT_LOGGED_IN' + }); + } + var accountName = LocalStore.getAccountName(); + var hash = LocalStore.getUserHash(); + if (!hash) { + return void cb({ + error: 'E_NOT_LOGGED_IN' + }); + } + + var password = data.password; // To remove your old block + var newPassword = data.newPassword; // To create your new block + var secret = Hash.getSecrets('drive', hash); + var newHash, newHref, newSecret, blockKeys; + var oldIsOwned = false; + + var blockHash = LocalStore.getBlockHash(); + var oldBlockKeys; + + var Cred, Block, Login; + Nthen(function (waitFor) { + getBlockKeys(data, waitFor(function (obj) { + if (obj && obj.error) { + waitFor.abort(); + return void cb(obj); + } + oldBlockKeys = obj.blockKeys; + Cred = obj.Cred; + Login = obj.Login; + Block = obj.Block; + })); }).nThen(function (waitFor) { // Check if our drive is already owned console.log("checking if old drive is owned"); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 63a7fc352..553a72225 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -741,6 +741,7 @@ define([ Store.deleteAccount = function (clientId, data, cb) { var edPublic = store.proxy.edPublic; + var removeData = data && data.removeData; Store.anonRpcMsg(clientId, { msg: 'GET_METADATA', data: store.driveChannel @@ -769,8 +770,11 @@ define([ channel: store.driveChannel, force: true }, waitFor()); + }).nThen(function (waitFor) { + if (!removeData) { return; } + // Delete the block. Don't abort if it fails, it doesn't leak any data. + store.rpc.removeLoginBlock(removeData, waitFor()); }).nThen(function () { - // TODO delete block // Log out current worker postMessage(clientId, "DELETE_ACCOUNT", token, function () {}); store.network.disconnect(); diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less index a789ad3e4..3d4577e99 100644 --- a/www/settings/app-settings.less +++ b/www/settings/app-settings.less @@ -85,7 +85,7 @@ } } - .cp-settings-change-password, .cp-settings-own-drive { + .cp-settings-change-password, .cp-settings-own-drive, .cp-settings-delete { [type="password"], [type="text"] { width: @sidebar_button-width; flex: unset; diff --git a/www/settings/inner.js b/www/settings/inner.js index 8feef06e4..0139ab2b4 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -469,63 +469,64 @@ define([ }); }, true); - create['delete'] = function() { - if (!common.isLoggedIn()) { return; } - var $div = $('
', { 'class': 'cp-settings-delete cp-sidebarlayout-element' }); - - $('', { 'class': 'label' }).text(Messages.settings_deleteTitle).appendTo($div); - - $('', { 'class': 'cp-sidebarlayout-description' }) - .append(Messages.settings_deleteHint).appendTo($div); - - var $ok = $('', { 'class': 'fa fa-check', title: Messages.saved }); - var $spinner = $('', { 'class': 'fa fa-spinner fa-pulse' }); + makeBlock('delete', function(cb) { // Msg.settings_deleteHint, .settings_deleteTitle + if (!common.isLoggedIn()) { return cb(false); } - var $button = $('