Merge pull request #4831 from matrix-org/dbkr/recovery_key_passphrase_2

Implement new design on security setup & login
pull/21833/head
David Baker 2020-06-30 17:55:03 +01:00 committed by GitHub
commit 21c5c745c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 347 additions and 275 deletions

View File

@ -319,7 +319,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
}
.mx_Dialog_titleImage {
vertical-align: middle;
vertical-align: sub;
width: 25px;
height: 25px;
margin-left: -2px;

View File

@ -15,13 +15,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_AccessSecretStorageDialog_keyStatus {
height: 30px;
.mx_AccessSecretStorageDialog_titleWithIcon::before {
content: '';
display: inline-block;
width: 24px;
height: 24px;
margin-right: 8px;
position: relative;
top: 5px;
background-color: $primary-fg-color;
}
.mx_AccessSecretStorageDialog_primaryContainer {
/* FIXME: plinth colour in new theme(s). background-color: $accent-color; */
padding: 20px;
.mx_AccessSecretStorageDialog_secureBackupTitle::before {
mask-image: url('$(res)/img/feather-customised/secure-backup.svg');
}
.mx_AccessSecretStorageDialog_securePhraseTitle::before {
mask-image: url('$(res)/img/feather-customised/secure-phrase.svg');
}
.mx_AccessSecretStorageDialog_keyStatus {
height: 30px;
}
.mx_AccessSecretStorageDialog_passPhraseInput,

View File

@ -48,6 +48,29 @@ limitations under the License.
margin-bottom: 1em;
}
.mx_CreateSecretStorageDialog_titleWithIcon::before {
content: '';
display: inline-block;
width: 24px;
height: 24px;
margin-right: 8px;
position: relative;
top: 5px;
background-color: $primary-fg-color;
}
.mx_CreateSecretStorageDialog_secureBackupTitle::before {
mask-image: url('$(res)/img/feather-customised/secure-backup.svg');
}
.mx_CreateSecretStorageDialog_securePhraseTitle::before {
mask-image: url('$(res)/img/feather-customised/secure-phrase.svg');
}
.mx_CreateSecretStorageDialog_centeredTitle, .mx_CreateSecretStorageDialog_centeredBody {
text-align: center;
}
.mx_CreateSecretStorageDialog_primaryContainer {
/* FIXME: plinth colour in new theme(s). background-color: $accent-color; */
padding-top: 20px;
@ -59,6 +82,36 @@ 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;
padding-bottom: 10px;
}
.mx_CreateSecretStorageDialog_optionIcon {
display: inline-block;
width: 24px;
height: 24px;
margin-right: 8px;
position: relative;
top: 5px;
background-color: $primary-fg-color;
}
.mx_CreateSecretStorageDialog_optionIcon_securePhrase {
mask-image: url('$(res)/img/feather-customised/secure-phrase.svg');
}
.mx_CreateSecretStorageDialog_optionIcon_secureBackup {
mask-image: url('$(res)/img/feather-customised/secure-backup.svg');
}
.mx_CreateSecretStorageDialog_passPhraseContainer {
display: flex;
align-items: flex-start;
@ -73,33 +126,42 @@ limitations under the License.
margin-left: 20px;
}
.mx_CreateSecretStorageDialog_recoveryKeyHeader {
margin-bottom: 1em;
}
.mx_CreateSecretStorageDialog_recoveryKeyContainer {
display: flex;
width: 380px;
margin-left: auto;
margin-right: auto;
}
.mx_CreateSecretStorageDialog_recoveryKey {
width: 262px;
font-weight: bold;
text-align: center;
padding: 20px;
color: $info-plinth-fg-color;
background-color: $info-plinth-bg-color;
margin-right: 12px;
border-radius: 6px;
word-spacing: 1em;
margin-bottom: 20px;
}
.mx_CreateSecretStorageDialog_recoveryKeyButtons {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton {
margin-right: 10px;
}
.mx_CreateSecretStorageDialog_recoveryKeyButtons button {
flex: 1;
width: 160px;
padding-left: 0px;
padding-right: 0px;
white-space: nowrap;
}
.mx_CreateSecretStorageDialog_continueSpinner {
margin-top: 33px;
text-align: right;
}
.mx_CreateSecretStorageDialog_continueSpinner img {
width: 20px;
height: 20px;
}

View File

@ -25,13 +25,17 @@ 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;
flex-direction: column;
margin-left: 8px;
margin-right: 8px;
@ -105,3 +109,7 @@ limitations under the License.
}
}
}
.mx_RadioButton_checked {
border-color: $accent-color;
}

