Remember password for e2e bootstrapping

Fixes https://github.com/vector-im/riot-web/issues/12046
This commit is contained in:
David Baker 2020-01-25 15:28:06 +00:00
parent 988ae14d35
commit 437b45f8a6
5 changed files with 36 additions and 8 deletions

View file

@ -55,6 +55,7 @@ function selectText(target) {
export default class CreateSecretStorageDialog extends React.PureComponent { export default class CreateSecretStorageDialog extends React.PureComponent {
static propTypes = { static propTypes = {
hasCancel: PropTypes.bool, hasCancel: PropTypes.bool,
accountPassword: PropTypes.string,
}; };
defaultProps = { defaultProps = {
@ -82,7 +83,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
// does the server offer a UI auth flow with just m.login.password // does the server offer a UI auth flow with just m.login.password
// for /keys/device_signing/upload? // for /keys/device_signing/upload?
canUploadKeysWithPasswordOnly: null, canUploadKeysWithPasswordOnly: null,
accountPassword: '', accountPassword: props.accountPassword,
accountPasswordCorrect: null, accountPasswordCorrect: null,
// set if we are 'upgrading' encryption (making an SSSS store from // set if we are 'upgrading' encryption (making an SSSS store from
// an existing key backup secret). // an existing key backup secret).

View file

@ -256,6 +256,9 @@ export default createReactClass({
// logout page. // logout page.
Lifecycle.loadSession({}); Lifecycle.loadSession({});
} }
this._accountPassword = null;
this._accountPasswordTimer = null;
}, },
componentDidMount: function() { componentDidMount: function() {
@ -352,6 +355,8 @@ export default createReactClass({
window.removeEventListener("focus", this.onFocus); window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize); this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer);
}, },
componentWillUpdate: function(props, state) { componentWillUpdate: function(props, state) {
@ -1729,9 +1734,8 @@ export default createReactClass({
this.showScreen("forgot_password"); this.showScreen("forgot_password");
}, },
onRegisterFlowComplete: function(credentials) { onRegisterFlowComplete: function(credentials, password) {
this.onUserCompletedLoginFlow(); return this.onUserCompletedLoginFlow(credentials, password);
return this.onRegistered(credentials);
}, },
// returns a promise which resolves to the new MatrixClient // returns a promise which resolves to the new MatrixClient
@ -1822,7 +1826,14 @@ export default createReactClass({
this._loggedInView = ref; this._loggedInView = ref;
}, },
async onUserCompletedLoginFlow(credentials) { async onUserCompletedLoginFlow(credentials, password) {
this._accountPassword = password;
// self-destruct the password after 5mins
if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer);
this._accountPasswordTimer = setTimeout(() => {
this._accountPassword = null;
this._accountPasswordTimer = null;
}, 60 * 5 * 1000);
// Wait for the client to be logged in (but not started) // Wait for the client to be logged in (but not started)
// which is enough to ask the server about account data. // which is enough to ask the server about account data.
const loggedIn = new Promise(resolve => { const loggedIn = new Promise(resolve => {
@ -1836,7 +1847,7 @@ export default createReactClass({
}); });
// Create and start the client in the background // Create and start the client in the background
Lifecycle.setLoggedIn(credentials); const setLoggedInPromise = Lifecycle.setLoggedIn(credentials);
await loggedIn; await loggedIn;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -1865,6 +1876,8 @@ export default createReactClass({
} else { } else {
this._onLoggedIn(); this._onLoggedIn();
} }
return setLoggedInPromise;
}, },
// complete security / e2e setup has finished // complete security / e2e setup has finished
@ -1896,6 +1909,7 @@ export default createReactClass({
view = ( view = (
<E2eSetup <E2eSetup
onFinished={this.onCompleteSecurityE2eSetupFinished} onFinished={this.onCompleteSecurityE2eSetupFinished}
accountPassword={this._accountPassword}
/> />
); );
} else if (this.state.view === VIEWS.POST_REGISTRATION) { } else if (this.state.view === VIEWS.POST_REGISTRATION) {

View file

@ -22,6 +22,7 @@ import * as sdk from '../../../index';
export default class E2eSetup extends React.Component { export default class E2eSetup extends React.Component {
static propTypes = { static propTypes = {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
accountPassword: PropTypes.string,
}; };
constructor() { constructor() {
@ -40,6 +41,7 @@ export default class E2eSetup extends React.Component {
<AsyncWrapper prom={this._createStorageDialogPromise} <AsyncWrapper prom={this._createStorageDialogPromise}
hasCancel={false} hasCancel={false}
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
accountPassword={this.props.accountPassword}
/> />
</AuthBody> </AuthBody>
</AuthPage> </AuthPage>

View file

@ -58,6 +58,11 @@ export default createReactClass({
displayName: 'Login', displayName: 'Login',
propTypes: { propTypes: {
// Called when the user has logged in. Params:
// - The object returned by the login API
// - The user's password, if applicable, (may be cached in memory for a
// short time so the user is not required to re-enter their password
// for operations like uploading cross-signing keys).
onLoggedIn: PropTypes.func.isRequired, onLoggedIn: PropTypes.func.isRequired,
// If true, the component will consider itself busy. // If true, the component will consider itself busy.
@ -181,7 +186,7 @@ export default createReactClass({
username, phoneCountry, phoneNumber, password, username, phoneCountry, phoneNumber, password,
).then((data) => { ).then((data) => {
this.setState({serverIsAlive: true}); // it must be, we logged in. this.setState({serverIsAlive: true}); // it must be, we logged in.
this.props.onLoggedIn(data); this.props.onLoggedIn(data, password);
}, (error) => { }, (error) => {
if (this._unmounted) { if (this._unmounted) {
return; return;

View file

@ -45,7 +45,13 @@ export default createReactClass({
displayName: 'Registration', displayName: 'Registration',
propTypes: { propTypes: {
// Called when the user has logged in. Params:
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
// - The user's password, if available and applicable (may be cached in memory
// for a short time so the user is not required to re-enter their password
// for operations like uploading cross-signing keys).
onLoggedIn: PropTypes.func.isRequired, onLoggedIn: PropTypes.func.isRequired,
clientSecret: PropTypes.string, clientSecret: PropTypes.string,
sessionId: PropTypes.string, sessionId: PropTypes.string,
makeRegistrationUrl: PropTypes.func.isRequired, makeRegistrationUrl: PropTypes.func.isRequired,
@ -348,7 +354,7 @@ export default createReactClass({
homeserverUrl: this.state.matrixClient.getHomeserverUrl(), homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(), identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: response.access_token, accessToken: response.access_token,
}); }, this.state.formVals.password);
this._setupPushers(cli); this._setupPushers(cli);
// we're still busy until we get unmounted: don't show the registration form again // we're still busy until we get unmounted: don't show the registration form again