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