diff --git a/res/css/_common.scss b/res/css/_common.scss
index 51d985efb7..abc57a95ed 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -338,6 +338,14 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
margin-bottom: 10px;
}
+.mx_Dialog_titleImage {
+ vertical-align: middle;
+ width: 25px;
+ height: 25px;
+ margin-left: -2px;
+ margin-right: 4px;
+}
+
.mx_Dialog_title {
font-size: 22px;
line-height: 36px;
diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js
index 01b9c9c7c8..a56a1ee905 100644
--- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js
+++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js
@@ -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,29 +154,46 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
});
}
+ _doBootstrapUIAuth = async (makeRequest) => {
+ if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
+ 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,
+ });
+ } else {
+ const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
+ const { finished } = Modal.createTrackedDialog(
+ 'Cross-signing keys dialog', '', InteractiveAuthDialog,
+ {
+ title: _t("Send cross-signing keys to homeserver"),
+ matrixClient: MatrixClientPeg.get(),
+ makeRequest,
+ },
+ );
+ const [confirmed] = await finished;
+ 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 {
- const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
await cli.bootstrapSecretStorage({
- authUploadDeviceSigningKeys: async (makeRequest) => {
- const { finished } = Modal.createTrackedDialog(
- 'Cross-signing keys dialog', '', InteractiveAuthDialog,
- {
- title: _t("Send cross-signing keys to homeserver"),
- matrixClient: MatrixClientPeg.get(),
- makeRequest,
- },
- );
- const [confirmed] = await finished;
- if (!confirmed) {
- throw new Error("Cross-signing key upload auth canceled");
- }
- },
+ 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) {
- this.setState({ error: 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
@@ -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
+ const Field = sdk.getComponent('views.elements.Field');
+
+ let authPrompt;
+ if (this.state.canUploadKeysWithPasswordOnly) {
+ authPrompt =
+
{_t("Enter your account password to confirm the upgrade:")}
+
+
;
+ } else {
+ authPrompt =
+ {_t("You'll need to authenticate with the server to confirm the upgrade.")}
+
;
+ }
+
+ return
;
+ ;
}
_renderPhasePassPhrase() {
@@ -533,7 +613,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return
{_t(
- "Your access to encrypted messages is now protected.",
+ "This device can now verify other devices, granting them access " +
+ "to encrypted messages and marking them as trusted for other users.",
+ )}
+
{_t(
+ "Verify other users in their profile.",
)}
{content}
diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js
index 19f22a15ad..9238024b60 100644
--- a/src/components/views/dialogs/BaseDialog.js
+++ b/src/components/views/dialogs/BaseDialog.js
@@ -65,6 +65,9 @@ export default createReactClass({
// Title for the dialog.
title: PropTypes.node.isRequired,
+ // Path to an icon to put in the header
+ headerImage: PropTypes.string,
+
// children should be the content of the dialog
children: PropTypes.node,
@@ -110,6 +113,13 @@ export default createReactClass({
);
}
+ let headerImage;
+ if (this.props.headerImage) {
+ headerImage =
;
+ }
+
return (
+ {headerImage}
{ this.props.title }
{ this.props.headerButton }
diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js
index 4e47e73052..7b37fdb4eb 100644
--- a/src/components/views/elements/DialogButtons.js
+++ b/src/components/views/elements/DialogButtons.js
@@ -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 =
+ cancelButton =
{ this.props.cancelButton || _t("Cancel") }
;
}
+
return (
{ cancelButton }
{ this.props.children }
- Warning: You should only set up secret storage from a trusted computer.": "Warning : 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,17 +2002,18 @@
"Print it and store it somewhere safe": "Print it and store it somewhere safe",
"Save it on a USB key or backup drive": "Save it on a USB key or backup drive",
"Copy it to your personal cloud storage": "Copy it to your personal cloud storage",
- "Your access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.",
+ "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.",
+ "Verify other users in their profile.": "Verify other users in their profile.",
"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",
"Keep it safe": "Keep it safe",
"Storing secrets...": "Storing secrets...",
- "Success!": "Success!",
+ "Encryption upgraded": "Encryption upgraded",
"Unable to set up secret storage": "Unable to set up secret storage",
"Retry": "Retry",
"We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.",
@@ -2022,6 +2025,7 @@
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
"Secure your backup with a passphrase": "Secure your backup with a passphrase",
"Starting backup...": "Starting backup...",
+ "Success!": "Success!",
"Create Key Backup": "Create Key Backup",
"Unable to create key backup": "Unable to create key backup",
"Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.",