/* Copyright 2024 New Vector Ltd. Copyright 2022-2024 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, { createRef, ReactNode } from "react"; import { ClientRendezvousFailureReason, LegacyRendezvousFailureReason, MSC4108FailureReason, } from "matrix-js-sdk/src/rendezvous"; import { Icon as ChevronLeftIcon } from "@vector-im/compound-design-tokens/icons/chevron-left.svg"; import { Icon as CheckCircleSolidIcon } from "@vector-im/compound-design-tokens/icons/check-circle-solid.svg"; import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"; import { Heading, MFAInput, Text } from "@vector-im/compound-web"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import QRCode from "../elements/QRCode"; import Spinner from "../elements/Spinner"; import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg"; import { Click, Phase } from "./LoginWithQR-types"; import SdkConfig from "../../../SdkConfig"; import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR"; import { XOR } from "../../../@types/common"; import { ErrorMessage } from "../../structures/ErrorMessage"; /** * @deprecated the MSC3906 implementation is deprecated in favour of MSC4108. */ interface MSC3906Props extends Pick { code?: string; confirmationDigits?: string; } interface Props { phase: Phase; code?: Uint8Array; onClick(type: Click, checkCodeEntered?: string): Promise; failureReason?: FailureReason; userCode?: string; checkCode?: string; } // n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed. // However, we want to keep this implementation around for some time. // TODO: define an end-of-life date for this implementation. /** * A component that implements the UI for sign in and E2EE set up with a QR code. * * This supports the unstable features of MSC3906 and MSC4108 */ export default class LoginWithQRFlow extends React.Component> { private checkCodeInput = createRef(); public constructor(props: XOR) { super(props); } private handleClick = (type: Click): ((e: React.FormEvent) => Promise) => { return async (e: React.FormEvent): Promise => { e.preventDefault(); await this.props.onClick(type, type === Click.Approve ? this.checkCodeInput.current?.value : undefined); }; }; private cancelButton = (): JSX.Element => ( {_t("action|cancel")} ); private simpleSpinner = (description?: string): JSX.Element => { return (
{description &&

{description}

}
); }; public render(): React.ReactNode { let main: JSX.Element | undefined; let buttons: JSX.Element | undefined; let backButton = true; let className = ""; switch (this.props.phase) { case Phase.Error: { let success = false; let title: string | undefined; let message: ReactNode | undefined; switch (this.props.failureReason) { case MSC4108FailureReason.UnsupportedProtocol: case LegacyRendezvousFailureReason.UnsupportedProtocol: title = _t("auth|qr_code_login|error_unsupported_protocol_title"); message = _t("auth|qr_code_login|error_unsupported_protocol"); break; case MSC4108FailureReason.UserCancelled: case LegacyRendezvousFailureReason.UserCancelled: title = _t("auth|qr_code_login|error_user_cancelled_title"); message = _t("auth|qr_code_login|error_user_cancelled"); break; case MSC4108FailureReason.AuthorizationExpired: case ClientRendezvousFailureReason.Expired: case LegacyRendezvousFailureReason.Expired: title = _t("auth|qr_code_login|error_expired_title"); message = _t("auth|qr_code_login|error_expired"); break; case ClientRendezvousFailureReason.InsecureChannelDetected: title = _t("auth|qr_code_login|error_insecure_channel_detected_title"); message = ( <> {_t("auth|qr_code_login|error_insecure_channel_detected")} {_t("auth|qr_code_login|error_insecure_channel_detected_instructions")}
  1. {_t("auth|qr_code_login|error_insecure_channel_detected_instructions_1")}
  2. {_t("auth|qr_code_login|error_insecure_channel_detected_instructions_2")}
  3. {_t("auth|qr_code_login|error_insecure_channel_detected_instructions_3")}
); break; case ClientRendezvousFailureReason.OtherDeviceAlreadySignedIn: success = true; title = _t("auth|qr_code_login|error_other_device_already_signed_in_title"); message = _t("auth|qr_code_login|error_other_device_already_signed_in"); break; case ClientRendezvousFailureReason.UserDeclined: title = _t("auth|qr_code_login|error_user_declined_title"); message = _t("auth|qr_code_login|error_user_declined"); break; case LoginWithQRFailureReason.RateLimited: title = _t("error|something_went_wrong"); message = _t("auth|qr_code_login|error_rate_limited"); break; case ClientRendezvousFailureReason.ETagMissing: title = _t("error|something_went_wrong"); message = _t("auth|qr_code_login|error_etag_missing"); break; case MSC4108FailureReason.DeviceAlreadyExists: case MSC4108FailureReason.DeviceNotFound: case MSC4108FailureReason.UnexpectedMessageReceived: case ClientRendezvousFailureReason.OtherDeviceNotSignedIn: case ClientRendezvousFailureReason.Unknown: default: title = _t("error|something_went_wrong"); message = _t("auth|qr_code_login|error_unexpected"); break; } className = "mx_LoginWithQR_error"; backButton = false; main = ( <>
{success ? : }
{title} {typeof message === "object" ? message :

{message}

} ); break; } case Phase.LegacyConnected: backButton = false; main = ( <>

{_t("auth|qr_code_login|confirm_code_match")}

{this.props.confirmationDigits}
{_t("auth|qr_code_login|approve_access_warning")}
); buttons = ( <> {_t("action|approve")} {_t("action|cancel")} ); break; case Phase.OutOfBandConfirmation: backButton = false; main = ( <> {_t("auth|qr_code_login|check_code_heading")} {_t("auth|qr_code_login|check_code_explainer")} ); buttons = ( <> {_t("action|continue")} {_t("action|cancel")} ); break; case Phase.ShowingQR: if (this.props.code) { const data = typeof this.props.code !== "string" ? this.props.code : Buffer.from(this.props.code ?? ""); main = ( <> {_t("auth|qr_code_login|scan_code_instruction")}
  1. {_t("auth|qr_code_login|open_element_other_device", { brand: SdkConfig.get().brand, })}
  2. {_t("auth|qr_code_login|select_qr_code", { scanQRCode: {_t("auth|qr_code_login|scan_qr_code")}, })}
  3. {_t("auth|qr_code_login|point_the_camera")}
  4. {_t("auth|qr_code_login|follow_remaining_instructions")}
); } else { main = this.simpleSpinner(); buttons = this.cancelButton(); } break; case Phase.Loading: main = this.simpleSpinner(); break; case Phase.WaitingForDevice: main = ( <> {this.simpleSpinner(_t("auth|qr_code_login|waiting_for_device"))} {this.props.userCode ? (

{_t("auth|qr_code_login|security_code")}

{_t("auth|qr_code_login|security_code_prompt")}

{this.props.userCode}

) : null} ); buttons = this.cancelButton(); break; case Phase.Verifying: main = this.simpleSpinner(_t("auth|qr_code_login|completing_setup")); break; } return (
{backButton ? (
{_t("settings|sessions|title")} / {_t("settings|sessions|sign_in_with_qr")}
) : null}
{main}
{buttons}
); } }