/* Copyright 2024 New Vector Ltd. Copyright 2020, 2021 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React from "react"; import { KeyBackupInfo, VerificationRequest } from "matrix-js-sdk/src/crypto-api"; import { logger } from "matrix-js-sdk/src/logger"; import { SecretStorageKeyDescription } from "matrix-js-sdk/src/secret-storage"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; import VerificationRequestDialog from "../../views/dialogs/VerificationRequestDialog"; import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore"; import EncryptionPanel from "../../views/right_panel/EncryptionPanel"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import Spinner from "../../views/elements/Spinner"; function keyHasPassphrase(keyInfo: SecretStorageKeyDescription): boolean { return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations); } interface IProps { onFinished: () => void; } interface IState { phase?: Phase; verificationRequest: VerificationRequest | null; backupInfo: KeyBackupInfo | null; lostKeys: boolean; } export default class SetupEncryptionBody extends React.Component { public constructor(props: IProps) { super(props); const store = SetupEncryptionStore.sharedInstance(); store.start(); this.state = { phase: store.phase, // this serves dual purpose as the object for the request logic and // the presence of it indicating that we're in 'verify mode'. // Because of the latter, it lives in the state. verificationRequest: store.verificationRequest, backupInfo: store.backupInfo, lostKeys: store.lostKeys(), }; } public componentDidMount(): void { const store = SetupEncryptionStore.sharedInstance(); store.on("update", this.onStoreUpdate); } private onStoreUpdate = (): void => { const store = SetupEncryptionStore.sharedInstance(); if (store.phase === Phase.Finished) { this.props.onFinished(); return; } this.setState({ phase: store.phase, verificationRequest: store.verificationRequest, backupInfo: store.backupInfo, lostKeys: store.lostKeys(), }); }; public componentWillUnmount(): void { const store = SetupEncryptionStore.sharedInstance(); store.off("update", this.onStoreUpdate); store.stop(); } private onUsePassphraseClick = async (): Promise => { const store = SetupEncryptionStore.sharedInstance(); store.usePassPhrase(); }; private onVerifyClick = (): void => { const cli = MatrixClientPeg.safeGet(); const userId = cli.getSafeUserId(); const requestPromise = cli.getCrypto()!.requestOwnUserVerification(); // We need to call onFinished now to close this dialog, and // again later to signal that the verification is complete. this.props.onFinished(); Modal.createDialog(VerificationRequestDialog, { verificationRequestPromise: requestPromise, member: cli.getUser(userId) ?? undefined, onFinished: async (): Promise => { const request = await requestPromise; request.cancel(); this.props.onFinished(); }, }); }; private onSkipConfirmClick = (): void => { const store = SetupEncryptionStore.sharedInstance(); store.skipConfirm(); }; private onSkipBackClick = (): void => { const store = SetupEncryptionStore.sharedInstance(); store.returnAfterSkip(); }; private onResetClick = (ev: ButtonEvent): void => { ev.preventDefault(); const store = SetupEncryptionStore.sharedInstance(); store.reset(); }; private onResetConfirmClick = (): void => { this.props.onFinished(); const store = SetupEncryptionStore.sharedInstance(); store.resetConfirm(); }; private onResetBackClick = (): void => { const store = SetupEncryptionStore.sharedInstance(); store.returnAfterReset(); }; private onDoneClick = (): void => { const store = SetupEncryptionStore.sharedInstance(); store.done(); }; private onEncryptionPanelClose = (): void => { this.props.onFinished(); }; public render(): React.ReactNode { const cli = MatrixClientPeg.safeGet(); const { phase, lostKeys } = this.state; if (this.state.verificationRequest && cli.getUser(this.state.verificationRequest.otherUserId)) { return ( ); } else if (phase === Phase.Intro) { if (lostKeys) { return (

{_t("encryption|verification|no_key_or_device")}

{_t("encryption|verification|reset_proceed_prompt")}
); } else { const store = SetupEncryptionStore.sharedInstance(); let recoveryKeyPrompt; if (store.keyInfo && keyHasPassphrase(store.keyInfo)) { recoveryKeyPrompt = _t("encryption|verification|verify_using_key_or_phrase"); } else if (store.keyInfo) { recoveryKeyPrompt = _t("encryption|verification|verify_using_key"); } let useRecoveryKeyButton; if (recoveryKeyPrompt) { useRecoveryKeyButton = ( {recoveryKeyPrompt} ); } let verifyButton; if (store.hasDevicesToVerifyAgainst) { verifyButton = ( {_t("encryption|verification|verify_using_device")} ); } return (

{_t("encryption|verification|verification_description")}

{verifyButton} {useRecoveryKeyButton}
{_t("encryption|reset_all_button", undefined, { a: (sub) => ( {sub} ), })}
); } } else if (phase === Phase.Done) { let message: JSX.Element; if (this.state.backupInfo) { message =

{_t("encryption|verification|verification_success_with_backup")}

; } else { message =

{_t("encryption|verification|verification_success_without_backup")}

; } return (
{message}
{_t("action|done")}
); } else if (phase === Phase.ConfirmSkip) { return (

{_t("encryption|verification|verification_skip_warning")}

{_t("encryption|verification|verify_later")} {_t("action|go_back")}
); } else if (phase === Phase.ConfirmReset) { return (

{_t("encryption|verification|verify_reset_warning_1")}

{_t("encryption|verification|verify_reset_warning_2")}

{_t("encryption|verification|reset_proceed_prompt")} {_t("action|go_back")}
); } else if (phase === Phase.Busy || phase === Phase.Loading) { return ; } else { logger.log(`SetupEncryptionBody: Unknown phase ${phase}`); } } }