View File

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="1" y="2" width="22" height="21">
<rect x="1" y="2" width="21.5" height="5" fill="white"/>
<rect x="1" y="17.7" width="21.5" height="5" fill="white"/>
</mask>
<g mask="url(#mask0)">
<path d="M3.16626 12.4014C3.16626 7.64675 6.99526 3.81775 11.75 3.81775C13.0964 3.81775 14.4429 4.15437 15.6631 4.74345H14.9899C14.5691 4.74345 14.2325 5.08006 14.2325 5.50083C14.2325 5.9216 14.5691 6.25822 14.9899 6.25822H17.3041C17.809 6.25822 18.1877 5.83745 18.1877 5.3746V3.06037C18.1877 2.6396 17.8511 2.30298 17.4303 2.30298C17.0096 2.30298 16.673 2.6396 16.673 3.06037V3.60737C16.6309 3.56529 16.5888 3.5653 16.5467 3.52322C15.074 2.72376 13.433 2.30298 11.75 2.30298C6.1958 2.30298 1.65149 6.84729 1.65149 12.4014C1.65149 14.0845 2.07226 15.7676 2.87172 17.2403C2.99795 17.4928 3.25041 17.619 3.54495 17.619C3.67118 17.619 3.79741 17.5769 3.92364 17.5348C4.30233 17.3245 4.42857 16.8616 4.21819 16.525C3.50288 15.2627 3.16626 13.8321 3.16626 12.4014Z" fill="#2E2F32"/>
<path d="M20.6281 7.56263C20.4177 7.18394 19.9548 7.05771 19.6182 7.2681C19.2395 7.47848 19.1133 7.94133 19.3237 8.27794C19.9969 9.54025 20.3756 10.9288 20.3756 12.4015C20.3756 17.1562 16.5045 20.9852 11.7919 20.9852C10.4454 20.9852 9.09897 20.6486 7.87874 20.0595H8.55198C8.97275 20.0595 9.30937 19.7229 9.30937 19.3021C9.30937 18.8813 8.97275 18.5447 8.55198 18.5447H6.23774C5.73282 18.5447 5.35413 18.9655 5.35413 19.4283V21.7426C5.35413 22.1633 5.69075 22.4999 6.11152 22.4999C6.53229 22.4999 6.8689 22.1633 6.8689 21.7426V21.1956C6.91098 21.2376 6.95306 21.2376 6.99514 21.2797C8.42575 22.0792 10.0667 22.4999 11.7498 22.4999C17.304 22.4999 21.8483 17.9556 21.8483 12.4015C21.8483 10.7184 21.4275 9.03532 20.6281 7.56263Z" fill="#2E2F32"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 9C1.89543 9 1 9.89543 1 11V14C1 15.1046 1.89543 16 3 16H21C22.1046 16 23 15.1046 23 14V11C23 9.89543 22.1046 9 21 9H3ZM5.25 10.5C4.83579 10.5 4.5 10.8358 4.5 11.25C4.5 11.6642 4.83579 12 5.25 12H7.75C8.16421 12 8.5 11.6642 8.5 11.25C8.5 10.8358 8.16421 10.5 7.75 10.5H5.25ZM9.5 11.25C9.5 10.8358 9.83579 10.5 10.25 10.5H10.75C11.1642 10.5 11.5 10.8358 11.5 11.25C11.5 11.6642 11.1642 12 10.75 12H10.25C9.83579 12 9.5 11.6642 9.5 11.25ZM13.25 10.5C12.8358 10.5 12.5 10.8358 12.5 11.25C12.5 11.6642 12.8358 12 13.25 12H15.75C16.1642 12 16.5 11.6642 16.5 11.25C16.5 10.8358 16.1642 10.5 15.75 10.5H13.25ZM17.5 11.25C17.5 10.8358 17.8358 10.5 18.25 10.5H18.75C19.1642 10.5 19.5 10.8358 19.5 11.25C19.5 11.6642 19.1642 12 18.75 12H18.25C17.8358 12 17.5 11.6642 17.5 11.25ZM5.25 13C4.83579 13 4.5 13.3358 4.5 13.75C4.5 14.1642 4.83579 14.5 5.25 14.5H5.75C6.16421 14.5 6.5 14.1642 6.5 13.75C6.5 13.3358 6.16421 13 5.75 13H5.25ZM7.5 13.75C7.5 13.3358 7.83579 13 8.25 13H10.75C11.1642 13 11.5 13.3358 11.5 13.75C11.5 14.1642 11.1642 14.5 10.75 14.5H8.25C7.83579 14.5 7.5 14.1642 7.5 13.75ZM13.25 13C12.8358 13 12.5 13.3358 12.5 13.75C12.5 14.1642 12.8358 14.5 13.25 14.5H13.75C14.1642 14.5 14.5 14.1642 14.5 13.75C14.5 13.3358 14.1642 13 13.75 13H13.25Z" fill="#2E2F32"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="1" y="2" width="22" height="21">
<rect x="1" y="2" width="21.5" height="5" fill="white"/>
<rect x="1" y="17.7" width="21.5" height="5" fill="white"/>
</mask>
<g mask="url(#mask0)">
<path d="M3.16626 12.4014C3.16626 7.64675 6.99526 3.81775 11.75 3.81775C13.0964 3.81775 14.4429 4.15437 15.6631 4.74345H14.9899C14.5691 4.74345 14.2325 5.08006 14.2325 5.50083C14.2325 5.9216 14.5691 6.25822 14.9899 6.25822H17.3041C17.809 6.25822 18.1877 5.83745 18.1877 5.3746V3.06037C18.1877 2.6396 17.8511 2.30298 17.4303 2.30298C17.0096 2.30298 16.673 2.6396 16.673 3.06037V3.60737C16.6309 3.56529 16.5888 3.5653 16.5467 3.52322C15.074 2.72376 13.433 2.30298 11.75 2.30298C6.1958 2.30298 1.65149 6.84729 1.65149 12.4014C1.65149 14.0845 2.07226 15.7676 2.87172 17.2403C2.99795 17.4928 3.25041 17.619 3.54495 17.619C3.67118 17.619 3.79741 17.5769 3.92364 17.5348C4.30233 17.3245 4.42857 16.8616 4.21819 16.525C3.50288 15.2627 3.16626 13.8321 3.16626 12.4014Z" fill="#2E2F32"/>
<path d="M20.6281 7.56263C20.4177 7.18394 19.9548 7.05771 19.6182 7.2681C19.2395 7.47848 19.1133 7.94133 19.3237 8.27794C19.9969 9.54025 20.3756 10.9288 20.3756 12.4015C20.3756 17.1562 16.5045 20.9852 11.7919 20.9852C10.4454 20.9852 9.09897 20.6486 7.87874 20.0595H8.55198C8.97275 20.0595 9.30937 19.7229 9.30937 19.3021C9.30937 18.8813 8.97275 18.5447 8.55198 18.5447H6.23774C5.73282 18.5447 5.35413 18.9655 5.35413 19.4283V21.7426C5.35413 22.1633 5.69075 22.4999 6.11152 22.4999C6.53229 22.4999 6.8689 22.1633 6.8689 21.7426V21.1956C6.91098 21.2376 6.95306 21.2376 6.99514 21.2797C8.42575 22.0792 10.0667 22.4999 11.7498 22.4999C17.304 22.4999 21.8483 17.9556 21.8483 12.4015C21.8483 10.7184 21.4275 9.03532 20.6281 7.56263Z" fill="#2E2F32"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 11C1 9.89543 1.89543 9 3 9H21C22.1046 9 23 9.89543 23 11V14C23 15.1046 22.1046 16 21 16H3C1.89543 16 1 15.1046 1 14V11ZM6 12.5C6 13.3284 5.32843 14 4.5 14C3.67157 14 3 13.3284 3 12.5C3 11.6716 3.67157 11 4.5 11C5.32843 11 6 11.6716 6 12.5ZM9.5 14C10.3284 14 11 13.3284 11 12.5C11 11.6716 10.3284 11 9.5 11C8.67157 11 8 11.6716 8 12.5C8 13.3284 8.67157 14 9.5 14ZM16 12.5C16 13.3284 15.3284 14 14.5 14C13.6716 14 13 13.3284 13 12.5C13 11.6716 13.6716 11 14.5 11C15.3284 11 16 11.6716 16 12.5ZM19.5 14C20.3284 14 21 13.3284 21 12.5C21 11.6716 20.3284 11 19.5 11C18.6716 11 18 11.6716 18 12.5C18 13.3284 18.6716 14 19.5 14Z" fill="#2E2F32"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -26,20 +26,27 @@ 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';
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
import DialogButtons from "../../../../components/views/elements/DialogButtons";
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
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_STORING = 8;
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 CREATE_STORAGE_OPTION_KEY = 'key';
const CREATE_STORAGE_OPTION_PASSPHRASE = 'passphrase';
/*
* Walks the user through the process of creating a passphrase to guard Secure
* Secret Storage in account data.
@ -70,6 +77,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
passPhraseConfirm: '',
copied: false,
downloaded: false,
setPassphrase: false,
backupInfo: null,
backupSigStatus: null,
// does the server offer a UI auth flow with just m.login.password
@ -77,8 +85,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
canUploadKeysWithPasswordOnly: null,
accountPassword: props.accountPassword || "",
accountPasswordCorrect: null,
// status of the key backup toggle switch
useKeyBackup: true,
passPhraseKeySelected: CREATE_STORAGE_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,14 +160,33 @@ 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;
}
_onUseKeyBackupChange = (enabled) => {
this.setState({
useKeyBackup: enabled,
});
_onChooseKeyPassphraseFormSubmit = async () => {
if (this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY) {
this._recoveryKey =
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
this.setState({
copied: false,
downloaded: false,
setPassphrase: false,
phase: PHASE_SHOWKEY,
});
} else {
this.setState({
copied: false,
downloaded: false,
phase: PHASE_PASSPHRASE,
});
}
}
_onMigrateFormSubmit = (e) => {
@ -176,7 +203,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
if (successful) {
this.setState({
copied: true,
phase: PHASE_KEEPITSAFE,
});
}
}
@ -189,7 +215,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
this.setState({
downloaded: true,
phase: PHASE_KEEPITSAFE,
});
}
@ -259,22 +284,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
await cli.bootstrapSecretStorage({
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
createSecretStorageKey: async () => this._recoveryKey,
setupNewKeyBackup: this.state.useKeyBackup,
setupNewKeyBackup: true,
setupNewSecretStorage: true,
});
if (!this.state.useKeyBackup && this.state.backupInfo) {
// If the user is resetting their cross-signing keys and doesn't want
// key backup (but had it enabled before), delete the key backup as it's
// no longer valid.
console.log("Deleting invalid key backup (secrets have been reset; key backup not requested)");
await cli.deleteKeyBackupVersion(this.state.backupInfo.version);
}
} else {
await cli.bootstrapSecretStorage({
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
createSecretStorageKey: async () => this._recoveryKey,
keyBackupInfo: this.state.backupInfo,
setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup,
setupNewKeyBackup: !this.state.backupInfo,
getKeyBackupPassphrase: () => {
// We may already have the backup key if we earlier went
// through the restore backup path, so pass it along
@ -286,9 +304,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
},
});
}
this.setState({
phase: PHASE_DONE,
});
this.props.onFinished(true);
} catch (e) {
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) {
this.setState({
@ -342,22 +358,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
this._fetchBackupInfo();
}
_onSkipSetupClick = () => {
_onShowKeyContinueClick = () => {
this._bootstrapSecretStorage();
}
_onCancelClick = () => {
this.setState({phase: PHASE_CONFIRM_SKIP});
}
_onSetUpClick = () => {
this.setState({phase: PHASE_PASSPHRASE});
}
_onSkipPassPhraseClick = async () => {
this._recoveryKey =
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
this.setState({
copied: false,
downloaded: false,
phase: PHASE_SHOWKEY,
});
_onGoBackClick = () => {
this.setState({phase: PHASE_CHOOSE_KEY_PASSPHRASE});
}
_onPassPhraseNextClick = async (e) => {
@ -384,6 +394,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
this.setState({
copied: false,
downloaded: false,
setPassphrase: true,
phase: PHASE_SHOWKEY,
});
}
@ -397,12 +408,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
});
}
_onKeepItSafeBackClick = () => {
this.setState({
phase: PHASE_SHOWKEY,
});
}
_onPassPhraseValidate = (result) => {
this.setState({
passPhraseValid: result.valid,
@ -427,13 +432,53 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
});
}
_renderPhaseChooseKeyPassphrase() {
return <form onSubmit={this._onChooseKeyPassphraseFormSubmit}>
<p className="mx_CreateSecretStorageDialog_centeredBody">{_t(
"Safeguard against losing access to encrypted messages & data by " +
"backing up encryption keys on your server.",
)}</p>
<div className="mx_CreateSecretStorageDialog_primaryContainer" role="radiogroup" onChange={this._onKeyPassphraseChange}>
<StyledRadioButton
key={CREATE_STORAGE_OPTION_KEY}
value={CREATE_STORAGE_OPTION_KEY}
name="keyPassphrase"
checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY}
>
<div className="mx_CreateSecretStorageDialog_optionTitle">
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup"></span>
{_t("Generate a Security Key")}
</div>
<div>{_t("Well generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}</div>
</StyledRadioButton>
<StyledRadioButton
key={CREATE_STORAGE_OPTION_PASSPHRASE}
value={CREATE_STORAGE_OPTION_PASSPHRASE}
name="keyPassphrase"
checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_PASSPHRASE}
>
<div className="mx_CreateSecretStorageDialog_optionTitle">
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase"></span>
{_t("Enter a Security Phrase")}
</div>
<div>{_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}</div>
</StyledRadioButton>
</div>
<DialogButtons
primaryButton={_t("Continue")}
onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit}
onCancel={this._onCancelClick}
hasCancel={true}
/>
</form>;
}
_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.
// Once we're confident enough in this (and it's supported enough) we can do
// it automatically.
// https://github.com/vector-im/riot-web/issues/11696
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('views.elements.Field');
let authPrompt;
@ -474,7 +519,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
hasCancel={false}
primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
>
<button type="button" className="danger" onClick={this._onSkipSetupClick}>
<button type="button" className="danger" onClick={this._onCancelClick}>
{_t('Skip')}
</button>
</DialogButtons>
@ -482,14 +527,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_renderPhasePassPhrase() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
return <form onSubmit={this._onPassPhraseNextClick}>
<p>{_t(
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
"This should be different to your account password:",
"Enter a security phrase only you know, as its used to safeguard your data. " +
"To be secure, you shouldnt re-use your account password.",
)}</p>
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
@ -508,11 +549,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
/>
</div>
<LabelledToggleSwitch
label={ _t("Back up encrypted message keys")}
onChange={this._onUseKeyBackupChange} value={this.state.useKeyBackup}
/>
<DialogButtons
primaryButton={_t('Continue')}
onPrimaryButtonClick={this._onPassPhraseNextClick}
@ -520,22 +556,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
disabled={!this.state.passPhraseValid}
>
<button type="button"
onClick={this._onSkipSetupClick}
onClick={this._onCancelClick}
className="danger"
>{_t("Skip")}</button>
>{_t("Cancel")}</button>
</DialogButtons>
<details>
<summary>{_t("Advanced")}</summary>
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick} >
{_t("Set up with a recovery key")}
</AccessibleButton>
</details>
</form>;
}
_renderPhasePassPhraseConfirm() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const Field = sdk.getComponent('views.elements.Field');
let matchText;
@ -566,7 +594,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
</div>
</div>;
}
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
<p>{_t(
"Enter your recovery passphrase a second time to confirm it.",
@ -592,7 +619,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
>
<button type="button"
onClick={this._onSkipSetupClick}
onClick={this._onCancelClick}
className="danger"
>{_t("Skip")}</button>
</DialogButtons>
@ -600,66 +627,48 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_renderPhaseShowKey() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let continueButton;
if (this.state.phase === PHASE_SHOWKEY) {
continueButton = <DialogButtons primaryButton={_t("Continue")}
disabled={!this.state.downloaded && !this.state.copied && !this.state.setPassphrase}
onPrimaryButtonClick={this._onShowKeyContinueClick}
hasCancel={false}
/>;
} else {
continueButton = <div className="mx_CreateSecretStorageDialog_continueSpinner">
<InlineSpinner />
</div>;
}
return <div>
<p>{_t(
"Your recovery key is a safety net - you can use it to restore " +
"access to your encrypted messages if you forget your recovery passphrase.",
)}</p>
<p>{_t(
"Keep a copy of it somewhere secure, like a password manager or even a safe.",
"Store your Security Key somewhere safe, like a password manager or a safe, " +
"as its used to safeguard your encrypted data.",
)}</p>
<div className="mx_CreateSecretStorageDialog_primaryContainer">
<div className="mx_CreateSecretStorageDialog_recoveryKeyHeader">
{_t("Your recovery key")}
</div>
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
<div className="mx_CreateSecretStorageDialog_recoveryKey">
<code ref={this._collectRecoveryKeyNode}>{this._recoveryKey.encodedPrivateKey}</code>
</div>
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
<AccessibleButton kind='primary' className="mx_Dialog_primary"
onClick={this._onDownloadClick}
disabled={this.state.phase === PHASE_STORING}
>
{_t("Download")}
</AccessibleButton>
<span>{_t("or")}</span>
<AccessibleButton
kind='primary'
className="mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn"
onClick={this._onCopyClick}
disabled={this.state.phase === PHASE_STORING}
>
{_t("Copy")}
</AccessibleButton>
<AccessibleButton kind='primary' className="mx_Dialog_primary" onClick={this._onDownloadClick}>
{_t("Download")}
{this.state.copied ? _t("Copied!") : _t("Copy")}
</AccessibleButton>
</div>
</div>
</div>
</div>;
}
_renderPhaseKeepItSafe() {
let introText;
if (this.state.copied) {
introText = _t(
"Your recovery key has been <b>copied to your clipboard</b>, paste it to:",
{}, {b: s => <b>{s}</b>},
);
} else if (this.state.downloaded) {
introText = _t(
"Your recovery key is in your <b>Downloads</b> folder.",
{}, {b: s => <b>{s}</b>},
);
}
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
{introText}
<ul>
<li>{_t("<b>Print it</b> and store it somewhere safe", {}, {b: s => <b>{s}</b>})}</li>
<li>{_t("<b>Save it</b> on a USB key or backup drive", {}, {b: s => <b>{s}</b>})}</li>
<li>{_t("<b>Copy it</b> to your personal cloud storage", {}, {b: s => <b>{s}</b>})}</li>
</ul>
<DialogButtons primaryButton={_t("Continue")}
onPrimaryButtonClick={this._bootstrapSecretStorage}
hasCancel={false}>
<button onClick={this._onKeepItSafeBackClick}>{_t("Back")}</button>
</DialogButtons>
{continueButton}
</div>;
}
@ -671,7 +680,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_renderPhaseLoadError() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<p>{_t("Unable to query secret storage status")}</p>
<div className="mx_Dialog_buttons">
@ -684,53 +692,39 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
</div>;
}
_renderPhaseDone() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
_renderPhaseSkipConfirm() {
return <div>
<p>{_t(
"You can now verify your other devices, " +
"and other users to keep your chats safe.",
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
)}</p>
<p>{_t(
"You can also set up Secure Backup & manage your keys in Settings.",
)}</p>
<DialogButtons primaryButton={_t('OK')}
onPrimaryButtonClick={this._onDone}
hasCancel={false}
/>
</div>;
}
_renderPhaseSkipConfirm() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
{_t(
"Without completing security on this session, it wont have " +
"access to encrypted messages.",
)}
<DialogButtons primaryButton={_t('Go back')}
onPrimaryButtonClick={this._onSetUpClick}
onPrimaryButtonClick={this._onGoBackClick}
hasCancel={false}
>
<button type="button" className="danger" onClick={this._onCancel}>{_t('Skip')}</button>
<button type="button" className="danger" onClick={this._onCancel}>{_t('Cancel')}</button>
</DialogButtons>
</div>;
}
_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:
return _t('Set up encryption');
return _t('Set a Security Phrase');
case PHASE_PASSPHRASE_CONFIRM:
return _t('Confirm recovery passphrase');
return _t('Confirm Security Phrase');
case PHASE_CONFIRM_SKIP:
return _t('Are you sure?');
case PHASE_SHOWKEY:
case PHASE_KEEPITSAFE:
return _t('Make a copy of your recovery key');
return _t('Save your Security Key');
case PHASE_STORING:
return _t('Setting up keys');
case PHASE_DONE:
return _t("You're done!");
default:
return '';
}
@ -741,7 +735,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
let content;
if (this.state.error) {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
content = <div>
<p>{_t("Unable to set up secret storage")}</p>
<div className="mx_Dialog_buttons">
@ -760,6 +753,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;
@ -772,31 +768,40 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
case PHASE_SHOWKEY:
content = this._renderPhaseShowKey();
break;
case PHASE_KEEPITSAFE:
content = this._renderPhaseKeepItSafe();
break;
case PHASE_STORING:
content = this._renderBusyPhase();
break;
case PHASE_DONE:
content = this._renderPhaseDone();
break;
case PHASE_CONFIRM_SKIP:
content = this._renderPhaseSkipConfirm();
break;
}
}
let headerImage;
if (this._titleForPhase(this.state.phase)) {
headerImage = require("../../../../../res/img/e2e/normal.svg");
let titleClass = null;
switch (this.state.phase) {
case PHASE_PASSPHRASE:
case PHASE_PASSPHRASE_CONFIRM:
titleClass = [
'mx_CreateSecretStorageDialog_titleWithIcon',
'mx_CreateSecretStorageDialog_securePhraseTitle',
];
break;
case PHASE_SHOWKEY:
titleClass = [
'mx_CreateSecretStorageDialog_titleWithIcon',
'mx_CreateSecretStorageDialog_secureBackupTitle',
];
break;
case PHASE_CHOOSE_KEY_PASSPHRASE:
titleClass = 'mx_CreateSecretStorageDialog_centeredTitle';
break;
}
return (
<BaseDialog className='mx_CreateSecretStorageDialog'
onFinished={this.props.onFinished}
title={this._titleForPhase(this.state.phase)}
headerImage={headerImage}
titleClass={titleClass}
hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)}
fixedWidth={false}
>

View File

@ -75,8 +75,12 @@ export default createReactClass({
// If provided, this is used to add a aria-describedby attribute
contentId: PropTypes.string,
// optional additional class for the title element
titleClass: PropTypes.string,
// optional additional class for the title element (basically anything that can be passed to classnames)
titleClass: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.arrayOf(PropTypes.string),
]),
},
getDefaultProps: function() {

View File

@ -21,7 +21,6 @@ import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import { _t } from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager';
/*
* Access Secure Secret Storage by requesting the user's passphrase.
@ -55,12 +54,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
});
}
_onResetRecoveryClick = () => {
// Re-enter the access flow, but resetting storage this time around.
this.props.onFinished(false);
accessSecretStorage(() => {}, /* forceReset = */ true);
}
_onRecoveryKeyChange = (e) => {
this.setState({
recoveryKey: e.target.value,
@ -118,10 +111,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
let content;
let title;
let titleClass;
if (hasPassphrase && !this.state.forceRecoveryKey) {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
title = _t("Enter recovery passphrase");
title = _t("Security Phrase");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle'];
let keyStatus;
if (this.state.keyMatches === false) {
@ -137,12 +132,15 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
content = <div>
<p>{_t(
"<b>Warning</b>: You should only do this on a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"Access your secure message history and your cross-signing " +
"identity for verifying other sessions by entering your recovery passphrase.",
"Enter your Security Phrase or <button>Use your Security Key</button> to continue.", {},
{
button: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onUseRecoveryKeyClick}
>
{s}
</AccessibleButton>,
},
)}</p>
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
@ -153,10 +151,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
value={this.state.passPhrase}
autoFocus={true}
autoComplete="new-password"
placeholder={_t("Security Phrase")}
/>
{keyStatus}
<DialogButtons
primaryButton={_t('Next')}
primaryButton={_t('Continue')}
onPrimaryButtonClick={this._onPassPhraseNext}
hasCancel={true}
onCancel={this._onCancel}
@ -164,29 +163,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
primaryDisabled={this.state.passPhrase.length === 0}
/>
</form>
{_t(
"If you've forgotten your recovery passphrase you can "+
"<button1>use your recovery key</button1> or " +
"<button2>set up new recovery options</button2>."
, {}, {
button1: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onUseRecoveryKeyClick}
>
{s}
</AccessibleButton>,
button2: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
})}
</div>;
} else {
title = _t("Enter recovery key");
title = _t("Security Key");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let keyStatus;
if (this.state.recoveryKey.length === 0) {
@ -209,14 +190,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
}
content = <div>
<p>{_t(
"<b>Warning</b>: You should only do this on a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"Access your secure message history and your cross-signing " +
"identity for verifying other sessions by entering your recovery key.",
)}</p>
<p>{_t("Use your Security Key to continue.")}</p>
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onRecoveryKeyNext}>
<input className="mx_AccessSecretStorageDialog_recoveryKeyInput"
@ -226,7 +200,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
/>
{keyStatus}
<DialogButtons
primaryButton={_t('Next')}
primaryButton={_t('Continue')}
onPrimaryButtonClick={this._onRecoveryKeyNext}
hasCancel={true}
onCancel={this._onCancel}
@ -234,17 +208,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
primaryDisabled={!this.state.recoveryKeyValid}
/>
</form>
{_t(
"If you've forgotten your recovery key you can "+
"<button>set up new recovery options</button>."
, {}, {
button: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
</AccessibleButton>,
})}
</div>;
}
@ -252,6 +215,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
<BaseDialog className='mx_AccessSecretStorageDialog'
onFinished={this.props.onFinished}
title={title}
titleClass={titleClass}
>
<div>
{content}

View File

@ -42,7 +42,7 @@ export default class StyledRadioButton extends React.PureComponent<IProps, IStat
<input type='radio' disabled={disabled} {...otherProps} />
{/* Used to render the radio button circle */}
<div><div></div></div>
<span>{children}</span>
<div className="mx_RadioButton_content">{children}</div>
<div className="mx_RadioButton_spacer" />
</label>;
}

