From 328f0cd6bf5695f7dbf8be508f8bc655b31d6ab9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 28 Mar 2019 16:22:17 +0000 Subject: [PATCH] Notify user when crypto data is missing If we have account data in local storage but nothing in the crypto store, it generally means the browser has evicted IndexedDB out from under us. This adds a modal to explain the situation and offer to completely clear storage to get things back to normal. Fixes https://github.com/vector-im/riot-web/issues/9109 --- .babelrc | 21 ++++- package.json | 1 + src/Lifecycle.js | 38 ++++++++- .../dialogs/SessionRestoreErrorDialog.js | 2 +- .../views/dialogs/StorageEvictedDialog.js | 77 +++++++++++++++++++ src/i18n/strings/en_EN.json | 6 +- src/utils/StorageManager.js | 6 ++ yarn.lock | 12 ++- 8 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 src/components/views/dialogs/StorageEvictedDialog.js diff --git a/.babelrc b/.babelrc index fc5bd1788f..3fb847ad18 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,21 @@ { - "presets": ["react", "es2015", "es2016"], - "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports", "syntax-dynamic-import"] + "presets": [ + "react", + "es2015", + "es2016" + ], + "plugins": [ + [ + "transform-builtin-extend", + { + "globals": ["Error"] + } + ], + "transform-class-properties", + "transform-object-rest-spread", + "transform-async-to-bluebird", + "transform-runtime", + "add-module-exports", + "syntax-dynamic-import" + ] } diff --git a/package.json b/package.json index 8997d3c2af..91a349cda9 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "babel-loader": "^7.1.5", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-async-to-bluebird": "^1.1.1", + "babel-plugin-transform-builtin-extend": "^1.1.2", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-runtime": "^6.23.0", diff --git a/src/Lifecycle.js b/src/Lifecycle.js index fbb68481ad..f65dffa006 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -103,9 +103,14 @@ export async function loadSession(opts) { return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName); } - // fall back to login screen + // fall back to welcome screen return false; } catch (e) { + if (e instanceof AbortLoginAndRebuildStorage) { + // If we're aborting login because of a storage inconsistency, we don't + // need to show the general failure dialog. Instead, just go back to welcome. + return false; + } return _handleLoadSessionFailure(e); } } @@ -279,7 +284,7 @@ async function _restoreFromLocalStorage() { } function _handleLoadSessionFailure(e) { - console.log("Unable to load session", e); + console.error("Unable to load session", e); const def = Promise.defer(); const SessionRestoreErrorDialog = @@ -354,7 +359,21 @@ async function _doSetLoggedIn(credentials, clearStorage) { await _clearStorage(); } - await StorageManager.checkConsistency(); + const results = await StorageManager.checkConsistency(); + // If there's an inconsistency between account data in local storage and the + // crypto store, we'll be generally confused when handling encrypted data. + // Show a modal recommending a full reset of storage. + if (results.dataInLocalStorage && !results.dataInCryptoStore) { + const signOut = await _showStorageEvictedDialog(); + if (signOut) { + await _clearStorage(); + // This error feels a bit clunky, but we want to make sure we don't go any + // further and instead head back to sign in. + throw new AbortLoginAndRebuildStorage( + "Aborting login in progress because of storage inconsistency", + ); + } + } Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl); @@ -386,6 +405,19 @@ async function _doSetLoggedIn(credentials, clearStorage) { return MatrixClientPeg.get(); } +function _showStorageEvictedDialog() { + const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog'); + return new Promise(resolve => { + Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, { + onFinished: resolve, + }); + }); +} + +// Note: Babel 6 requires the `transform-builtin-extend` plugin for this to satisfy +// `instanceof`. Babel 7 supports this natively in their class handling. +class AbortLoginAndRebuildStorage extends Error { } + function _persistCredentialsToLocalStorage(credentials) { localStorage.setItem("mx_hs_url", credentials.homeserverUrl); localStorage.setItem("mx_is_url", credentials.identityServerUrl); diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.js b/src/components/views/dialogs/SessionRestoreErrorDialog.js index 60430b995e..f7e117b31b 100644 --- a/src/components/views/dialogs/SessionRestoreErrorDialog.js +++ b/src/components/views/dialogs/SessionRestoreErrorDialog.js @@ -41,7 +41,7 @@ export default React.createClass({ Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, { title: _t("Sign out"), description: -
{ _t("Log out and remove encryption keys?") }
, +
{ _t("Sign out and remove encryption keys?") }
, button: _t("Sign out"), danger: true, onFinished: this.props.onFinished, diff --git a/src/components/views/dialogs/StorageEvictedDialog.js b/src/components/views/dialogs/StorageEvictedDialog.js new file mode 100644 index 0000000000..22fa3ae3ab --- /dev/null +++ b/src/components/views/dialogs/StorageEvictedDialog.js @@ -0,0 +1,77 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import SdkConfig from '../../../SdkConfig'; +import Modal from '../../../Modal'; +import { _t } from '../../../languageHandler'; + +export default class StorageEvictedDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + _sendBugReport = ev => { + ev.preventDefault(); + const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); + Modal.createTrackedDialog('Storage evicted', 'Send Bug Report Dialog', BugReportDialog, {}); + }; + + _onSignOutClick = () => { + this.props.onFinished(true); + }; + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + let logRequest; + if (SdkConfig.get().bug_report_endpoint_url) { + logRequest = _t( + "To help us prevent this in future, please send us logs.", {}, + { + a: text => {text}, + }); + } + + return ( + +
+

{_t( + "Some session data, including encrypted message keys, is " + + "missing. Sign out and sign in to fix this, restoring keys " + + "from backup.", + )}

+

{_t( + "Your browser likely removed this data when running low on " + + "disk space.", + )} {logRequest}

+
+ +
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3b24f28c2c..98301c5f76 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1141,7 +1141,7 @@ "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages", - "Log out and remove encryption keys?": "Log out and remove encryption keys?", + "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", "Clear Storage and Sign Out": "Clear Storage and Sign Out", "Send Logs": "Send Logs", "Refresh": "Refresh", @@ -1177,6 +1177,10 @@ "Share Room Message": "Share Room Message", "Link to selected message": "Link to selected message", "COPY": "COPY", + "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", + "Missing session data": "Missing session data", + "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", + "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", "Error showing you your room": "Error showing you your room", "Riot has run into a problem which makes it difficult to show you your messages right now. Nothing has been lost and reloading the app should fix this for you. In order to assist us in troubleshooting the problem, we'd like to take a look at your debug logs. You do not need to send your logs unless you want to, but we would really appreciate it if you did. We'd also like to apologize for having to show this message to you - we hope your debug logs are the key to solving the issue once and for all. If you'd like more information on the bug you've accidentally run into, please visit the issue.": "Riot has run into a problem which makes it difficult to show you your messages right now. Nothing has been lost and reloading the app should fix this for you. In order to assist us in troubleshooting the problem, we'd like to take a look at your debug logs. You do not need to send your logs unless you want to, but we would really appreciate it if you did. We'd also like to apologize for having to show this message to you - we hope your debug logs are the key to solving the issue once and for all. If you'd like more information on the bug you've accidentally run into, please visit the issue.", "Send debug logs and reload Riot": "Send debug logs and reload Riot", diff --git a/src/utils/StorageManager.js b/src/utils/StorageManager.js index 472e1c93d4..6184f7fde9 100644 --- a/src/utils/StorageManager.js +++ b/src/utils/StorageManager.js @@ -110,4 +110,10 @@ export async function checkConsistency() { error("Storage consistency checks failed"); track("Consistency checks failed"); } + + return { + dataInLocalStorage, + dataInCryptoStore, + healthy, + }; } diff --git a/yarn.lock b/yarn.lock index 893ceac0a2..470e80f85a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -902,6 +902,14 @@ babel-plugin-transform-async-to-generator@^6.24.1: babel-plugin-syntax-async-functions "^6.8.0" babel-runtime "^6.22.0" +babel-plugin-transform-builtin-extend@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz#5e96fecf58b8fa1ed74efcad88475b2af3c9116e" + integrity sha1-Xpb+z1i4+h7XTvytiEdbKvPJEW4= + dependencies: + babel-runtime "^6.2.0" + babel-template "^6.3.0" + babel-plugin-transform-class-properties@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" @@ -1267,7 +1275,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -1275,7 +1283,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0: +babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0, babel-template@^6.9.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=