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
pull/21833/head
J. Ryan Stinnett 2019-03-28 16:22:17 +00:00
parent 1ae429f57b
commit 328f0cd6bf
8 changed files with 154 additions and 9 deletions

View File

@ -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"
]
}

View File

@ -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",

View File

@ -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);

View File

@ -41,7 +41,7 @@ export default React.createClass({
Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, {
title: _t("Sign out"),
description:
<div>{ _t("Log out and remove encryption keys?") }</div>,
<div>{ _t("Sign out and remove encryption keys?") }</div>,
button: _t("Sign out"),
danger: true,
onFinished: this.props.onFinished,

View File

@ -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 <a>send us logs</a>.", {},
{
a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>,
});
}
return (
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
title={_t('Missing session data')}
contentId='mx_Dialog_content'
hasCancel={false}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{_t(
"Some session data, including encrypted message keys, is " +
"missing. Sign out and sign in to fix this, restoring keys " +
"from backup.",
)}</p>
<p>{_t(
"Your browser likely removed this data when running low on " +
"disk space.",
)} {logRequest}</p>
</div>
<DialogButtons primaryButton={_t("Sign out")}
onPrimaryButtonClick={this._onSignOutClick}
focus={true}
hasCancel={false}
/>
</BaseDialog>
);
}
}

View File

@ -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 <a>send us logs</a>.": "To help us prevent this in future, please <a>send us logs</a>.",
"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 <a>the issue</a>.": "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 <a>the issue</a>.",
"Send debug logs and reload Riot": "Send debug logs and reload Riot",

View File

@ -110,4 +110,10 @@ export async function checkConsistency() {
error("Storage consistency checks failed");
track("Consistency checks failed");
}
return {
dataInLocalStorage,
dataInCryptoStore,
healthy,
};
}

View File

@ -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=