View File

@ -1839,17 +1839,14 @@
"Remember my selection for this widget": "Remember my selection for this widget",
"Allow": "Allow",
"Deny": "Deny",
"Enter recovery passphrase": "Enter recovery passphrase",
"Security Phrase": "Security Phrase",
"Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.",
"<b>Warning</b>: You should only do this on a trusted computer.": "<b>Warning</b>: You should only do this on a trusted computer.",
"Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.",
"If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.": "If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.",
"Enter recovery key": "Enter recovery key",
"Enter your Security Phrase or <button>Use your Security Key</button> to continue.": "Enter your Security Phrase or <button>Use your Security Key</button> to continue.",
"Security Key": "Security Key",
"Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.",
"This looks like a valid recovery key!": "This looks like a valid recovery key!",
"Not a valid recovery key": "Not a valid recovery key",
"Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.",
"If you've forgotten your recovery key you can <button>set up new recovery options</button>.": "If you've forgotten your recovery key you can <button>set up new recovery options</button>.",
"Use your Security Key to continue.": "Use your Security Key to continue.",
"Restoring keys from backup": "Restoring keys from backup",
"Fetching keys from server...": "Fetching keys from server...",
"%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored",
@ -1863,9 +1860,11 @@
"Keys restored": "Keys restored",
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
"Successfully restored %(sessionCount)s keys": "Successfully restored %(sessionCount)s keys",
"Enter recovery passphrase": "Enter recovery passphrase",
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Warning</b>: you should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.",
"If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>": "If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>",
"Enter recovery key": "Enter recovery key",
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.",
"If you've forgotten your recovery key you can <button>set up new recovery options</button>": "If you've forgotten your recovery key you can <button>set up new recovery options</button>",
@ -2228,48 +2227,56 @@
"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",
"Well generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Well 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",
"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 session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
"Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:",
"Enter a security phrase only you know, as its used to safeguard your data. To be secure, you shouldnt re-use your account password.": "Enter a security phrase only you know, as its used to safeguard your data. To be secure, you shouldnt re-use your account password.",
"Enter a recovery passphrase": "Enter a recovery passphrase",
"Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.",
"Back up encrypted message keys": "Back up encrypted message keys",
"Set up with a recovery key": "Set up with a recovery key",
"That matches!": "That matches!",
"Use a different passphrase?": "Use a different passphrase?",
"That doesn't match.": "That doesn't match.",
"Go back to set it again.": "Go back to set it again.",
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
"Confirm your recovery passphrase": "Confirm your recovery passphrase",
"Store your Security Key somewhere safe, like a password manager or a safe, as its used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as its used to safeguard your encrypted data.",
"Download": "Download",
"Copy": "Copy",
"Unable to query secret storage status": "Unable to query secret storage status",
"Retry": "Retry",
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
"You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.",
"Set up Secure backup": "Set up Secure backup",
"Upgrade your encryption": "Upgrade your encryption",
"Set a Security Phrase": "Set a Security Phrase",
"Confirm Security Phrase": "Confirm Security Phrase",
"Save your Security Key": "Save your Security Key",
"Unable to set up secret storage": "Unable to set up secret storage",
"We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.",
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
"Set up with a recovery key": "Set up with a recovery key",
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
"Repeat your recovery passphrase...": "Repeat your recovery passphrase...",
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.",
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.",
"Your recovery key": "Your recovery key",
"Copy": "Copy",
"Download": "Download",
"Your recovery key has been <b>copied to your clipboard</b>, paste it to:": "Your recovery key has been <b>copied to your clipboard</b>, paste it to:",
"Your recovery key is in your <b>Downloads</b> folder.": "Your recovery key is in your <b>Downloads</b> folder.",
"<b>Print it</b> and store it somewhere safe": "<b>Print it</b> and store it somewhere safe",
"<b>Save it</b> on a USB key or backup drive": "<b>Save it</b> on a USB key or backup drive",
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
"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.",
"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",
"You're done!": "You're done!",
"Unable to set up secret storage": "Unable to set up secret storage",
"We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.",
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
"Repeat your recovery passphrase...": "Repeat your recovery passphrase...",
"Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).",
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.",
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
"Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase",
"Make a copy of your recovery key": "Make a copy of your recovery key",
"Starting backup...": "Starting backup...",
"Success!": "Success!",
"Create key backup": "Create key backup",

View File

@ -79,20 +79,8 @@ module.exports = async function signup(session, username, password, homeserver)
const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit');
await acceptButton.click();
//plow through cross-signing setup by entering arbitrary details
//TODO: It's probably important for the tests to know the passphrase
const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/
let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input');
await session.replaceInputText(passphraseField, xsigningPassphrase);
await session.delay(1000); // give it a second to analyze our passphrase for security
let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
await xsignContButton.click();
//repeat passphrase entry
passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input');
await session.replaceInputText(passphraseField, xsigningPassphrase);
await session.delay(1000); // give it a second to analyze our passphrase for security
xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
// Continue with the default (generate a security key)
const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
await xsignContButton.click();
//ignore the recovery key
@ -101,13 +89,11 @@ module.exports = async function signup(session, username, password, homeserver)
await copyButton.click();
//acknowledge that we copied the recovery key to a safe place
const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary');
const copyContinueButton = await session.query(
'.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary',
);
await copyContinueButton.click();
//acknowledge that we're done cross-signing setup and our keys are safe
const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary');
await doneOkButton.click();
//wait for registration to finish so the hash gets set
//onhashchange better?