From 6ce8584337cb74695d4d8d711301b136468f323d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Jun 2020 15:04:39 +0100 Subject: [PATCH] Implement first screen (recovery key / passphrase choice) --- .../_CreateSecretStorageDialog.scss | 18 ++++ .../views/elements/_StyledRadioButton.scss | 13 ++- res/img/feather-customised/secure-backup.svg | 11 ++ res/img/feather-customised/secure-phrase.svg | 11 ++ .../CreateSecretStorageDialog.js | 102 ++++++++++++++++-- .../views/elements/StyledRadioButton.tsx | 2 +- src/i18n/strings/en_EN.json | 6 ++ 7 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 res/img/feather-customised/secure-backup.svg create mode 100644 res/img/feather-customised/secure-phrase.svg diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 63e5a3de09..28df5039f1 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -59,6 +59,24 @@ limitations under the License. display: block; } +.mx_CreateSecretStorageDialog_primaryContainer .mx_RadioButton { + margin-bottom: 16px; + padding: 11px; +} + +.mx_CreateSecretStorageDialog_optionTitle { + color: $dialog-title-fg-color; + font-weight: 600; + font-size: $font-18px; +} + +.mx_CreateSecretStorageDialog_optionIcon { + width: 24px; + margin-right: 8px; + position: relative; + top: 5px; +} + .mx_CreateSecretStorageDialog_passPhraseContainer { display: flex; align-items: flex-start; diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss index c2edb359dc..eb73cec5b5 100644 --- a/res/css/views/elements/_StyledRadioButton.scss +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -25,16 +25,21 @@ limitations under the License. position: relative; display: flex; - align-items: center; + align-items: baseline; flex-grow: 1; - > span { + border: 1px solid $input-darker-bg-color; + border-radius: 8px; + + > .mx_RadioButton_content { flex-grow: 1; display: flex; margin-left: 8px; margin-right: 8px; + + flex-direction: column; } .mx_RadioButton_spacer { @@ -105,3 +110,7 @@ limitations under the License. } } } + +.mx_RadioButton_checked { + border-color: $accent-color; +} diff --git a/res/img/feather-customised/secure-backup.svg b/res/img/feather-customised/secure-backup.svg new file mode 100644 index 0000000000..c06f93c1fe --- /dev/null +++ b/res/img/feather-customised/secure-backup.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-customised/secure-phrase.svg b/res/img/feather-customised/secure-phrase.svg new file mode 100644 index 0000000000..eb13d3f048 --- /dev/null +++ b/res/img/feather-customised/secure-phrase.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index d7b79c2cfa..08a1dc5d9e 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -26,20 +26,26 @@ import { promptForBackupPassphrase } from '../../../../CrossSigningManager'; import {copyNode} from "../../../../utils/strings"; import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; +import StyledRadioButton from '../../../../components/views/elements/StyledRadioButton'; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; -const PHASE_MIGRATE = 2; -const PHASE_PASSPHRASE = 3; -const PHASE_PASSPHRASE_CONFIRM = 4; -const PHASE_SHOWKEY = 5; -const PHASE_KEEPITSAFE = 6; -const PHASE_STORING = 7; -const PHASE_DONE = 8; -const PHASE_CONFIRM_SKIP = 9; +const PHASE_CHOOSE_KEY_PASSPHRASE = 2; +const PHASE_MIGRATE = 3; +const PHASE_PASSPHRASE = 4; +const PHASE_PASSPHRASE_CONFIRM = 5; +const PHASE_SHOWKEY = 6; +const PHASE_KEEPITSAFE = 7; +const PHASE_STORING = 8; +const PHASE_DONE = 9; +const PHASE_CONFIRM_SKIP = 10; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. +// these end up as strings from being values in the radio buttons, so just use strings +const CREATESTORAGE_OPTION_KEY = 'key'; +const CREATESTORAGE_OPTION_PASSPHRASE = 'passphrase'; + /* * Walks the user through the process of creating a passphrase to guard Secure * Secret Storage in account data. @@ -79,6 +85,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { accountPasswordCorrect: null, // status of the key backup toggle switch useKeyBackup: true, + + passPhraseKeySelected: CREATESTORAGE_OPTION_KEY, }; this._passphraseField = createRef(); @@ -110,7 +118,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { ); const { force } = this.props; - const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_PASSPHRASE; + const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; this.setState({ phase, @@ -152,6 +160,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo(); } + _onKeyPassphraseChange = e => { + this.setState({ + passPhraseKeySelected: e.target.value, + }); + } + _collectRecoveryKeyNode = (n) => { this._recoveryKeyNode = n; } @@ -162,6 +176,24 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _onChooseKeyPassphraseFormSubmit = async () => { + if (this.state.passPhraseKeySelected === CREATESTORAGE_OPTION_KEY) { + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } else { + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_PASSPHRASE, + }); + } + } + _onMigrateFormSubmit = (e) => { e.preventDefault(); if (this.state.backupSigStatus.usable) { @@ -427,6 +459,53 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _renderPhaseChooseKeyPassphrase() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_t( + "Safeguard against losing access to encrypted messages & data by " + + "backing up encryption keys on your server.", + )}

+
+ +
+ + {_t("Generate a Security Key")} +
+
{_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
+
+ +
+ + {_t("Enter a Security Phrase")} +
+
{_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
+
+
+ + ; + } + _renderPhaseMigrate() { // TODO: This is a temporary screen so people who have the labs flag turned on and // click the button are aware they're making a change to their account. @@ -716,6 +795,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _titleForPhase(phase) { switch (phase) { + case PHASE_CHOOSE_KEY_PASSPHRASE: + return _t('Set up Secure backup'); case PHASE_MIGRATE: return _t('Upgrade your encryption'); case PHASE_PASSPHRASE: @@ -760,6 +841,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_LOADERROR: content = this._renderPhaseLoadError(); break; + case PHASE_CHOOSE_KEY_PASSPHRASE: + content = this._renderPhaseChooseKeyPassphrase(); + break; case PHASE_MIGRATE: content = this._renderPhaseMigrate(); break; diff --git a/src/components/views/elements/StyledRadioButton.tsx b/src/components/views/elements/StyledRadioButton.tsx index d7ae4d5af8..3b83666048 100644 --- a/src/components/views/elements/StyledRadioButton.tsx +++ b/src/components/views/elements/StyledRadioButton.tsx @@ -42,7 +42,7 @@ export default class StyledRadioButton extends React.PureComponent {/* Used to render the radio button circle */}
- {children} +
{children}
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 646f43af33..c7b05eafe4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2171,6 +2171,11 @@ "Import": "Import", "Confirm encryption setup": "Confirm encryption setup", "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.", + "Generate a Security Key": "Generate a Security Key", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.", + "Enter a Security Phrase": "Enter a Security Phrase", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use a secret phrase only you know, and optionally save a Security Key to use for backup.", "Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:", "Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption", "Restore": "Restore", @@ -2200,6 +2205,7 @@ "Unable to query secret storage status": "Unable to query secret storage status", "Retry": "Retry", "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", + "Set up Secure backup": "Set up Secure backup", "Upgrade your encryption": "Upgrade your encryption", "Confirm recovery passphrase": "Confirm recovery passphrase", "Make a copy of your recovery key": "Make a copy of your recovery key",