You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-10-31 01:45:39 +03:00 
			
		
		
		
	Convert /src/async-components/views/dialogs/security to TS (#6923)
				
					
				
			* Convert RecoveryMethodRemovedDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert NewRecoveryMethodDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert ImportE2eKeysDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert ExportE2eKeysDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert CreateSecretStorageDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert CreateKeyBackupDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Fix types This is somewhat hacky though I don't know of a better way to do this Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
		| @@ -32,6 +32,7 @@ import SecurityCustomisations from "./customisations/Security"; | ||||
| import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning'; | ||||
|  | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| import { ComponentType } from "react"; | ||||
|  | ||||
| // This stores the secret storage private keys in memory for the JS SDK. This is | ||||
| // only meant to act as a cache to avoid prompting the user multiple times | ||||
| @@ -335,7 +336,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f | ||||
|             // This dialog calls bootstrap itself after guiding the user through | ||||
|             // passphrase creation. | ||||
|             const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', | ||||
|                 import("./async-components/views/dialogs/security/CreateSecretStorageDialog"), | ||||
|                 import( | ||||
|                     "./async-components/views/dialogs/security/CreateSecretStorageDialog" | ||||
|                 ) as unknown as Promise<ComponentType<{}>>, | ||||
|                 { | ||||
|                     forceReset, | ||||
|                 }, | ||||
|   | ||||
| @@ -17,56 +17,70 @@ limitations under the License. | ||||
| 
 | ||||
| import React, { createRef } from 'react'; | ||||
| import FileSaver from 'file-saver'; | ||||
| import * as sdk from '../../../../index'; | ||||
| import { MatrixClientPeg } from '../../../../MatrixClientPeg'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { _t, _td } from '../../../../languageHandler'; | ||||
| import { accessSecretStorage } from '../../../../SecurityManager'; | ||||
| import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; | ||||
| import { copyNode } from "../../../../utils/strings"; | ||||
| import PassphraseField from "../../../../components/views/auth/PassphraseField"; | ||||
| 
 | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import Field from "../../../../components/views/elements/Field"; | ||||
| import Spinner from "../../../../components/views/elements/Spinner"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import DialogButtons from "../../../../components/views/elements/DialogButtons"; | ||||
| import { IValidationResult } from "../../../../components/views/elements/Validation"; | ||||
| import { IPreparedKeyBackupVersion } from "matrix-js-sdk/src/crypto/backup"; | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| 
 | ||||
| const PHASE_PASSPHRASE = 0; | ||||
| const PHASE_PASSPHRASE_CONFIRM = 1; | ||||
| const PHASE_SHOWKEY = 2; | ||||
| const PHASE_KEEPITSAFE = 3; | ||||
| const PHASE_BACKINGUP = 4; | ||||
| const PHASE_DONE = 5; | ||||
| const PHASE_OPTOUT_CONFIRM = 6; | ||||
| enum Phase { | ||||
|     Passphrase = "passphrase", | ||||
|     PassphraseConfirm = "passphrase_confirm", | ||||
|     ShowKey = "show_key", | ||||
|     KeepItSafe = "keep_it_safe", | ||||
|     BackingUp = "backing_up", | ||||
|     Done = "done", | ||||
|     OptOutConfirm = "opt_out_confirm", | ||||
| } | ||||
| 
 | ||||
| const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
 | ||||
| 
 | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
| interface IState { | ||||
|     secureSecretStorage: boolean; | ||||
|     phase: Phase; | ||||
|     passPhrase: string; | ||||
|     passPhraseValid: boolean; | ||||
|     passPhraseConfirm: string; | ||||
|     copied: boolean; | ||||
|     downloaded: boolean; | ||||
|     error?: string; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Walks the user through the process of creating an e2e key backup | ||||
|  * on the server. | ||||
|  */ | ||||
| export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     } | ||||
| export default class CreateKeyBackupDialog extends React.PureComponent<IProps, IState> { | ||||
|     private keyBackupInfo: Pick<IPreparedKeyBackupVersion, "recovery_key" | "algorithm" | "auth_data">; | ||||
|     private recoveryKeyNode = createRef<HTMLElement>(); | ||||
|     private passphraseField = createRef<Field>(); | ||||
| 
 | ||||
|     constructor(props) { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._recoveryKeyNode = null; | ||||
|         this._keyBackupInfo = null; | ||||
| 
 | ||||
|         this.state = { | ||||
|             secureSecretStorage: null, | ||||
|             phase: PHASE_PASSPHRASE, | ||||
|             phase: Phase.Passphrase, | ||||
|             passPhrase: '', | ||||
|             passPhraseValid: false, | ||||
|             passPhraseConfirm: '', | ||||
|             copied: false, | ||||
|             downloaded: false, | ||||
|         }; | ||||
| 
 | ||||
|         this._passphraseField = createRef(); | ||||
|     } | ||||
| 
 | ||||
|     async componentDidMount() { | ||||
|     public async componentDidMount(): Promise<void> { | ||||
|         const cli = MatrixClientPeg.get(); | ||||
|         const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); | ||||
|         this.setState({ secureSecretStorage }); | ||||
| @@ -74,41 +88,37 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|         // If we're using secret storage, skip ahead to the backing up step, as
 | ||||
|         // `accessSecretStorage` will handle passphrases as needed.
 | ||||
|         if (secureSecretStorage) { | ||||
|             this.setState({ phase: PHASE_BACKINGUP }); | ||||
|             this._createBackup(); | ||||
|             this.setState({ phase: Phase.BackingUp }); | ||||
|             this.createBackup(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _collectRecoveryKeyNode = (n) => { | ||||
|         this._recoveryKeyNode = n; | ||||
|     } | ||||
| 
 | ||||
|     _onCopyClick = () => { | ||||
|         const successful = copyNode(this._recoveryKeyNode); | ||||
|     private onCopyClick = (): void => { | ||||
|         const successful = copyNode(this.recoveryKeyNode.current); | ||||
|         if (successful) { | ||||
|             this.setState({ | ||||
|                 copied: true, | ||||
|                 phase: PHASE_KEEPITSAFE, | ||||
|                 phase: Phase.KeepItSafe, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onDownloadClick = () => { | ||||
|         const blob = new Blob([this._keyBackupInfo.recovery_key], { | ||||
|     private onDownloadClick = (): void => { | ||||
|         const blob = new Blob([this.keyBackupInfo.recovery_key], { | ||||
|             type: 'text/plain;charset=us-ascii', | ||||
|         }); | ||||
|         FileSaver.saveAs(blob, 'security-key.txt'); | ||||
| 
 | ||||
|         this.setState({ | ||||
|             downloaded: true, | ||||
|             phase: PHASE_KEEPITSAFE, | ||||
|             phase: Phase.KeepItSafe, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _createBackup = async () => { | ||||
|     private createBackup = async (): Promise<void> => { | ||||
|         const { secureSecretStorage } = this.state; | ||||
|         this.setState({ | ||||
|             phase: PHASE_BACKINGUP, | ||||
|             phase: Phase.BackingUp, | ||||
|             error: null, | ||||
|         }); | ||||
|         let info; | ||||
| @@ -123,12 +133,12 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 }); | ||||
|             } else { | ||||
|                 info = await MatrixClientPeg.get().createKeyBackupVersion( | ||||
|                     this._keyBackupInfo, | ||||
|                     this.keyBackupInfo, | ||||
|                 ); | ||||
|             } | ||||
|             await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup(); | ||||
|             this.setState({ | ||||
|                 phase: PHASE_DONE, | ||||
|                 phase: Phase.Done, | ||||
|             }); | ||||
|         } catch (e) { | ||||
|             logger.error("Error creating key backup", e); | ||||
| @@ -143,97 +153,91 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 error: e, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onCancel = () => { | ||||
|     private onCancel = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onDone = () => { | ||||
|     private onDone = (): void => { | ||||
|         this.props.onFinished(true); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onOptOutClick = () => { | ||||
|         this.setState({ phase: PHASE_OPTOUT_CONFIRM }); | ||||
|     } | ||||
|     private onSetUpClick = (): void => { | ||||
|         this.setState({ phase: Phase.Passphrase }); | ||||
|     }; | ||||
| 
 | ||||
|     _onSetUpClick = () => { | ||||
|         this.setState({ phase: PHASE_PASSPHRASE }); | ||||
|     } | ||||
| 
 | ||||
|     _onSkipPassPhraseClick = async () => { | ||||
|         this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(); | ||||
|     private onSkipPassPhraseClick = async (): Promise<void> => { | ||||
|         this.keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(); | ||||
|         this.setState({ | ||||
|             copied: false, | ||||
|             downloaded: false, | ||||
|             phase: PHASE_SHOWKEY, | ||||
|             phase: Phase.ShowKey, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseNextClick = async (e) => { | ||||
|     private onPassPhraseNextClick = async (e: React.FormEvent): Promise<void> => { | ||||
|         e.preventDefault(); | ||||
|         if (!this._passphraseField.current) return; // unmounting
 | ||||
|         if (!this.passphraseField.current) return; // unmounting
 | ||||
| 
 | ||||
|         await this._passphraseField.current.validate({ allowEmpty: false }); | ||||
|         if (!this._passphraseField.current.state.valid) { | ||||
|             this._passphraseField.current.focus(); | ||||
|             this._passphraseField.current.validate({ allowEmpty: false, focused: true }); | ||||
|         await this.passphraseField.current.validate({ allowEmpty: false }); | ||||
|         if (!this.passphraseField.current.state.valid) { | ||||
|             this.passphraseField.current.focus(); | ||||
|             this.passphraseField.current.validate({ allowEmpty: false, focused: true }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.setState({ phase: PHASE_PASSPHRASE_CONFIRM }); | ||||
|         this.setState({ phase: Phase.PassphraseConfirm }); | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseConfirmNextClick = async (e) => { | ||||
|     private onPassPhraseConfirmNextClick = async (e: React.FormEvent): Promise<void> => { | ||||
|         e.preventDefault(); | ||||
| 
 | ||||
|         if (this.state.passPhrase !== this.state.passPhraseConfirm) return; | ||||
| 
 | ||||
|         this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase); | ||||
|         this.keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase); | ||||
|         this.setState({ | ||||
|             copied: false, | ||||
|             downloaded: false, | ||||
|             phase: PHASE_SHOWKEY, | ||||
|             phase: Phase.ShowKey, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     _onSetAgainClick = () => { | ||||
|     private onSetAgainClick = (): void => { | ||||
|         this.setState({ | ||||
|             passPhrase: '', | ||||
|             passPhraseValid: false, | ||||
|             passPhraseConfirm: '', | ||||
|             phase: PHASE_PASSPHRASE, | ||||
|             phase: Phase.Passphrase, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onKeepItSafeBackClick = () => { | ||||
|     private onKeepItSafeBackClick = (): void => { | ||||
|         this.setState({ | ||||
|             phase: PHASE_SHOWKEY, | ||||
|             phase: Phase.ShowKey, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseValidate = (result) => { | ||||
|     private onPassPhraseValidate = (result: IValidationResult): void => { | ||||
|         this.setState({ | ||||
|             passPhraseValid: result.valid, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseChange = (e) => { | ||||
|     private onPassPhraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             passPhrase: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseConfirmChange = (e) => { | ||||
|     private onPassPhraseConfirmChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             passPhraseConfirm: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _renderPhasePassPhrase() { | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
| 
 | ||||
|         return <form onSubmit={this._onPassPhraseNextClick}> | ||||
|     private renderPhasePassPhrase(): JSX.Element { | ||||
|         return <form onSubmit={this.onPassPhraseNextClick}> | ||||
|             <p>{ _t( | ||||
|                 "<b>Warning</b>: You should only set up key backup from a trusted computer.", {}, | ||||
|                 { b: sub => <b>{ sub }</b> }, | ||||
| @@ -248,11 +252,11 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 <div className="mx_CreateKeyBackupDialog_passPhraseContainer"> | ||||
|                     <PassphraseField | ||||
|                         className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                         onChange={this._onPassPhraseChange} | ||||
|                         onChange={this.onPassPhraseChange} | ||||
|                         minScore={PASSWORD_MIN_SCORE} | ||||
|                         value={this.state.passPhrase} | ||||
|                         onValidate={this._onPassPhraseValidate} | ||||
|                         fieldRef={this._passphraseField} | ||||
|                         onValidate={this.onPassPhraseValidate} | ||||
|                         fieldRef={this.passphraseField} | ||||
|                         autoFocus={true} | ||||
|                         label={_td("Enter a Security Phrase")} | ||||
|                         labelEnterPassword={_td("Enter a Security Phrase")} | ||||
| @@ -264,23 +268,21 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
| 
 | ||||
|             <DialogButtons | ||||
|                 primaryButton={_t('Next')} | ||||
|                 onPrimaryButtonClick={this._onPassPhraseNextClick} | ||||
|                 onPrimaryButtonClick={this.onPassPhraseNextClick} | ||||
|                 hasCancel={false} | ||||
|                 disabled={!this.state.passPhraseValid} | ||||
|             /> | ||||
| 
 | ||||
|             <details> | ||||
|                 <summary>{ _t("Advanced") }</summary> | ||||
|                 <AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick}> | ||||
|                 <AccessibleButton kind='primary' onClick={this.onSkipPassPhraseClick}> | ||||
|                     { _t("Set up with a Security Key") } | ||||
|                 </AccessibleButton> | ||||
|             </details> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhasePassPhraseConfirm() { | ||||
|         const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); | ||||
| 
 | ||||
|     private renderPhasePassPhraseConfirm(): JSX.Element { | ||||
|         let matchText; | ||||
|         let changeText; | ||||
|         if (this.state.passPhraseConfirm === this.state.passPhrase) { | ||||
| @@ -303,14 +305,13 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|             passPhraseMatch = <div className="mx_CreateKeyBackupDialog_passPhraseMatch"> | ||||
|                 <div>{ matchText }</div> | ||||
|                 <div> | ||||
|                     <AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}> | ||||
|                     <AccessibleButton element="span" className="mx_linkButton" onClick={this.onSetAgainClick}> | ||||
|                         { changeText } | ||||
|                     </AccessibleButton> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         return <form onSubmit={this._onPassPhraseConfirmNextClick}> | ||||
|         return <form onSubmit={this.onPassPhraseConfirmNextClick}> | ||||
|             <p>{ _t( | ||||
|                 "Enter your Security Phrase a second time to confirm it.", | ||||
|             ) }</p> | ||||
| @@ -318,7 +319,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 <div className="mx_CreateKeyBackupDialog_passPhraseContainer"> | ||||
|                     <div> | ||||
|                         <input type="password" | ||||
|                             onChange={this._onPassPhraseConfirmChange} | ||||
|                             onChange={this.onPassPhraseConfirmChange} | ||||
|                             value={this.state.passPhraseConfirm} | ||||
|                             className="mx_CreateKeyBackupDialog_passPhraseInput" | ||||
|                             placeholder={_t("Repeat your Security Phrase...")} | ||||
| @@ -330,14 +331,14 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|             </div> | ||||
|             <DialogButtons | ||||
|                 primaryButton={_t('Next')} | ||||
|                 onPrimaryButtonClick={this._onPassPhraseConfirmNextClick} | ||||
|                 onPrimaryButtonClick={this.onPassPhraseConfirmNextClick} | ||||
|                 hasCancel={false} | ||||
|                 disabled={this.state.passPhrase !== this.state.passPhraseConfirm} | ||||
|             /> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseShowKey() { | ||||
|     private renderPhaseShowKey(): JSX.Element { | ||||
|         return <div> | ||||
|             <p>{ _t( | ||||
|                 "Your Security Key is a safety net - you can use it to restore " + | ||||
| @@ -352,13 +353,13 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 </div> | ||||
|                 <div className="mx_CreateKeyBackupDialog_recoveryKeyContainer"> | ||||
|                     <div className="mx_CreateKeyBackupDialog_recoveryKey"> | ||||
|                         <code ref={this._collectRecoveryKeyNode}>{ this._keyBackupInfo.recovery_key }</code> | ||||
|                         <code ref={this.recoveryKeyNode}>{ this.keyBackupInfo.recovery_key }</code> | ||||
|                     </div> | ||||
|                     <div className="mx_CreateKeyBackupDialog_recoveryKeyButtons"> | ||||
|                         <button className="mx_Dialog_primary" onClick={this._onCopyClick}> | ||||
|                         <button className="mx_Dialog_primary" onClick={this.onCopyClick}> | ||||
|                             { _t("Copy") } | ||||
|                         </button> | ||||
|                         <button className="mx_Dialog_primary" onClick={this._onDownloadClick}> | ||||
|                         <button className="mx_Dialog_primary" onClick={this.onDownloadClick}> | ||||
|                             { _t("Download") } | ||||
|                         </button> | ||||
|                     </div> | ||||
| @@ -367,7 +368,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseKeepItSafe() { | ||||
|     private renderPhaseKeepItSafe(): JSX.Element { | ||||
|         let introText; | ||||
|         if (this.state.copied) { | ||||
|             introText = _t( | ||||
| @@ -380,7 +381,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 {}, { b: s => <b>{ s }</b> }, | ||||
|             ); | ||||
|         } | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         return <div> | ||||
|             { introText } | ||||
|             <ul> | ||||
| @@ -389,107 +389,101 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|                 <li>{ _t("<b>Copy it</b> to your personal cloud storage", {}, { b: s => <b>{ s }</b> }) }</li> | ||||
|             </ul> | ||||
|             <DialogButtons primaryButton={_t("Continue")} | ||||
|                 onPrimaryButtonClick={this._createBackup} | ||||
|                 onPrimaryButtonClick={this.createBackup} | ||||
|                 hasCancel={false}> | ||||
|                 <button onClick={this._onKeepItSafeBackClick}>{ _t("Back") }</button> | ||||
|                 <button onClick={this.onKeepItSafeBackClick}>{ _t("Back") }</button> | ||||
|             </DialogButtons> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderBusyPhase(text) { | ||||
|         const Spinner = sdk.getComponent('views.elements.Spinner'); | ||||
|     private renderBusyPhase(): JSX.Element { | ||||
|         return <div> | ||||
|             <Spinner /> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseDone() { | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|     private renderPhaseDone(): JSX.Element { | ||||
|         return <div> | ||||
|             <p>{ _t( | ||||
|                 "Your keys are being backed up (the first backup could take a few minutes).", | ||||
|             ) }</p> | ||||
|             <DialogButtons primaryButton={_t('OK')} | ||||
|                 onPrimaryButtonClick={this._onDone} | ||||
|                 onPrimaryButtonClick={this.onDone} | ||||
|                 hasCancel={false} | ||||
|             /> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseOptOutConfirm() { | ||||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|     private renderPhaseOptOutConfirm(): JSX.Element { | ||||
|         return <div> | ||||
|             { _t( | ||||
|                 "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.", | ||||
|             ) } | ||||
|             <DialogButtons primaryButton={_t('Set up Secure Message Recovery')} | ||||
|                 onPrimaryButtonClick={this._onSetUpClick} | ||||
|                 onPrimaryButtonClick={this.onSetUpClick} | ||||
|                 hasCancel={false} | ||||
|             > | ||||
|                 <button onClick={this._onCancel}>I understand, continue without</button> | ||||
|                 <button onClick={this.onCancel}>I understand, continue without</button> | ||||
|             </DialogButtons> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _titleForPhase(phase) { | ||||
|     private titleForPhase(phase: Phase): string { | ||||
|         switch (phase) { | ||||
|             case PHASE_PASSPHRASE: | ||||
|             case Phase.Passphrase: | ||||
|                 return _t('Secure your backup with a Security Phrase'); | ||||
|             case PHASE_PASSPHRASE_CONFIRM: | ||||
|             case Phase.PassphraseConfirm: | ||||
|                 return _t('Confirm your Security Phrase'); | ||||
|             case PHASE_OPTOUT_CONFIRM: | ||||
|             case Phase.OptOutConfirm: | ||||
|                 return _t('Warning!'); | ||||
|             case PHASE_SHOWKEY: | ||||
|             case PHASE_KEEPITSAFE: | ||||
|             case Phase.ShowKey: | ||||
|             case Phase.KeepItSafe: | ||||
|                 return _t('Make a copy of your Security Key'); | ||||
|             case PHASE_BACKINGUP: | ||||
|             case Phase.BackingUp: | ||||
|                 return _t('Starting backup...'); | ||||
|             case PHASE_DONE: | ||||
|             case Phase.Done: | ||||
|                 return _t('Success!'); | ||||
|             default: | ||||
|                 return _t("Create key backup"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         let content; | ||||
|         if (this.state.error) { | ||||
|             const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|             content = <div> | ||||
|                 <p>{ _t("Unable to create key backup") }</p> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <DialogButtons primaryButton={_t('Retry')} | ||||
|                         onPrimaryButtonClick={this._createBackup} | ||||
|                         onPrimaryButtonClick={this.createBackup} | ||||
|                         hasCancel={true} | ||||
|                         onCancel={this._onCancel} | ||||
|                         onCancel={this.onCancel} | ||||
|                     /> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } else { | ||||
|             switch (this.state.phase) { | ||||
|                 case PHASE_PASSPHRASE: | ||||
|                     content = this._renderPhasePassPhrase(); | ||||
|                 case Phase.Passphrase: | ||||
|                     content = this.renderPhasePassPhrase(); | ||||
|                     break; | ||||
|                 case PHASE_PASSPHRASE_CONFIRM: | ||||
|                     content = this._renderPhasePassPhraseConfirm(); | ||||
|                 case Phase.PassphraseConfirm: | ||||
|                     content = this.renderPhasePassPhraseConfirm(); | ||||
|                     break; | ||||
|                 case PHASE_SHOWKEY: | ||||
|                     content = this._renderPhaseShowKey(); | ||||
|                 case Phase.ShowKey: | ||||
|                     content = this.renderPhaseShowKey(); | ||||
|                     break; | ||||
|                 case PHASE_KEEPITSAFE: | ||||
|                     content = this._renderPhaseKeepItSafe(); | ||||
|                 case Phase.KeepItSafe: | ||||
|                     content = this.renderPhaseKeepItSafe(); | ||||
|                     break; | ||||
|                 case PHASE_BACKINGUP: | ||||
|                     content = this._renderBusyPhase(); | ||||
|                 case Phase.BackingUp: | ||||
|                     content = this.renderBusyPhase(); | ||||
|                     break; | ||||
|                 case PHASE_DONE: | ||||
|                     content = this._renderPhaseDone(); | ||||
|                 case Phase.Done: | ||||
|                     content = this.renderPhaseDone(); | ||||
|                     break; | ||||
|                 case PHASE_OPTOUT_CONFIRM: | ||||
|                     content = this._renderPhaseOptOutConfirm(); | ||||
|                 case Phase.OptOutConfirm: | ||||
|                     content = this.renderPhaseOptOutConfirm(); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| @@ -497,8 +491,8 @@ export default class CreateKeyBackupDialog extends React.PureComponent { | ||||
|         return ( | ||||
|             <BaseDialog className='mx_CreateKeyBackupDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={this._titleForPhase(this.state.phase)} | ||||
|                 hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)} | ||||
|                 title={this.titleForPhase(this.state.phase)} | ||||
|                 hasCancel={[Phase.Passphrase, Phase.Done].includes(this.state.phase)} | ||||
|             > | ||||
|                 <div> | ||||
|                     { content } | ||||
| @@ -16,8 +16,6 @@ limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React, { createRef } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import * as sdk from '../../../../index'; | ||||
| import { MatrixClientPeg } from '../../../../MatrixClientPeg'; | ||||
| import FileSaver from 'file-saver'; | ||||
| import { _t, _td } from '../../../../languageHandler'; | ||||
| @@ -31,52 +29,105 @@ import AccessibleButton from "../../../../components/views/elements/AccessibleBu | ||||
| import DialogButtons from "../../../../components/views/elements/DialogButtons"; | ||||
| import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; | ||||
| import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; | ||||
| import { getSecureBackupSetupMethods, isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; | ||||
| import { | ||||
|     getSecureBackupSetupMethods, | ||||
|     isSecureBackupRequired, | ||||
|     SecureBackupSetupMethod, | ||||
| } from '../../../../utils/WellKnownUtils'; | ||||
| import SecurityCustomisations from "../../../../customisations/Security"; | ||||
| 
 | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import Field from "../../../../components/views/elements/Field"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import Spinner from "../../../../components/views/elements/Spinner"; | ||||
| import { TrustInfo } from "matrix-js-sdk/src/crypto/backup"; | ||||
| import { CrossSigningKeys } from "matrix-js-sdk"; | ||||
| import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog"; | ||||
| import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; | ||||
| import { IValidationResult } from "../../../../components/views/elements/Validation"; | ||||
| 
 | ||||
| const PHASE_LOADING = 0; | ||||
| const PHASE_LOADERROR = 1; | ||||
| 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; | ||||
| // I made a mistake while converting this and it has to be fixed!
 | ||||
| enum Phase { | ||||
|     Loading = "loading", | ||||
|     LoadError = "load_error", | ||||
|     ChooseKeyPassphrase = "choose_key_passphrase", | ||||
|     Migrate = "migrate", | ||||
|     Passphrase = "passphrase", | ||||
|     PassphraseConfirm = "passphrase_confirm", | ||||
|     ShowKey = "show_key", | ||||
|     Storing = "storing", | ||||
|     ConfirmSkip = "confirm_skip", | ||||
| } | ||||
| 
 | ||||
| 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'; | ||||
| interface IProps extends IDialogProps { | ||||
|     hasCancel: boolean; | ||||
|     accountPassword: string; | ||||
|     forceReset: boolean; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|     phase: Phase; | ||||
|     passPhrase: string; | ||||
|     passPhraseValid: boolean; | ||||
|     passPhraseConfirm: string; | ||||
|     copied: boolean; | ||||
|     downloaded: boolean; | ||||
|     setPassphrase: boolean; | ||||
|     backupInfo: IKeyBackupInfo; | ||||
|     backupSigStatus: TrustInfo; | ||||
|     // does the server offer a UI auth flow with just m.login.password
 | ||||
|     // for /keys/device_signing/upload?
 | ||||
|     canUploadKeysWithPasswordOnly: boolean; | ||||
|     accountPassword: string; | ||||
|     accountPasswordCorrect: boolean; | ||||
|     canSkip: boolean; | ||||
|     passPhraseKeySelected: string; | ||||
|     error?: string; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Walks the user through the process of creating a passphrase to guard Secure | ||||
|  * Secret Storage in account data. | ||||
|  */ | ||||
| export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         hasCancel: PropTypes.bool, | ||||
|         accountPassword: PropTypes.string, | ||||
|         forceReset: PropTypes.bool, | ||||
|     }; | ||||
| 
 | ||||
|     static defaultProps = { | ||||
| export default class CreateSecretStorageDialog extends React.PureComponent<IProps, IState> { | ||||
|     public static defaultProps: Partial<IProps> = { | ||||
|         hasCancel: true, | ||||
|         forceReset: false, | ||||
|     }; | ||||
|     private recoveryKey: IRecoveryKey; | ||||
|     private backupKey: Uint8Array; | ||||
|     private recoveryKeyNode = createRef<HTMLElement>(); | ||||
|     private passphraseField = createRef<Field>(); | ||||
| 
 | ||||
|     constructor(props) { | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._recoveryKey = null; | ||||
|         this._recoveryKeyNode = null; | ||||
|         this._backupKey = null; | ||||
|         let passPhraseKeySelected; | ||||
|         const setupMethods = getSecureBackupSetupMethods(); | ||||
|         if (setupMethods.includes(SecureBackupSetupMethod.Key)) { | ||||
|             passPhraseKeySelected = SecureBackupSetupMethod.Key; | ||||
|         } else { | ||||
|             passPhraseKeySelected = SecureBackupSetupMethod.Passphrase; | ||||
|         } | ||||
| 
 | ||||
|         const accountPassword = props.accountPassword || ""; | ||||
|         let canUploadKeysWithPasswordOnly = null; | ||||
|         if (accountPassword) { | ||||
|             // If we have an account password in memory, let's simplify and
 | ||||
|             // assume it means password auth is also supported for device
 | ||||
|             // signing key upload as well. This avoids hitting the server to
 | ||||
|             // test auth flows, which may be slow under high load.
 | ||||
|             canUploadKeysWithPasswordOnly = true; | ||||
|         } else { | ||||
|             this.queryKeyUploadAuth(); | ||||
|         } | ||||
| 
 | ||||
|         this.state = { | ||||
|             phase: PHASE_LOADING, | ||||
|             phase: Phase.Loading, | ||||
|             passPhrase: '', | ||||
|             passPhraseValid: false, | ||||
|             passPhraseConfirm: '', | ||||
| @@ -87,55 +138,37 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             backupSigStatus: null, | ||||
|             // does the server offer a UI auth flow with just m.login.password
 | ||||
|             // for /keys/device_signing/upload?
 | ||||
|             canUploadKeysWithPasswordOnly: null, | ||||
|             accountPassword: props.accountPassword || "", | ||||
|             accountPasswordCorrect: null, | ||||
|             canSkip: !isSecureBackupRequired(), | ||||
|             canUploadKeysWithPasswordOnly, | ||||
|             passPhraseKeySelected, | ||||
|             accountPassword, | ||||
|         }; | ||||
| 
 | ||||
|         const setupMethods = getSecureBackupSetupMethods(); | ||||
|         if (setupMethods.includes("key")) { | ||||
|             this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_KEY; | ||||
|         } else { | ||||
|             this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_PASSPHRASE; | ||||
|         MatrixClientPeg.get().on('crypto.keyBackupStatus', this.onKeyBackupStatusChange); | ||||
| 
 | ||||
|         this.getInitialPhase(); | ||||
|     } | ||||
| 
 | ||||
|         this._passphraseField = createRef(); | ||||
| 
 | ||||
|         MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange); | ||||
| 
 | ||||
|         if (this.state.accountPassword) { | ||||
|             // If we have an account password in memory, let's simplify and
 | ||||
|             // assume it means password auth is also supported for device
 | ||||
|             // signing key upload as well. This avoids hitting the server to
 | ||||
|             // test auth flows, which may be slow under high load.
 | ||||
|             this.state.canUploadKeysWithPasswordOnly = true; | ||||
|         } else { | ||||
|             this._queryKeyUploadAuth(); | ||||
|     public componentWillUnmount(): void { | ||||
|         MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this.onKeyBackupStatusChange); | ||||
|     } | ||||
| 
 | ||||
|         this._getInitialPhase(); | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange); | ||||
|     } | ||||
| 
 | ||||
|     _getInitialPhase() { | ||||
|     private getInitialPhase(): void { | ||||
|         const keyFromCustomisations = SecurityCustomisations.createSecretStorageKey?.(); | ||||
|         if (keyFromCustomisations) { | ||||
|             logger.log("Created key via customisations, jumping to bootstrap step"); | ||||
|             this._recoveryKey = { | ||||
|             this.recoveryKey = { | ||||
|                 privateKey: keyFromCustomisations, | ||||
|             }; | ||||
|             this._bootstrapSecretStorage(); | ||||
|             this.bootstrapSecretStorage(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this._fetchBackupInfo(); | ||||
|         this.fetchBackupInfo(); | ||||
|     } | ||||
| 
 | ||||
|     async _fetchBackupInfo() { | ||||
|     private async fetchBackupInfo(): Promise<{ backupInfo: IKeyBackupInfo, backupSigStatus: TrustInfo }> { | ||||
|         try { | ||||
|             const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); | ||||
|             const backupSigStatus = ( | ||||
| @@ -144,7 +177,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             ); | ||||
| 
 | ||||
|             const { forceReset } = this.props; | ||||
|             const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; | ||||
|             const phase = (backupInfo && !forceReset) ? Phase.Migrate : Phase.ChooseKeyPassphrase; | ||||
| 
 | ||||
|             this.setState({ | ||||
|                 phase, | ||||
| @@ -157,13 +190,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 backupSigStatus, | ||||
|             }; | ||||
|         } catch (e) { | ||||
|             this.setState({ phase: PHASE_LOADERROR }); | ||||
|             this.setState({ phase: Phase.LoadError }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async _queryKeyUploadAuth() { | ||||
|     private async queryKeyUploadAuth(): Promise<void> { | ||||
|         try { | ||||
|             await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {}); | ||||
|             await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {} as CrossSigningKeys); | ||||
|             // 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.
 | ||||
| @@ -182,59 +215,55 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _onKeyBackupStatusChange = () => { | ||||
|         if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo(); | ||||
|     } | ||||
|     private onKeyBackupStatusChange = (): void => { | ||||
|         if (this.state.phase === Phase.Migrate) this.fetchBackupInfo(); | ||||
|     }; | ||||
| 
 | ||||
|     _onKeyPassphraseChange = e => { | ||||
|     private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             passPhraseKeySelected: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _collectRecoveryKeyNode = (n) => { | ||||
|         this._recoveryKeyNode = n; | ||||
|     } | ||||
| 
 | ||||
|     _onChooseKeyPassphraseFormSubmit = async () => { | ||||
|         if (this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY) { | ||||
|             this._recoveryKey = | ||||
|     private onChooseKeyPassphraseFormSubmit = async (): Promise<void> => { | ||||
|         if (this.state.passPhraseKeySelected === SecureBackupSetupMethod.Key) { | ||||
|             this.recoveryKey = | ||||
|                 await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); | ||||
|             this.setState({ | ||||
|                 copied: false, | ||||
|                 downloaded: false, | ||||
|                 setPassphrase: false, | ||||
|                 phase: PHASE_SHOWKEY, | ||||
|                 phase: Phase.ShowKey, | ||||
|             }); | ||||
|         } else { | ||||
|             this.setState({ | ||||
|                 copied: false, | ||||
|                 downloaded: false, | ||||
|                 phase: PHASE_PASSPHRASE, | ||||
|                 phase: Phase.Passphrase, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onMigrateFormSubmit = (e) => { | ||||
|     private onMigrateFormSubmit = (e: React.FormEvent): void => { | ||||
|         e.preventDefault(); | ||||
|         if (this.state.backupSigStatus.usable) { | ||||
|             this._bootstrapSecretStorage(); | ||||
|             this.bootstrapSecretStorage(); | ||||
|         } else { | ||||
|             this._restoreBackup(); | ||||
|         } | ||||
|             this.restoreBackup(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     _onCopyClick = () => { | ||||
|         const successful = copyNode(this._recoveryKeyNode); | ||||
|     private onCopyClick = (): void => { | ||||
|         const successful = copyNode(this.recoveryKeyNode.current); | ||||
|         if (successful) { | ||||
|             this.setState({ | ||||
|                 copied: true, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onDownloadClick = () => { | ||||
|         const blob = new Blob([this._recoveryKey.encodedPrivateKey], { | ||||
|     private onDownloadClick = (): void => { | ||||
|         const blob = new Blob([this.recoveryKey.encodedPrivateKey], { | ||||
|             type: 'text/plain;charset=us-ascii', | ||||
|         }); | ||||
|         FileSaver.saveAs(blob, 'security-key.txt'); | ||||
| @@ -242,9 +271,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         this.setState({ | ||||
|             downloaded: true, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _doBootstrapUIAuth = async (makeRequest) => { | ||||
|     private doBootstrapUIAuth = async (makeRequest: (authData: any) => void): Promise<void> => { | ||||
|         if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) { | ||||
|             await makeRequest({ | ||||
|                 type: 'm.login.password', | ||||
| @@ -258,8 +287,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 password: this.state.accountPassword, | ||||
|             }); | ||||
|         } else { | ||||
|             const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); | ||||
| 
 | ||||
|             const dialogAesthetics = { | ||||
|                 [SSOAuthEntry.PHASE_PREAUTH]: { | ||||
|                     title: _t("Use Single Sign On to continue"), | ||||
| @@ -292,11 +319,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 throw new Error("Cross-signing key upload auth canceled"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _bootstrapSecretStorage = async () => { | ||||
|     private bootstrapSecretStorage = async (): Promise<void> => { | ||||
|         this.setState({ | ||||
|             phase: PHASE_STORING, | ||||
|             phase: Phase.Storing, | ||||
|             error: null, | ||||
|         }); | ||||
| 
 | ||||
| @@ -308,7 +335,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             if (forceReset) { | ||||
|                 logger.log("Forcing secret storage reset"); | ||||
|                 await cli.bootstrapSecretStorage({ | ||||
|                     createSecretStorageKey: async () => this._recoveryKey, | ||||
|                     createSecretStorageKey: async () => this.recoveryKey, | ||||
|                     setupNewKeyBackup: true, | ||||
|                     setupNewSecretStorage: true, | ||||
|                 }); | ||||
| @@ -321,18 +348,18 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 //     keys (and also happen to skip all post-authentication flows at the
 | ||||
|                 //     moment via token login)
 | ||||
|                 await cli.bootstrapCrossSigning({ | ||||
|                     authUploadDeviceSigningKeys: this._doBootstrapUIAuth, | ||||
|                     authUploadDeviceSigningKeys: this.doBootstrapUIAuth, | ||||
|                 }); | ||||
|                 await cli.bootstrapSecretStorage({ | ||||
|                     createSecretStorageKey: async () => this._recoveryKey, | ||||
|                     createSecretStorageKey: async () => this.recoveryKey, | ||||
|                     keyBackupInfo: this.state.backupInfo, | ||||
|                     setupNewKeyBackup: !this.state.backupInfo, | ||||
|                     getKeyBackupPassphrase: () => { | ||||
|                     getKeyBackupPassphrase: async () => { | ||||
|                         // We may already have the backup key if we earlier went
 | ||||
|                         // through the restore backup path, so pass it along
 | ||||
|                         // rather than prompting again.
 | ||||
|                         if (this._backupKey) { | ||||
|                             return this._backupKey; | ||||
|                         if (this.backupKey) { | ||||
|                             return this.backupKey; | ||||
|                         } | ||||
|                         return promptForBackupPassphrase(); | ||||
|                     }, | ||||
| @@ -344,27 +371,23 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 this.setState({ | ||||
|                     accountPassword: '', | ||||
|                     accountPasswordCorrect: false, | ||||
|                     phase: PHASE_MIGRATE, | ||||
|                     phase: Phase.Migrate, | ||||
|                 }); | ||||
|             } else { | ||||
|                 this.setState({ error: e }); | ||||
|             } | ||||
|             logger.error("Error bootstrapping secret storage", e); | ||||
|         } | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onCancel = () => { | ||||
|     private onCancel = (): void => { | ||||
|         this.props.onFinished(false); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onDone = () => { | ||||
|         this.props.onFinished(true); | ||||
|     } | ||||
| 
 | ||||
|     _restoreBackup = async () => { | ||||
|     private restoreBackup = async (): Promise<void> => { | ||||
|         // It's possible we'll need the backup key later on for bootstrapping,
 | ||||
|         // so let's stash it here, rather than prompting for it twice.
 | ||||
|         const keyCallback = k => this._backupKey = k; | ||||
|         const keyCallback = k => this.backupKey = k; | ||||
| 
 | ||||
|         const { finished } = Modal.createTrackedDialog( | ||||
|             'Restore Backup', '', RestoreKeyBackupDialog, | ||||
| @@ -376,103 +399,103 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         ); | ||||
| 
 | ||||
|         await finished; | ||||
|         const { backupSigStatus } = await this._fetchBackupInfo(); | ||||
|         const { backupSigStatus } = await this.fetchBackupInfo(); | ||||
|         if ( | ||||
|             backupSigStatus.usable && | ||||
|             this.state.canUploadKeysWithPasswordOnly && | ||||
|             this.state.accountPassword | ||||
|         ) { | ||||
|             this._bootstrapSecretStorage(); | ||||
|         } | ||||
|             this.bootstrapSecretStorage(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     _onLoadRetryClick = () => { | ||||
|         this.setState({ phase: PHASE_LOADING }); | ||||
|         this._fetchBackupInfo(); | ||||
|     } | ||||
|     private onLoadRetryClick = (): void => { | ||||
|         this.setState({ phase: Phase.Loading }); | ||||
|         this.fetchBackupInfo(); | ||||
|     }; | ||||
| 
 | ||||
|     _onShowKeyContinueClick = () => { | ||||
|         this._bootstrapSecretStorage(); | ||||
|     } | ||||
|     private onShowKeyContinueClick = (): void => { | ||||
|         this.bootstrapSecretStorage(); | ||||
|     }; | ||||
| 
 | ||||
|     _onCancelClick = () => { | ||||
|         this.setState({ phase: PHASE_CONFIRM_SKIP }); | ||||
|     } | ||||
|     private onCancelClick = (): void => { | ||||
|         this.setState({ phase: Phase.ConfirmSkip }); | ||||
|     }; | ||||
| 
 | ||||
|     _onGoBackClick = () => { | ||||
|         this.setState({ phase: PHASE_CHOOSE_KEY_PASSPHRASE }); | ||||
|     } | ||||
|     private onGoBackClick = (): void => { | ||||
|         this.setState({ phase: Phase.ChooseKeyPassphrase }); | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseNextClick = async (e) => { | ||||
|     private onPassPhraseNextClick = async (e: React.FormEvent) => { | ||||
|         e.preventDefault(); | ||||
|         if (!this._passphraseField.current) return; // unmounting
 | ||||
|         if (!this.passphraseField.current) return; // unmounting
 | ||||
| 
 | ||||
|         await this._passphraseField.current.validate({ allowEmpty: false }); | ||||
|         if (!this._passphraseField.current.state.valid) { | ||||
|             this._passphraseField.current.focus(); | ||||
|             this._passphraseField.current.validate({ allowEmpty: false, focused: true }); | ||||
|         await this.passphraseField.current.validate({ allowEmpty: false }); | ||||
|         if (!this.passphraseField.current.state.valid) { | ||||
|             this.passphraseField.current.focus(); | ||||
|             this.passphraseField.current.validate({ allowEmpty: false, focused: true }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.setState({ phase: PHASE_PASSPHRASE_CONFIRM }); | ||||
|         this.setState({ phase: Phase.PassphraseConfirm }); | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseConfirmNextClick = async (e) => { | ||||
|     private onPassPhraseConfirmNextClick = async (e: React.FormEvent) => { | ||||
|         e.preventDefault(); | ||||
| 
 | ||||
|         if (this.state.passPhrase !== this.state.passPhraseConfirm) return; | ||||
| 
 | ||||
|         this._recoveryKey = | ||||
|         this.recoveryKey = | ||||
|             await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase); | ||||
|         this.setState({ | ||||
|             copied: false, | ||||
|             downloaded: false, | ||||
|             setPassphrase: true, | ||||
|             phase: PHASE_SHOWKEY, | ||||
|             phase: Phase.ShowKey, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onSetAgainClick = () => { | ||||
|     private onSetAgainClick = (): void => { | ||||
|         this.setState({ | ||||
|             passPhrase: '', | ||||
|             passPhraseValid: false, | ||||
|             passPhraseConfirm: '', | ||||
|             phase: PHASE_PASSPHRASE, | ||||
|             phase: Phase.Passphrase, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseValidate = (result) => { | ||||
|     private onPassPhraseValidate = (result: IValidationResult): void => { | ||||
|         this.setState({ | ||||
|             passPhraseValid: result.valid, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseChange = (e) => { | ||||
|     private onPassPhraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             passPhrase: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onPassPhraseConfirmChange = (e) => { | ||||
|     private onPassPhraseConfirmChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             passPhraseConfirm: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _onAccountPasswordChange = (e) => { | ||||
|     private onAccountPasswordChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||||
|         this.setState({ | ||||
|             accountPassword: e.target.value, | ||||
|         }); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     _renderOptionKey() { | ||||
|     private renderOptionKey(): JSX.Element { | ||||
|         return ( | ||||
|             <StyledRadioButton | ||||
|                 key={CREATE_STORAGE_OPTION_KEY} | ||||
|                 value={CREATE_STORAGE_OPTION_KEY} | ||||
|                 key={SecureBackupSetupMethod.Key} | ||||
|                 value={SecureBackupSetupMethod.Key} | ||||
|                 name="keyPassphrase" | ||||
|                 checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY} | ||||
|                 onChange={this._onKeyPassphraseChange} | ||||
|                 checked={this.state.passPhraseKeySelected === SecureBackupSetupMethod.Key} | ||||
|                 onChange={this.onKeyPassphraseChange} | ||||
|                 outlined | ||||
|             > | ||||
|                 <div className="mx_CreateSecretStorageDialog_optionTitle"> | ||||
| @@ -484,14 +507,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     _renderOptionPassphrase() { | ||||
|     private renderOptionPassphrase(): JSX.Element { | ||||
|         return ( | ||||
|             <StyledRadioButton | ||||
|                 key={CREATE_STORAGE_OPTION_PASSPHRASE} | ||||
|                 value={CREATE_STORAGE_OPTION_PASSPHRASE} | ||||
|                 key={SecureBackupSetupMethod.Passphrase} | ||||
|                 value={SecureBackupSetupMethod.Passphrase} | ||||
|                 name="keyPassphrase" | ||||
|                 checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_PASSPHRASE} | ||||
|                 onChange={this._onKeyPassphraseChange} | ||||
|                 checked={this.state.passPhraseKeySelected === SecureBackupSetupMethod.Passphrase} | ||||
|                 onChange={this.onKeyPassphraseChange} | ||||
|                 outlined | ||||
|             > | ||||
|                 <div className="mx_CreateSecretStorageDialog_optionTitle"> | ||||
| @@ -503,12 +526,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseChooseKeyPassphrase() { | ||||
|     private renderPhaseChooseKeyPassphrase(): JSX.Element { | ||||
|         const setupMethods = getSecureBackupSetupMethods(); | ||||
|         const optionKey = setupMethods.includes("key") ? this._renderOptionKey() : null; | ||||
|         const optionPassphrase = setupMethods.includes("passphrase") ? this._renderOptionPassphrase() : null; | ||||
|         const optionKey = setupMethods.includes(SecureBackupSetupMethod.Key) ? this.renderOptionKey() : null; | ||||
|         const optionPassphrase = setupMethods.includes(SecureBackupSetupMethod.Passphrase) | ||||
|             ? this.renderOptionPassphrase() | ||||
|             : null; | ||||
| 
 | ||||
|         return <form onSubmit={this._onChooseKeyPassphraseFormSubmit}> | ||||
|         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.", | ||||
| @@ -519,20 +544,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             </div> | ||||
|             <DialogButtons | ||||
|                 primaryButton={_t("Continue")} | ||||
|                 onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit} | ||||
|                 onCancel={this._onCancelClick} | ||||
|                 onPrimaryButtonClick={this.onChooseKeyPassphraseFormSubmit} | ||||
|                 onCancel={this.onCancelClick} | ||||
|                 hasCancel={this.state.canSkip} | ||||
|             /> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseMigrate() { | ||||
|     private renderPhaseMigrate(): JSX.Element { | ||||
|         // 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/element-web/issues/11696
 | ||||
|         const Field = sdk.getComponent('views.elements.Field'); | ||||
| 
 | ||||
|         let authPrompt; | ||||
|         let nextCaption = _t("Next"); | ||||
| @@ -543,7 +567,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                     type="password" | ||||
|                     label={_t("Password")} | ||||
|                     value={this.state.accountPassword} | ||||
|                     onChange={this._onAccountPasswordChange} | ||||
|                     onChange={this.onAccountPasswordChange} | ||||
|                     forceValidity={this.state.accountPasswordCorrect === false ? false : null} | ||||
|                     autoFocus={true} | ||||
|                 /></div> | ||||
| @@ -559,7 +583,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             </p>; | ||||
|         } | ||||
| 
 | ||||
|         return <form onSubmit={this._onMigrateFormSubmit}> | ||||
|         return <form onSubmit={this.onMigrateFormSubmit}> | ||||
|             <p>{ _t( | ||||
|                 "Upgrade this session to allow it to verify other sessions, " + | ||||
|                 "granting them access to encrypted messages and marking them " + | ||||
| @@ -568,19 +592,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             <div>{ authPrompt }</div> | ||||
|             <DialogButtons | ||||
|                 primaryButton={nextCaption} | ||||
|                 onPrimaryButtonClick={this._onMigrateFormSubmit} | ||||
|                 onPrimaryButtonClick={this.onMigrateFormSubmit} | ||||
|                 hasCancel={false} | ||||
|                 primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword} | ||||
|             > | ||||
|                 <button type="button" className="danger" onClick={this._onCancelClick}> | ||||
|                 <button type="button" className="danger" onClick={this.onCancelClick}> | ||||
|                     { _t('Skip') } | ||||
|                 </button> | ||||
|             </DialogButtons> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhasePassPhrase() { | ||||
|         return <form onSubmit={this._onPassPhraseNextClick}> | ||||
|     private renderPhasePassPhrase(): JSX.Element { | ||||
|         return <form onSubmit={this.onPassPhraseNextClick}> | ||||
|             <p>{ _t( | ||||
|                 "Enter a security phrase only you know, as it’s used to safeguard your data. " + | ||||
|                 "To be secure, you shouldn’t re-use your account password.", | ||||
| @@ -589,11 +613,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             <div className="mx_CreateSecretStorageDialog_passPhraseContainer"> | ||||
|                 <PassphraseField | ||||
|                     className="mx_CreateSecretStorageDialog_passPhraseField" | ||||
|                     onChange={this._onPassPhraseChange} | ||||
|                     onChange={this.onPassPhraseChange} | ||||
|                     minScore={PASSWORD_MIN_SCORE} | ||||
|                     value={this.state.passPhrase} | ||||
|                     onValidate={this._onPassPhraseValidate} | ||||
|                     fieldRef={this._passphraseField} | ||||
|                     onValidate={this.onPassPhraseValidate} | ||||
|                     fieldRef={this.passphraseField} | ||||
|                     autoFocus={true} | ||||
|                     label={_td("Enter a Security Phrase")} | ||||
|                     labelEnterPassword={_td("Enter a Security Phrase")} | ||||
| @@ -604,21 +628,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
| 
 | ||||
|             <DialogButtons | ||||
|                 primaryButton={_t('Continue')} | ||||
|                 onPrimaryButtonClick={this._onPassPhraseNextClick} | ||||
|                 onPrimaryButtonClick={this.onPassPhraseNextClick} | ||||
|                 hasCancel={false} | ||||
|                 disabled={!this.state.passPhraseValid} | ||||
|             > | ||||
|                 <button type="button" | ||||
|                     onClick={this._onCancelClick} | ||||
|                     onClick={this.onCancelClick} | ||||
|                     className="danger" | ||||
|                 >{ _t("Cancel") }</button> | ||||
|             </DialogButtons> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhasePassPhraseConfirm() { | ||||
|         const Field = sdk.getComponent('views.elements.Field'); | ||||
| 
 | ||||
|     private renderPhasePassPhraseConfirm(): JSX.Element { | ||||
|         let matchText; | ||||
|         let changeText; | ||||
|         if (this.state.passPhraseConfirm === this.state.passPhrase) { | ||||
| @@ -641,20 +663,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             passPhraseMatch = <div> | ||||
|                 <div>{ matchText }</div> | ||||
|                 <div> | ||||
|                     <AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}> | ||||
|                     <AccessibleButton element="span" className="mx_linkButton" onClick={this.onSetAgainClick}> | ||||
|                         { changeText } | ||||
|                     </AccessibleButton> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } | ||||
|         return <form onSubmit={this._onPassPhraseConfirmNextClick}> | ||||
|         return <form onSubmit={this.onPassPhraseConfirmNextClick}> | ||||
|             <p>{ _t( | ||||
|                 "Enter your Security Phrase a second time to confirm it.", | ||||
|             ) }</p> | ||||
|             <div className="mx_CreateSecretStorageDialog_passPhraseContainer"> | ||||
|                 <Field | ||||
|                     type="password" | ||||
|                     onChange={this._onPassPhraseConfirmChange} | ||||
|                     onChange={this.onPassPhraseConfirmChange} | ||||
|                     value={this.state.passPhraseConfirm} | ||||
|                     className="mx_CreateSecretStorageDialog_passPhraseField" | ||||
|                     label={_t("Confirm your Security Phrase")} | ||||
| @@ -667,24 +689,24 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             </div> | ||||
|             <DialogButtons | ||||
|                 primaryButton={_t('Continue')} | ||||
|                 onPrimaryButtonClick={this._onPassPhraseConfirmNextClick} | ||||
|                 onPrimaryButtonClick={this.onPassPhraseConfirmNextClick} | ||||
|                 hasCancel={false} | ||||
|                 disabled={this.state.passPhrase !== this.state.passPhraseConfirm} | ||||
|             > | ||||
|                 <button type="button" | ||||
|                     onClick={this._onCancelClick} | ||||
|                     onClick={this.onCancelClick} | ||||
|                     className="danger" | ||||
|                 >{ _t("Skip") }</button> | ||||
|             </DialogButtons> | ||||
|         </form>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseShowKey() { | ||||
|     private renderPhaseShowKey(): JSX.Element { | ||||
|         let continueButton; | ||||
|         if (this.state.phase === PHASE_SHOWKEY) { | ||||
|         if (this.state.phase === Phase.ShowKey) { | ||||
|             continueButton = <DialogButtons primaryButton={_t("Continue")} | ||||
|                 disabled={!this.state.downloaded && !this.state.copied && !this.state.setPassphrase} | ||||
|                 onPrimaryButtonClick={this._onShowKeyContinueClick} | ||||
|                 onPrimaryButtonClick={this.onShowKeyContinueClick} | ||||
|                 hasCancel={false} | ||||
|             />; | ||||
|         } else { | ||||
| @@ -700,13 +722,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|             <div className="mx_CreateSecretStorageDialog_primaryContainer"> | ||||
|                 <div className="mx_CreateSecretStorageDialog_recoveryKeyContainer"> | ||||
|                     <div className="mx_CreateSecretStorageDialog_recoveryKey"> | ||||
|                         <code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code> | ||||
|                         <code ref={this.recoveryKeyNode}>{ 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} | ||||
|                             onClick={this.onDownloadClick} | ||||
|                             disabled={this.state.phase === Phase.Storing} | ||||
|                         > | ||||
|                             { _t("Download") } | ||||
|                         </AccessibleButton> | ||||
| @@ -714,8 +736,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                         <AccessibleButton | ||||
|                             kind='primary' | ||||
|                             className="mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn" | ||||
|                             onClick={this._onCopyClick} | ||||
|                             disabled={this.state.phase === PHASE_STORING} | ||||
|                             onClick={this.onCopyClick} | ||||
|                             disabled={this.state.phase === Phase.Storing} | ||||
|                         > | ||||
|                             { this.state.copied ? _t("Copied!") : _t("Copy") } | ||||
|                         </AccessibleButton> | ||||
| @@ -726,27 +748,26 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderBusyPhase() { | ||||
|         const Spinner = sdk.getComponent('views.elements.Spinner'); | ||||
|     private renderBusyPhase(): JSX.Element { | ||||
|         return <div> | ||||
|             <Spinner /> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseLoadError() { | ||||
|     private renderPhaseLoadError(): JSX.Element { | ||||
|         return <div> | ||||
|             <p>{ _t("Unable to query secret storage status") }</p> | ||||
|             <div className="mx_Dialog_buttons"> | ||||
|                 <DialogButtons primaryButton={_t('Retry')} | ||||
|                     onPrimaryButtonClick={this._onLoadRetryClick} | ||||
|                     onPrimaryButtonClick={this.onLoadRetryClick} | ||||
|                     hasCancel={this.state.canSkip} | ||||
|                     onCancel={this._onCancel} | ||||
|                     onCancel={this.onCancel} | ||||
|                 /> | ||||
|             </div> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _renderPhaseSkipConfirm() { | ||||
|     private renderPhaseSkipConfirm(): JSX.Element { | ||||
|         return <div> | ||||
|             <p>{ _t( | ||||
|                 "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", | ||||
| @@ -755,98 +776,96 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|                 "You can also set up Secure Backup & manage your keys in Settings.", | ||||
|             ) }</p> | ||||
|             <DialogButtons primaryButton={_t('Go back')} | ||||
|                 onPrimaryButtonClick={this._onGoBackClick} | ||||
|                 onPrimaryButtonClick={this.onGoBackClick} | ||||
|                 hasCancel={false} | ||||
|             > | ||||
|                 <button type="button" className="danger" onClick={this._onCancel}>{ _t('Cancel') }</button> | ||||
|                 <button type="button" className="danger" onClick={this.onCancel}>{ _t('Cancel') }</button> | ||||
|             </DialogButtons> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     _titleForPhase(phase) { | ||||
|     private titleForPhase(phase: Phase): string { | ||||
|         switch (phase) { | ||||
|             case PHASE_CHOOSE_KEY_PASSPHRASE: | ||||
|             case Phase.ChooseKeyPassphrase: | ||||
|                 return _t('Set up Secure Backup'); | ||||
|             case PHASE_MIGRATE: | ||||
|             case Phase.Migrate: | ||||
|                 return _t('Upgrade your encryption'); | ||||
|             case PHASE_PASSPHRASE: | ||||
|             case Phase.Passphrase: | ||||
|                 return _t('Set a Security Phrase'); | ||||
|             case PHASE_PASSPHRASE_CONFIRM: | ||||
|             case Phase.PassphraseConfirm: | ||||
|                 return _t('Confirm Security Phrase'); | ||||
|             case PHASE_CONFIRM_SKIP: | ||||
|             case Phase.ConfirmSkip: | ||||
|                 return _t('Are you sure?'); | ||||
|             case PHASE_SHOWKEY: | ||||
|             case Phase.ShowKey: | ||||
|                 return _t('Save your Security Key'); | ||||
|             case PHASE_STORING: | ||||
|             case Phase.Storing: | ||||
|                 return _t('Setting up keys'); | ||||
|             default: | ||||
|                 return ''; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         let content; | ||||
|         if (this.state.error) { | ||||
|             content = <div> | ||||
|                 <p>{ _t("Unable to set up secret storage") }</p> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <DialogButtons primaryButton={_t('Retry')} | ||||
|                         onPrimaryButtonClick={this._bootstrapSecretStorage} | ||||
|                         onPrimaryButtonClick={this.bootstrapSecretStorage} | ||||
|                         hasCancel={this.state.canSkip} | ||||
|                         onCancel={this._onCancel} | ||||
|                         onCancel={this.onCancel} | ||||
|                     /> | ||||
|                 </div> | ||||
|             </div>; | ||||
|         } else { | ||||
|             switch (this.state.phase) { | ||||
|                 case PHASE_LOADING: | ||||
|                     content = this._renderBusyPhase(); | ||||
|                 case Phase.Loading: | ||||
|                     content = this.renderBusyPhase(); | ||||
|                     break; | ||||
|                 case PHASE_LOADERROR: | ||||
|                     content = this._renderPhaseLoadError(); | ||||
|                 case Phase.LoadError: | ||||
|                     content = this.renderPhaseLoadError(); | ||||
|                     break; | ||||
|                 case PHASE_CHOOSE_KEY_PASSPHRASE: | ||||
|                     content = this._renderPhaseChooseKeyPassphrase(); | ||||
|                 case Phase.ChooseKeyPassphrase: | ||||
|                     content = this.renderPhaseChooseKeyPassphrase(); | ||||
|                     break; | ||||
|                 case PHASE_MIGRATE: | ||||
|                     content = this._renderPhaseMigrate(); | ||||
|                 case Phase.Migrate: | ||||
|                     content = this.renderPhaseMigrate(); | ||||
|                     break; | ||||
|                 case PHASE_PASSPHRASE: | ||||
|                     content = this._renderPhasePassPhrase(); | ||||
|                 case Phase.Passphrase: | ||||
|                     content = this.renderPhasePassPhrase(); | ||||
|                     break; | ||||
|                 case PHASE_PASSPHRASE_CONFIRM: | ||||
|                     content = this._renderPhasePassPhraseConfirm(); | ||||
|                 case Phase.PassphraseConfirm: | ||||
|                     content = this.renderPhasePassPhraseConfirm(); | ||||
|                     break; | ||||
|                 case PHASE_SHOWKEY: | ||||
|                     content = this._renderPhaseShowKey(); | ||||
|                 case Phase.ShowKey: | ||||
|                     content = this.renderPhaseShowKey(); | ||||
|                     break; | ||||
|                 case PHASE_STORING: | ||||
|                     content = this._renderBusyPhase(); | ||||
|                 case Phase.Storing: | ||||
|                     content = this.renderBusyPhase(); | ||||
|                     break; | ||||
|                 case PHASE_CONFIRM_SKIP: | ||||
|                     content = this._renderPhaseSkipConfirm(); | ||||
|                 case Phase.ConfirmSkip: | ||||
|                     content = this.renderPhaseSkipConfirm(); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let titleClass = null; | ||||
|         switch (this.state.phase) { | ||||
|             case PHASE_PASSPHRASE: | ||||
|             case PHASE_PASSPHRASE_CONFIRM: | ||||
|             case Phase.Passphrase: | ||||
|             case Phase.PassphraseConfirm: | ||||
|                 titleClass = [ | ||||
|                     'mx_CreateSecretStorageDialog_titleWithIcon', | ||||
|                     'mx_CreateSecretStorageDialog_securePhraseTitle', | ||||
|                 ]; | ||||
|                 break; | ||||
|             case PHASE_SHOWKEY: | ||||
|             case Phase.ShowKey: | ||||
|                 titleClass = [ | ||||
|                     'mx_CreateSecretStorageDialog_titleWithIcon', | ||||
|                     'mx_CreateSecretStorageDialog_secureBackupTitle', | ||||
|                 ]; | ||||
|                 break; | ||||
|             case PHASE_CHOOSE_KEY_PASSPHRASE: | ||||
|             case Phase.ChooseKeyPassphrase: | ||||
|                 titleClass = 'mx_CreateSecretStorageDialog_centeredTitle'; | ||||
|                 break; | ||||
|         } | ||||
| @@ -854,9 +873,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { | ||||
|         return ( | ||||
|             <BaseDialog className='mx_CreateSecretStorageDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={this._titleForPhase(this.state.phase)} | ||||
|                 title={this.titleForPhase(this.state.phase)} | ||||
|                 titleClass={titleClass} | ||||
|                 hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} | ||||
|                 hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)} | ||||
|                 fixedWidth={false} | ||||
|             > | ||||
|                 <div> | ||||
| @@ -16,47 +16,51 @@ limitations under the License. | ||||
| 
 | ||||
| import FileSaver from 'file-saver'; | ||||
| import React, { createRef } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { _t } from '../../../../languageHandler'; | ||||
| 
 | ||||
| import { MatrixClient } from 'matrix-js-sdk/src/client'; | ||||
| import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; | ||||
| import * as sdk from '../../../../index'; | ||||
| 
 | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| 
 | ||||
| const PHASE_EDIT = 1; | ||||
| const PHASE_EXPORTING = 2; | ||||
| enum Phase { | ||||
|     Edit = "edit", | ||||
|     Exporting = "exporting", | ||||
| } | ||||
| 
 | ||||
| export default class ExportE2eKeysDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| interface IProps extends IDialogProps { | ||||
|     matrixClient: MatrixClient; | ||||
| } | ||||
| 
 | ||||
|     constructor(props) { | ||||
| interface IState { | ||||
|     phase: Phase; | ||||
|     errStr: string; | ||||
| } | ||||
| 
 | ||||
| export default class ExportE2eKeysDialog extends React.Component<IProps, IState> { | ||||
|     private unmounted = false; | ||||
|     private passphrase1 = createRef<HTMLInputElement>(); | ||||
|     private passphrase2 = createRef<HTMLInputElement>(); | ||||
| 
 | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._unmounted = false; | ||||
| 
 | ||||
|         this._passphrase1 = createRef(); | ||||
|         this._passphrase2 = createRef(); | ||||
| 
 | ||||
|         this.state = { | ||||
|             phase: PHASE_EDIT, | ||||
|             phase: Phase.Edit, | ||||
|             errStr: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         this._unmounted = true; | ||||
|     public componentWillUnmount(): void { | ||||
|         this.unmounted = true; | ||||
|     } | ||||
| 
 | ||||
|     _onPassphraseFormSubmit = (ev) => { | ||||
|     private onPassphraseFormSubmit = (ev: React.FormEvent): boolean => { | ||||
|         ev.preventDefault(); | ||||
| 
 | ||||
|         const passphrase = this._passphrase1.current.value; | ||||
|         if (passphrase !== this._passphrase2.current.value) { | ||||
|         const passphrase = this.passphrase1.current.value; | ||||
|         if (passphrase !== this.passphrase2.current.value) { | ||||
|             this.setState({ errStr: _t('Passphrases must match') }); | ||||
|             return false; | ||||
|         } | ||||
| @@ -65,11 +69,11 @@ export default class ExportE2eKeysDialog extends React.Component { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         this._startExport(passphrase); | ||||
|         this.startExport(passphrase); | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     _startExport(passphrase) { | ||||
|     private startExport(passphrase: string): void { | ||||
|         // extra Promise.resolve() to turn synchronous exceptions into
 | ||||
|         // asynchronous ones.
 | ||||
|         Promise.resolve().then(() => { | ||||
| @@ -86,39 +90,37 @@ export default class ExportE2eKeysDialog extends React.Component { | ||||
|             this.props.onFinished(true); | ||||
|         }).catch((e) => { | ||||
|             logger.error("Error exporting e2e keys:", e); | ||||
|             if (this._unmounted) { | ||||
|             if (this.unmounted) { | ||||
|                 return; | ||||
|             } | ||||
|             const msg = e.friendlyText || _t('Unknown error'); | ||||
|             this.setState({ | ||||
|                 errStr: msg, | ||||
|                 phase: PHASE_EDIT, | ||||
|                 phase: Phase.Edit, | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         this.setState({ | ||||
|             errStr: null, | ||||
|             phase: PHASE_EXPORTING, | ||||
|             phase: Phase.Exporting, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     _onCancelClick = (ev) => { | ||||
|     private onCancelClick = (ev: React.MouseEvent): boolean => { | ||||
|         ev.preventDefault(); | ||||
|         this.props.onFinished(false); | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
| 
 | ||||
|         const disableForm = (this.state.phase === PHASE_EXPORTING); | ||||
|     public render(): JSX.Element { | ||||
|         const disableForm = (this.state.phase === Phase.Exporting); | ||||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className='mx_exportE2eKeysDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={_t("Export room keys")} | ||||
|             > | ||||
|                 <form onSubmit={this._onPassphraseFormSubmit}> | ||||
|                 <form onSubmit={this.onPassphraseFormSubmit}> | ||||
|                     <div className="mx_Dialog_content"> | ||||
|                         <p> | ||||
|                             { _t( | ||||
| @@ -151,10 +153,10 @@ export default class ExportE2eKeysDialog extends React.Component { | ||||
|                                 </div> | ||||
|                                 <div className='mx_E2eKeysDialog_inputCell'> | ||||
|                                     <input | ||||
|                                         ref={this._passphrase1} | ||||
|                                         ref={this.passphrase1} | ||||
|                                         id='passphrase1' | ||||
|                                         autoFocus={true} | ||||
|                                         size='64' | ||||
|                                         size={64} | ||||
|                                         type='password' | ||||
|                                         disabled={disableForm} | ||||
|                                     /> | ||||
| @@ -167,9 +169,9 @@ export default class ExportE2eKeysDialog extends React.Component { | ||||
|                                     </label> | ||||
|                                 </div> | ||||
|                                 <div className='mx_E2eKeysDialog_inputCell'> | ||||
|                                     <input ref={this._passphrase2} | ||||
|                                     <input ref={this.passphrase2} | ||||
|                                         id='passphrase2' | ||||
|                                         size='64' | ||||
|                                         size={64} | ||||
|                                         type='password' | ||||
|                                         disabled={disableForm} | ||||
|                                     /> | ||||
| @@ -184,7 +186,7 @@ export default class ExportE2eKeysDialog extends React.Component { | ||||
|                             value={_t('Export')} | ||||
|                             disabled={disableForm} | ||||
|                         /> | ||||
|                         <button onClick={this._onCancelClick} disabled={disableForm}> | ||||
|                         <button onClick={this.onCancelClick} disabled={disableForm}> | ||||
|                             { _t("Cancel") } | ||||
|                         </button> | ||||
|                     </div> | ||||
| @@ -15,20 +15,19 @@ limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React, { createRef } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { MatrixClient } from 'matrix-js-sdk/src/client'; | ||||
| import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; | ||||
| import * as sdk from '../../../../index'; | ||||
| import { _t } from '../../../../languageHandler'; | ||||
| 
 | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import { logger } from "matrix-js-sdk/src/logger"; | ||||
| 
 | ||||
| function readFileAsArrayBuffer(file) { | ||||
| function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> { | ||||
|     return new Promise((resolve, reject) => { | ||||
|         const reader = new FileReader(); | ||||
|         reader.onload = (e) => { | ||||
|             resolve(e.target.result); | ||||
|             resolve(e.target.result as ArrayBuffer); | ||||
|         }; | ||||
|         reader.onerror = reject; | ||||
| 
 | ||||
| @@ -36,51 +35,57 @@ function readFileAsArrayBuffer(file) { | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| const PHASE_EDIT = 1; | ||||
| const PHASE_IMPORTING = 2; | ||||
| enum Phase { | ||||
|     Edit = "edit", | ||||
|     Importing = "importing", | ||||
| } | ||||
| 
 | ||||
| export default class ImportE2eKeysDialog extends React.Component { | ||||
|     static propTypes = { | ||||
|         matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }; | ||||
| interface IProps extends IDialogProps { | ||||
|     matrixClient: MatrixClient; | ||||
| } | ||||
| 
 | ||||
|     constructor(props) { | ||||
| interface IState { | ||||
|     enableSubmit: boolean; | ||||
|     phase: Phase; | ||||
|     errStr: string; | ||||
| } | ||||
| 
 | ||||
| export default class ImportE2eKeysDialog extends React.Component<IProps, IState> { | ||||
|     private unmounted = false; | ||||
|     private file = createRef<HTMLInputElement>(); | ||||
|     private passphrase = createRef<HTMLInputElement>(); | ||||
| 
 | ||||
|     constructor(props: IProps) { | ||||
|         super(props); | ||||
| 
 | ||||
|         this._unmounted = false; | ||||
| 
 | ||||
|         this._file = createRef(); | ||||
|         this._passphrase = createRef(); | ||||
| 
 | ||||
|         this.state = { | ||||
|             enableSubmit: false, | ||||
|             phase: PHASE_EDIT, | ||||
|             phase: Phase.Edit, | ||||
|             errStr: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         this._unmounted = true; | ||||
|     public componentWillUnmount(): void { | ||||
|         this.unmounted = true; | ||||
|     } | ||||
| 
 | ||||
|     _onFormChange = (ev) => { | ||||
|         const files = this._file.current.files || []; | ||||
|     private onFormChange = (ev: React.FormEvent): void => { | ||||
|         const files = this.file.current.files || []; | ||||
|         this.setState({ | ||||
|             enableSubmit: (this._passphrase.current.value !== "" && files.length > 0), | ||||
|             enableSubmit: (this.passphrase.current.value !== "" && files.length > 0), | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     _onFormSubmit = (ev) => { | ||||
|     private onFormSubmit = (ev: React.FormEvent): boolean => { | ||||
|         ev.preventDefault(); | ||||
|         this._startImport(this._file.current.files[0], this._passphrase.current.value); | ||||
|         this.startImport(this.file.current.files[0], this.passphrase.current.value); | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     _startImport(file, passphrase) { | ||||
|     private startImport(file: File, passphrase: string) { | ||||
|         this.setState({ | ||||
|             errStr: null, | ||||
|             phase: PHASE_IMPORTING, | ||||
|             phase: Phase.Importing, | ||||
|         }); | ||||
| 
 | ||||
|         return readFileAsArrayBuffer(file).then((arrayBuffer) => { | ||||
| @@ -94,34 +99,32 @@ export default class ImportE2eKeysDialog extends React.Component { | ||||
|             this.props.onFinished(true); | ||||
|         }).catch((e) => { | ||||
|             logger.error("Error importing e2e keys:", e); | ||||
|             if (this._unmounted) { | ||||
|             if (this.unmounted) { | ||||
|                 return; | ||||
|             } | ||||
|             const msg = e.friendlyText || _t('Unknown error'); | ||||
|             this.setState({ | ||||
|                 errStr: msg, | ||||
|                 phase: PHASE_EDIT, | ||||
|                 phase: Phase.Edit, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     _onCancelClick = (ev) => { | ||||
|     private onCancelClick = (ev: React.MouseEvent): boolean => { | ||||
|         ev.preventDefault(); | ||||
|         this.props.onFinished(false); | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
| 
 | ||||
|         const disableForm = (this.state.phase !== PHASE_EDIT); | ||||
|     public render(): JSX.Element { | ||||
|         const disableForm = (this.state.phase !== Phase.Edit); | ||||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className='mx_importE2eKeysDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={_t("Import room keys")} | ||||
|             > | ||||
|                 <form onSubmit={this._onFormSubmit}> | ||||
|                 <form onSubmit={this.onFormSubmit}> | ||||
|                     <div className="mx_Dialog_content"> | ||||
|                         <p> | ||||
|                             { _t( | ||||
| @@ -149,11 +152,11 @@ export default class ImportE2eKeysDialog extends React.Component { | ||||
|                                 </div> | ||||
|                                 <div className='mx_E2eKeysDialog_inputCell'> | ||||
|                                     <input | ||||
|                                         ref={this._file} | ||||
|                                         ref={this.file} | ||||
|                                         id='importFile' | ||||
|                                         type='file' | ||||
|                                         autoFocus={true} | ||||
|                                         onChange={this._onFormChange} | ||||
|                                         onChange={this.onFormChange} | ||||
|                                         disabled={disableForm} /> | ||||
|                                 </div> | ||||
|                             </div> | ||||
| @@ -165,11 +168,11 @@ export default class ImportE2eKeysDialog extends React.Component { | ||||
|                                 </div> | ||||
|                                 <div className='mx_E2eKeysDialog_inputCell'> | ||||
|                                     <input | ||||
|                                         ref={this._passphrase} | ||||
|                                         ref={this.passphrase} | ||||
|                                         id='passphrase' | ||||
|                                         size='64' | ||||
|                                         size={64} | ||||
|                                         type='password' | ||||
|                                         onChange={this._onFormChange} | ||||
|                                         onChange={this.onFormChange} | ||||
|                                         disabled={disableForm} /> | ||||
|                                 </div> | ||||
|                             </div> | ||||
| @@ -182,7 +185,7 @@ export default class ImportE2eKeysDialog extends React.Component { | ||||
|                             value={_t('Import')} | ||||
|                             disabled={!this.state.enableSubmit || disableForm} | ||||
|                         /> | ||||
|                         <button onClick={this._onCancelClick} disabled={disableForm}> | ||||
|                         <button onClick={this.onCancelClick} disabled={disableForm}> | ||||
|                             { _t("Cancel") } | ||||
|                         </button> | ||||
|                     </div> | ||||
| @@ -16,43 +16,40 @@ limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from "react"; | ||||
| import PropTypes from "prop-types"; | ||||
| import * as sdk from "../../../../index"; | ||||
| import { MatrixClientPeg } from '../../../../MatrixClientPeg'; | ||||
| import dis from "../../../../dispatcher/dispatcher"; | ||||
| import { _t } from "../../../../languageHandler"; | ||||
| import Modal from "../../../../Modal"; | ||||
| import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; | ||||
| import { Action } from "../../../../dispatcher/actions"; | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import DialogButtons from "../../../../components/views/elements/DialogButtons"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; | ||||
| 
 | ||||
| export default class NewRecoveryMethodDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         // As returned by js-sdk getKeyBackupVersion()
 | ||||
|         newVersionInfo: PropTypes.object, | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     } | ||||
| interface IProps extends IDialogProps { | ||||
|     newVersionInfo: IKeyBackupInfo; | ||||
| } | ||||
| 
 | ||||
|     onOkClick = () => { | ||||
| export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> { | ||||
|     private onOkClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     onGoToSettingsClick = () => { | ||||
|     private onGoToSettingsClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|         dis.fire(Action.ViewUserSettings); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     onSetupClick = async () => { | ||||
|     private onSetupClick = async (): Promise<void> => { | ||||
|         Modal.createTrackedDialog( | ||||
|             'Restore Backup', '', RestoreKeyBackupDialog, { | ||||
|                 onFinished: this.props.onFinished, | ||||
|             }, null, /* priority = */ false, /* static = */ true, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); | ||||
|         const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); | ||||
|     }; | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         const title = <span className="mx_KeyBackupFailedDialog_title"> | ||||
|             { _t("New Recovery Method") } | ||||
|         </span>; | ||||
| @@ -15,36 +15,32 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from "react"; | ||||
| import PropTypes from "prop-types"; | ||||
| import * as sdk from "../../../../index"; | ||||
| import React, { ComponentType } from "react"; | ||||
| import dis from "../../../../dispatcher/dispatcher"; | ||||
| import { _t } from "../../../../languageHandler"; | ||||
| import Modal from "../../../../Modal"; | ||||
| import { Action } from "../../../../dispatcher/actions"; | ||||
| import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; | ||||
| import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; | ||||
| import DialogButtons from "../../../../components/views/elements/DialogButtons"; | ||||
| 
 | ||||
| export default class RecoveryMethodRemovedDialog extends React.PureComponent { | ||||
|     static propTypes = { | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
|     } | ||||
| interface IProps extends IDialogProps {} | ||||
| 
 | ||||
|     onGoToSettingsClick = () => { | ||||
| export default class RecoveryMethodRemovedDialog extends React.PureComponent<IProps> { | ||||
|     private onGoToSettingsClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|         dis.fire(Action.ViewUserSettings); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     onSetupClick = () => { | ||||
|     private onSetupClick = (): void => { | ||||
|         this.props.onFinished(); | ||||
|         Modal.createTrackedDialogAsync("Key Backup", "Key Backup", | ||||
|             import("./CreateKeyBackupDialog"), | ||||
|             import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>, | ||||
|             null, null, /* priority = */ false, /* static = */ true, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); | ||||
|         const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); | ||||
|     }; | ||||
| 
 | ||||
|     public render(): JSX.Element { | ||||
|         const title = <span className="mx_KeyBackupFailedDialog_title"> | ||||
|             { _t("Recovery Method Removed") } | ||||
|         </span>; | ||||
| @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| import React, { createRef } from 'react'; | ||||
| import React, { ComponentType, createRef } from 'react'; | ||||
| import { createClient } from "matrix-js-sdk/src/matrix"; | ||||
| import { InvalidStoreError } from "matrix-js-sdk/src/errors"; | ||||
| import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | ||||
| @@ -1601,12 +1601,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> { | ||||
|  | ||||
|             if (haveNewVersion) { | ||||
|                 Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', | ||||
|                     import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'), | ||||
|                     import( | ||||
|                         '../../async-components/views/dialogs/security/NewRecoveryMethodDialog' | ||||
|                     ) as unknown as Promise<ComponentType<{}>>, | ||||
|                     { newVersionInfo }, | ||||
|                 ); | ||||
|             } else { | ||||
|                 Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed', | ||||
|                     import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'), | ||||
|                     import( | ||||
|                         '../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog' | ||||
|                     ) as unknown as Promise<ComponentType<{}>>, | ||||
|                 ); | ||||
|             } | ||||
|         }); | ||||
|   | ||||
| @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| import React from 'react'; | ||||
| import React, { ComponentType } from 'react'; | ||||
| import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; | ||||
| import Modal from '../../../Modal'; | ||||
| import * as sdk from '../../../index'; | ||||
| @@ -85,7 +85,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> { | ||||
|  | ||||
|     private onExportE2eKeysClicked = (): void => { | ||||
|         Modal.createTrackedDialogAsync('Export E2E Keys', '', | ||||
|             import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), | ||||
|             import( | ||||
|                 '../../../async-components/views/dialogs/security/ExportE2eKeysDialog' | ||||
|             ) as unknown as Promise<ComponentType<{}>>, | ||||
|             { | ||||
|                 matrixClient: MatrixClientPeg.get(), | ||||
|             }, | ||||
| @@ -111,7 +113,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> { | ||||
|             ); | ||||
|         } else { | ||||
|             Modal.createTrackedDialogAsync("Key Backup", "Key Backup", | ||||
|                 import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), | ||||
|                 import( | ||||
|                     "../../../async-components/views/dialogs/security/CreateKeyBackupDialog" | ||||
|                 ) as unknown as Promise<ComponentType<{}>>, | ||||
|                 null, null, /* priority = */ false, /* static = */ true, | ||||
|             ); | ||||
|         } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ limitations under the License. | ||||
| */ | ||||
|  | ||||
| import Field from "../elements/Field"; | ||||
| import React from 'react'; | ||||
| import React, { ComponentType } from 'react'; | ||||
| import { MatrixClientPeg } from "../../../MatrixClientPeg"; | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import Spinner from '../elements/Spinner'; | ||||
| @@ -186,7 +186,9 @@ export default class ChangePassword extends React.Component<IProps, IState> { | ||||
|  | ||||
|     private onExportE2eKeysClicked = (): void => { | ||||
|         Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', | ||||
|             import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), | ||||
|             import( | ||||
|                 '../../../async-components/views/dialogs/security/ExportE2eKeysDialog' | ||||
|             ) as unknown as Promise<ComponentType<{}>>, | ||||
|             { | ||||
|                 matrixClient: MatrixClientPeg.get(), | ||||
|             }, | ||||
|   | ||||
| @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| import React from 'react'; | ||||
| import React, { ComponentType } from 'react'; | ||||
|  | ||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| @@ -92,14 +92,18 @@ export default class CryptographyPanel extends React.Component<IProps, IState> { | ||||
|  | ||||
|     private onExportE2eKeysClicked = (): void => { | ||||
|         Modal.createTrackedDialogAsync('Export E2E Keys', '', | ||||
|             import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), | ||||
|             import( | ||||
|                 '../../../async-components/views/dialogs/security/ExportE2eKeysDialog' | ||||
|             ) as unknown as Promise<ComponentType<{}>>, | ||||
|             { matrixClient: MatrixClientPeg.get() }, | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     private onImportE2eKeysClicked = (): void => { | ||||
|         Modal.createTrackedDialogAsync('Import E2E Keys', '', | ||||
|             import('../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), | ||||
|             import( | ||||
|                 '../../../async-components/views/dialogs/security/ImportE2eKeysDialog' | ||||
|             ) as unknown as Promise<ComponentType<{}>>, | ||||
|             { matrixClient: MatrixClientPeg.get() }, | ||||
|         ); | ||||
|     }; | ||||
|   | ||||
| @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| import React from 'react'; | ||||
| import React, { ComponentType } from 'react'; | ||||
|  | ||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||
| import { _t } from '../../../languageHandler'; | ||||
| @@ -170,7 +170,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { | ||||
|  | ||||
|     private startNewBackup = (): void => { | ||||
|         Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', | ||||
|             import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'), | ||||
|             import( | ||||
|                 '../../../async-components/views/dialogs/security/CreateKeyBackupDialog' | ||||
|             ) as unknown as Promise<ComponentType<{}>>, | ||||
|             { | ||||
|                 onFinished: () => { | ||||
|                     this.loadBackupStatus(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user