Merge pull request #4847 from matrix-org/dbkr/recovery_key_upload_2
Add file upload button to recovery key input
This commit is contained in:
commit
2247400010
7 changed files with 226 additions and 55 deletions
|
@ -38,11 +38,56 @@ limitations under the License.
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessSecretStorageDialog_passPhraseInput,
|
.mx_AccessSecretStorageDialog_passPhraseInput {
|
||||||
.mx_AccessSecretStorageDialog_recoveryKeyInput {
|
|
||||||
width: 300px;
|
width: 300px;
|
||||||
border: 1px solid $accent-color;
|
border: 1px solid $accent-color;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyEntry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyFeedback {
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid {
|
||||||
|
color: $input-valid-border-color;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/check.svg');
|
||||||
|
background-color: $input-valid-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid {
|
||||||
|
color: $input-invalid-border-color;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/x.svg');
|
||||||
|
background-color: $input-invalid-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -491,7 +491,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
label={_t("Password")}
|
label={_t("Password")}
|
||||||
value={this.state.accountPassword}
|
value={this.state.accountPassword}
|
||||||
onChange={this._onAccountPasswordChange}
|
onChange={this._onAccountPasswordChange}
|
||||||
flagInvalid={this.state.accountPasswordCorrect === false}
|
forceValidity={this.state.accountPasswordCorrect === false ? false : null}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/></div>
|
/></div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -15,13 +15,25 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import * as sdk from '../../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
|
import Field from '../../elements/Field';
|
||||||
|
import AccessibleButton from '../../elements/AccessibleButton';
|
||||||
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
|
// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode,
|
||||||
|
// so this should be plenty and allow for people putting extra whitespace in the file because
|
||||||
|
// maybe that's a thing people would do?
|
||||||
|
const KEY_FILE_MAX_SIZE = 128;
|
||||||
|
|
||||||
|
// Don't shout at the user that their key is invalid every time they type a key: wait a short time
|
||||||
|
const VALIDATION_THROTTLE_MS = 200;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Access Secure Secret Storage by requesting the user's passphrase.
|
* Access Secure Secret Storage by requesting the user's passphrase.
|
||||||
*/
|
*/
|
||||||
|
@ -35,9 +47,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this._fileUpload = React.createRef();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
recoveryKey: "",
|
recoveryKey: "",
|
||||||
recoveryKeyValid: false,
|
recoveryKeyValid: null,
|
||||||
|
recoveryKeyCorrect: null,
|
||||||
|
recoveryKeyFileError: null,
|
||||||
forceRecoveryKey: false,
|
forceRecoveryKey: false,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
keyMatches: null,
|
keyMatches: null,
|
||||||
|
@ -54,12 +71,89 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_validateRecoveryKeyOnChange = debounce(() => {
|
||||||
|
this._validateRecoveryKey();
|
||||||
|
}, VALIDATION_THROTTLE_MS);
|
||||||
|
|
||||||
|
async _validateRecoveryKey() {
|
||||||
|
if (this.state.recoveryKey === '') {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyValid: null,
|
||||||
|
recoveryKeyCorrect: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
|
||||||
|
const correct = await cli.checkSecretStorageKey(
|
||||||
|
decodedKey, this.props.keyInfo,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyValid: true,
|
||||||
|
recoveryKeyCorrect: correct,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyValid: false,
|
||||||
|
recoveryKeyCorrect: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onRecoveryKeyChange = (e) => {
|
_onRecoveryKeyChange = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
recoveryKey: e.target.value,
|
recoveryKey: e.target.value,
|
||||||
recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value),
|
recoveryKeyFileError: null,
|
||||||
keyMatches: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// also clear the file upload control so that the user can upload the same file
|
||||||
|
// the did before (otherwise the onchange wouldn't fire)
|
||||||
|
if (this._fileUpload.current) this._fileUpload.current.value = null;
|
||||||
|
|
||||||
|
// We don't use Field's validation here because a) we want it in a separate place rather
|
||||||
|
// than in a tooltip and b) we want it to display feedback based on the uploaded file
|
||||||
|
// as well as the text box. Ideally we would refactor Field's validation logic so we could
|
||||||
|
// re-use some of it.
|
||||||
|
this._validateRecoveryKeyOnChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRecoveryKeyFileChange = async e => {
|
||||||
|
if (e.target.files.length === 0) return;
|
||||||
|
|
||||||
|
const f = e.target.files[0];
|
||||||
|
|
||||||
|
if (f.size > KEY_FILE_MAX_SIZE) {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyFileError: true,
|
||||||
|
recoveryKeyCorrect: false,
|
||||||
|
recoveryKeyValid: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const contents = await f.text();
|
||||||
|
// test it's within the base58 alphabet. We could be more strict here, eg. require the
|
||||||
|
// right number of characters, but it's really just to make sure that what we're reading is
|
||||||
|
// text because we'll put it in the text field.
|
||||||
|
if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyFileError: null,
|
||||||
|
recoveryKey: contents.trim(),
|
||||||
|
});
|
||||||
|
this._validateRecoveryKey();
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyFileError: true,
|
||||||
|
recoveryKeyCorrect: false,
|
||||||
|
recoveryKeyValid: false,
|
||||||
|
recoveryKey: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRecoveryKeyFileUploadClick = () => {
|
||||||
|
this._fileUpload.current.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassPhraseNext = async (e) => {
|
_onPassPhraseNext = async (e) => {
|
||||||
|
@ -99,6 +193,20 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getKeyValidationText() {
|
||||||
|
if (this.state.recoveryKeyFileError) {
|
||||||
|
return _t("Wrong file type");
|
||||||
|
} else if (this.state.recoveryKeyCorrect) {
|
||||||
|
return _t("Looks good!");
|
||||||
|
} else if (this.state.recoveryKeyValid) {
|
||||||
|
return _t("Wrong Recovery Key");
|
||||||
|
} else if (this.state.recoveryKeyValid === null) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return _t("Invalid Recovery Key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
|
@ -169,40 +277,50 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
|
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
let keyStatus;
|
const feedbackClasses = classNames({
|
||||||
if (this.state.recoveryKey.length === 0) {
|
'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true,
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus" />;
|
'mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid': this.state.recoveryKeyCorrect === true,
|
||||||
} else if (this.state.keyMatches === false) {
|
'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false,
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
});
|
||||||
{"\uD83D\uDC4E "}{_t(
|
const recoveryKeyFeedback = <div className={feedbackClasses}>
|
||||||
"Unable to access secret storage. " +
|
{this.getKeyValidationText()}
|
||||||
"Please verify that you entered the correct recovery key.",
|
</div>;
|
||||||
)}
|
|
||||||
</div>;
|
|
||||||
} else if (this.state.recoveryKeyValid) {
|
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
|
||||||
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")}
|
|
||||||
</div>;
|
|
||||||
} else {
|
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
|
||||||
{"\uD83D\uDC4E "}{_t("Not a valid recovery key")}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{_t("Use your Security Key to continue.")}</p>
|
<p>{_t("Use your Security Key to continue.")}</p>
|
||||||
|
|
||||||
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onRecoveryKeyNext}>
|
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onRecoveryKeyNext} spellCheck={false}>
|
||||||
<input className="mx_AccessSecretStorageDialog_recoveryKeyInput"
|
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
|
||||||
onChange={this._onRecoveryKeyChange}
|
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput">
|
||||||
value={this.state.recoveryKey}
|
<Field
|
||||||
autoFocus={true}
|
type="text"
|
||||||
/>
|
label={_t('Security Key')}
|
||||||
{keyStatus}
|
value={this.state.recoveryKey}
|
||||||
|
onChange={this._onRecoveryKeyChange}
|
||||||
|
forceValidity={this.state.recoveryKeyCorrect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText">
|
||||||
|
{_t("or")}
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<input type="file"
|
||||||
|
className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
|
||||||
|
ref={this._fileUpload}
|
||||||
|
onChange={this._onRecoveryKeyFileChange}
|
||||||
|
/>
|
||||||
|
<AccessibleButton kind="primary" onClick={this._onRecoveryKeyFileUploadClick}>
|
||||||
|
{_t("Upload")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{recoveryKeyFeedback}
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t('Continue')}
|
primaryButton={_t('Continue')}
|
||||||
onPrimaryButtonClick={this._onRecoveryKeyNext}
|
onPrimaryButtonClick={this._onRecoveryKeyNext}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
|
cancelButton={_t("Go Back")}
|
||||||
|
cancelButtonClass='danger'
|
||||||
onCancel={this._onCancel}
|
onCancel={this._onCancel}
|
||||||
focus={false}
|
focus={false}
|
||||||
primaryDisabled={!this.state.recoveryKeyValid}
|
primaryDisabled={!this.state.recoveryKeyValid}
|
||||||
|
|
|
@ -50,7 +50,7 @@ interface IProps {
|
||||||
// to the user.
|
// to the user.
|
||||||
onValidate?: (input: IFieldState) => Promise<IValidationResult>;
|
onValidate?: (input: IFieldState) => Promise<IValidationResult>;
|
||||||
// If specified, overrides the value returned by onValidate.
|
// If specified, overrides the value returned by onValidate.
|
||||||
flagInvalid?: boolean;
|
forceValidity?: boolean;
|
||||||
// If specified, contents will appear as a tooltip on the element and
|
// If specified, contents will appear as a tooltip on the element and
|
||||||
// validation feedback tooltips will be suppressed.
|
// validation feedback tooltips will be suppressed.
|
||||||
tooltipContent?: React.ReactNode;
|
tooltipContent?: React.ReactNode;
|
||||||
|
@ -203,7 +203,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
element, prefixComponent, postfixComponent, className, onValidate, children,
|
element, prefixComponent, postfixComponent, className, onValidate, children,
|
||||||
tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props;
|
tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props;
|
||||||
|
|
||||||
// Set some defaults for the <input> element
|
// Set some defaults for the <input> element
|
||||||
const ref = input => this.input = input;
|
const ref = input => this.input = input;
|
||||||
|
@ -228,15 +228,15 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>;
|
postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
|
const hasValidationFlag = forceValidity !== null && forceValidity !== undefined;
|
||||||
const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, {
|
const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, {
|
||||||
// If we have a prefix element, leave the label always at the top left and
|
// If we have a prefix element, leave the label always at the top left and
|
||||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||||
// properly.
|
// properly.
|
||||||
mx_Field_labelAlwaysTopLeft: prefixComponent,
|
mx_Field_labelAlwaysTopLeft: prefixComponent,
|
||||||
mx_Field_valid: onValidate && this.state.valid === true,
|
mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true,
|
||||||
mx_Field_invalid: hasValidationFlag
|
mx_Field_invalid: hasValidationFlag
|
||||||
? flagInvalid
|
? !forceValidity
|
||||||
: onValidate && this.state.valid === false,
|
: onValidate && this.state.valid === false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ export default class SetIdServer extends React.Component {
|
||||||
tooltipContent={this._getTooltip()}
|
tooltipContent={this._getTooltip()}
|
||||||
tooltipClassName="mx_SetIdServer_tooltip"
|
tooltipClassName="mx_SetIdServer_tooltip"
|
||||||
disabled={this.state.busy}
|
disabled={this.state.busy}
|
||||||
flagInvalid={!!this.state.error}
|
forceValidity={this.state.error ? false : null}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton type="submit" kind="primary_sm"
|
<AccessibleButton type="submit" kind="primary_sm"
|
||||||
onClick={this._checkIdServer}
|
onClick={this._checkIdServer}
|
||||||
|
|
|
@ -1839,14 +1839,16 @@
|
||||||
"Remember my selection for this widget": "Remember my selection for this widget",
|
"Remember my selection for this widget": "Remember my selection for this widget",
|
||||||
"Allow": "Allow",
|
"Allow": "Allow",
|
||||||
"Deny": "Deny",
|
"Deny": "Deny",
|
||||||
|
"Wrong file type": "Wrong file type",
|
||||||
|
"Looks good!": "Looks good!",
|
||||||
|
"Wrong Recovery Key": "Wrong Recovery Key",
|
||||||
|
"Invalid Recovery Key": "Invalid Recovery Key",
|
||||||
"Security Phrase": "Security Phrase",
|
"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.",
|
"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.",
|
||||||
"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.",
|
"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",
|
"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",
|
|
||||||
"Use your Security Key to continue.": "Use your Security Key to continue.",
|
"Use your Security Key to continue.": "Use your Security Key to continue.",
|
||||||
|
"Go Back": "Go Back",
|
||||||
"Restoring keys from backup": "Restoring keys from backup",
|
"Restoring keys from backup": "Restoring keys from backup",
|
||||||
"Fetching keys from server...": "Fetching keys from server...",
|
"Fetching keys from server...": "Fetching keys from server...",
|
||||||
"%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored",
|
"%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored",
|
||||||
|
@ -1865,6 +1867,8 @@
|
||||||
"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.",
|
"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>",
|
"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 recovery key": "Enter 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",
|
||||||
"<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.",
|
"<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.",
|
"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>",
|
"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>",
|
||||||
|
@ -2188,7 +2192,6 @@
|
||||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||||
"Go Back": "Go Back",
|
|
||||||
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
|
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
|
||||||
"Incorrect password": "Incorrect password",
|
"Incorrect password": "Incorrect password",
|
||||||
"Failed to re-authenticate": "Failed to re-authenticate",
|
"Failed to re-authenticate": "Failed to re-authenticate",
|
||||||
|
|
|
@ -40,19 +40,20 @@ describe("AccessSecretStorageDialog", function() {
|
||||||
testInstance.getInstance()._onRecoveryKeyNext(e);
|
testInstance.getInstance()._onRecoveryKeyNext(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Considers a valid key to be valid", function() {
|
it("Considers a valid key to be valid", async function() {
|
||||||
const testInstance = TestRenderer.create(
|
const testInstance = TestRenderer.create(
|
||||||
<AccessSecretStorageDialog
|
<AccessSecretStorageDialog
|
||||||
checkPrivateKey={() => true}
|
checkPrivateKey={() => true}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
const v = "asfd";
|
const v = "asdf";
|
||||||
const e = { target: { value: v } };
|
const e = { target: { value: v } };
|
||||||
stubClient();
|
stubClient();
|
||||||
MatrixClientPeg.get().isValidRecoveryKey = function(k) {
|
MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => 'a raw key';
|
||||||
return k == v;
|
MatrixClientPeg.get().checkSecretStorageKey = () => true;
|
||||||
};
|
|
||||||
testInstance.getInstance()._onRecoveryKeyChange(e);
|
testInstance.getInstance()._onRecoveryKeyChange(e);
|
||||||
|
// force a validation now because it debounces
|
||||||
|
await testInstance.getInstance()._validateRecoveryKey();
|
||||||
const { recoveryKeyValid } = testInstance.getInstance().state;
|
const { recoveryKeyValid } = testInstance.getInstance().state;
|
||||||
expect(recoveryKeyValid).toBe(true);
|
expect(recoveryKeyValid).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -65,17 +66,21 @@ describe("AccessSecretStorageDialog", function() {
|
||||||
);
|
);
|
||||||
const e = { target: { value: "a" } };
|
const e = { target: { value: "a" } };
|
||||||
stubClient();
|
stubClient();
|
||||||
MatrixClientPeg.get().isValidRecoveryKey = () => true;
|
MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => {
|
||||||
|
throw new Error("that's no key");
|
||||||
|
};
|
||||||
testInstance.getInstance()._onRecoveryKeyChange(e);
|
testInstance.getInstance()._onRecoveryKeyChange(e);
|
||||||
await testInstance.getInstance()._onRecoveryKeyNext({ preventDefault: () => {} });
|
// force a validation now because it debounces
|
||||||
const { keyMatches } = testInstance.getInstance().state;
|
await testInstance.getInstance()._validateRecoveryKey();
|
||||||
expect(keyMatches).toBe(false);
|
|
||||||
|
const { recoveryKeyValid, recoveryKeyCorrect } = testInstance.getInstance().state;
|
||||||
|
expect(recoveryKeyValid).toBe(false);
|
||||||
|
expect(recoveryKeyCorrect).toBe(false);
|
||||||
const notification = testInstance.root.findByProps({
|
const notification = testInstance.root.findByProps({
|
||||||
className: "mx_AccessSecretStorageDialog_keyStatus",
|
className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback " +
|
||||||
|
"mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid",
|
||||||
});
|
});
|
||||||
expect(notification.props.children).toEqual(
|
expect(notification.props.children).toEqual("Invalid Recovery Key");
|
||||||
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
|
|
||||||
"entered the correct recovery key."]);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue