UI to bootsrap SSSS from key backup
This commit is contained in:
parent
8b2adf313f
commit
d211372740
3 changed files with 123 additions and 30 deletions
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -70,9 +70,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
setPassPhrase: false,
|
||||
backupInfo: null,
|
||||
backupSigStatus: null,
|
||||
// does the server offer a UI auth flow with just m.login.password
|
||||
// for /keys/device_signing/upload?
|
||||
canUploadKeysWithPasswordOnly: null,
|
||||
accountPassword: '',
|
||||
accountPasswordCorrect: null,
|
||||
};
|
||||
|
||||
this._fetchBackupInfo();
|
||||
this._queryKeyUploadAuth();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -96,11 +102,32 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
async _queryKeyUploadAuth() {
|
||||
try {
|
||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
||||
// We should never get here: the server should always require
|
||||
// UI auth to upload device signing keys. If we do, we upload
|
||||
// no keys which would be a no-op.
|
||||
console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
|
||||
} catch (error) {
|
||||
if (!error.data.flows) {
|
||||
console.log("uploadDeviceSigningKeys advertised no flows!");
|
||||
}
|
||||
const canUploadKeysWithPasswordOnly = error.data.flows.some(f => {
|
||||
return f.stages.length === 1 && f.stages[0] === 'm.login.password';
|
||||
});
|
||||
this.setState({
|
||||
canUploadKeysWithPasswordOnly,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_collectRecoveryKeyNode = (n) => {
|
||||
this._recoveryKeyNode = n;
|
||||
}
|
||||
|
||||
_onMigrateNextClick = () => {
|
||||
_onMigrateFormSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this._bootstrapSecretStorage();
|
||||
}
|
||||
|
||||
|
@ -127,16 +154,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_bootstrapSecretStorage = async () => {
|
||||
this.setState({
|
||||
phase: PHASE_STORING,
|
||||
error: null,
|
||||
_doBootstrapUIAuth = async (makeRequest) => {
|
||||
if (this.state.canUploadKeysWithPasswordOnly) {
|
||||
await makeRequest({
|
||||
type: 'm.login.password',
|
||||
identifier: {
|
||||
type: 'm.id.user',
|
||||
user: MatrixClientPeg.get().getUserId(),
|
||||
},
|
||||
// https://github.com/matrix-org/synapse/issues/5665
|
||||
user: MatrixClientPeg.get().getUserId(),
|
||||
password: this.state.accountPassword,
|
||||
});
|
||||
const cli = MatrixClientPeg.get();
|
||||
try {
|
||||
} else {
|
||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
{
|
||||
|
@ -149,7 +180,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_bootstrapSecretStorage = async () => {
|
||||
this.setState({
|
||||
phase: PHASE_STORING,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
try {
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
createSecretStorageKey: async () => this._keyInfo,
|
||||
keyBackupInfo: this.state.backupInfo,
|
||||
});
|
||||
|
@ -157,7 +201,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
phase: PHASE_DONE,
|
||||
});
|
||||
} catch (e) {
|
||||
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
|
||||
this.setState({
|
||||
accountPasswordCorrect: false,
|
||||
phase: PHASE_MIGRATE,
|
||||
});
|
||||
} else {
|
||||
this.setState({ error: e });
|
||||
}
|
||||
console.error("Error bootstrapping secret storage", e);
|
||||
}
|
||||
}
|
||||
|
@ -285,6 +336,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
||||
}
|
||||
|
||||
_onAccountPasswordChange = (e) => {
|
||||
this.setState({
|
||||
accountPassword: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
_renderPhaseRestoreKeyBackup() {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
|
@ -309,18 +366,41 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
// it automatically.
|
||||
// https://github.com/vector-im/riot-web/issues/11696
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
const Field = sdk.getComponent('views.elements.Field');
|
||||
|
||||
let authPrompt;
|
||||
if (this.state.canUploadKeysWithPasswordOnly) {
|
||||
authPrompt = <div>
|
||||
<div>{_t("Enter your account password to confirm the upgrade:")}</div>
|
||||
<div><Field type="password"
|
||||
id="mx_CreateSecretStorage_accountPassword"
|
||||
label={_t("Password")}
|
||||
value={this.state.accountPassword}
|
||||
onChange={this._onAccountPasswordChange}
|
||||
flagInvalid={this.state.accountPasswordCorrect === false}
|
||||
autoFocus={true}
|
||||
/></div>
|
||||
</div>;
|
||||
} else {
|
||||
authPrompt = <p>
|
||||
{_t("You'll need to authenticate with the server to confirm the upgrade.")}
|
||||
</p>;
|
||||
}
|
||||
|
||||
return <form onSubmit={this._onMigrateFormSubmit}>
|
||||
<p>{_t(
|
||||
"Secret Storage will be set up using your existing key backup details. " +
|
||||
"Your secret storage passphrase and recovery key will be the same as " +
|
||||
"they were for your key backup.",
|
||||
"Upgrade this device to allow it to verify other devices, " +
|
||||
"granting them access to encrypted messages and marking them " +
|
||||
"as trusted for other users.",
|
||||
)}</p>
|
||||
<div>{authPrompt}</div>
|
||||
<DialogButtons primaryButton={_t('Next')}
|
||||
onPrimaryButtonClick={this._onMigrateNextClick}
|
||||
primaryIsSubmit={true}
|
||||
hasCancel={true}
|
||||
onCancel={this._onCancel}
|
||||
primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
|
||||
/>
|
||||
</div>;
|
||||
</form>;
|
||||
}
|
||||
|
||||
_renderPhasePassPhrase() {
|
||||
|
@ -564,7 +644,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
case PHASE_RESTORE_KEY_BACKUP:
|
||||
return _t('Restore your Key Backup');
|
||||
case PHASE_MIGRATE:
|
||||
return _t('Migrate from Key Backup');
|
||||
return _t('Upgrade your encryption');
|
||||
case PHASE_PASSPHRASE:
|
||||
return _t('Secure your encrypted messages with a passphrase');
|
||||
case PHASE_PASSPHRASE_CONFIRM:
|
||||
|
|
|
@ -34,8 +34,11 @@ export default createReactClass({
|
|||
// A node to insert into the cancel button instead of default "Cancel"
|
||||
cancelButton: PropTypes.node,
|
||||
|
||||
// If true, make the primary button a form submit button (input type="submit")
|
||||
primaryIsSubmit: PropTypes.bool,
|
||||
|
||||
// onClick handler for the primary button.
|
||||
onPrimaryButtonClick: PropTypes.func.isRequired,
|
||||
onPrimaryButtonClick: PropTypes.func,
|
||||
|
||||
// should there be a cancel button? default: true
|
||||
hasCancel: PropTypes.bool,
|
||||
|
@ -70,15 +73,23 @@ export default createReactClass({
|
|||
}
|
||||
let cancelButton;
|
||||
if (this.props.cancelButton || this.props.hasCancel) {
|
||||
cancelButton = <button onClick={this._onCancelClick} disabled={this.props.disabled}>
|
||||
cancelButton = <button
|
||||
// important: the default type is 'submit' and this button comes before the
|
||||
// primary in the DOM so will get form submissions unless we make it not a submit.
|
||||
type="button"
|
||||
onClick={this._onCancelClick}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{ this.props.cancelButton || _t("Cancel") }
|
||||
</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_Dialog_buttons">
|
||||
{ cancelButton }
|
||||
{ this.props.children }
|
||||
<button className={primaryButtonClassName}
|
||||
<button type={this.props.primaryIsSubmit ? 'submit' : 'button'}
|
||||
className={primaryButtonClassName}
|
||||
onClick={this.props.onPrimaryButtonClick}
|
||||
autoFocus={this.props.focus}
|
||||
disabled={this.props.disabled || this.props.primaryDisabled}
|
||||
|
|
|
@ -1972,7 +1972,9 @@
|
|||
"Import": "Import",
|
||||
"Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.",
|
||||
"Restore": "Restore",
|
||||
"Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.",
|
||||
"Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:",
|
||||
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
|
||||
"Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.",
|
||||
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
|
||||
"<b>Warning</b>: You should only set up secret storage from a trusted computer.": "<b>Warning</b>: You should only set up secret storage from a trusted computer.",
|
||||
"We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.",
|
||||
|
@ -2000,7 +2002,7 @@
|
|||
"Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.",
|
||||
"Set up secret storage": "Set up secret storage",
|
||||
"Restore your Key Backup": "Restore your Key Backup",
|
||||
"Migrate from Key Backup": "Migrate from Key Backup",
|
||||
"Upgrade your encryption": "Upgrade your encryption",
|
||||
"Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase",
|
||||
"Confirm your passphrase": "Confirm your passphrase",
|
||||
"Recovery key": "Recovery key",
|
||||
|
|
Loading…
Reference in a new issue