/* Copyright 2024 New Vector Ltd. Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Copyright 2016 OpenMarket Ltd 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 { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { _t } from "../../../languageHandler"; import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth"; import { ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import StyledCheckbox from "../elements/StyledCheckbox"; import BaseDialog from "./BaseDialog"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; type DialogAesthetics = Partial<{ [x in AuthType]: { [x: number]: { body: string; continueText?: string; continueKind?: ContinueKind; }; }; }>; interface IProps { onFinished: (success?: boolean) => void; } interface IState { shouldErase: boolean; errStr: string | null; authData: any; // for UIA authEnabled: boolean; // see usages for information // A few strings that are passed to InteractiveAuth for design or are displayed // next to the InteractiveAuth component. bodyText?: string; continueText?: string; continueKind?: ContinueKind; } export default class DeactivateAccountDialog extends React.Component { public constructor(props: IProps) { super(props); this.state = { shouldErase: false, errStr: null, authData: null, // for UIA authEnabled: true, // see usages for information }; } public componentDidMount(): void { this.initAuth(/* shouldErase= */ false); } private onStagePhaseChange = (stage: AuthType, phase: number): void => { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { body: _t("settings|general|deactivate_confirm_body_sso"), continueText: _t("auth|sso"), continueKind: "danger", }, [SSOAuthEntry.PHASE_POSTAUTH]: { body: _t("settings|general|deactivate_confirm_body"), continueText: _t("settings|general|deactivate_confirm_continue"), continueKind: "danger", }, }; // This is the same as aestheticsForStagePhases in InteractiveAuthDialog minus the `title` const DEACTIVATE_AESTHETICS: DialogAesthetics = { [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, }; const aesthetics = DEACTIVATE_AESTHETICS[stage]; let bodyText: string | undefined; let continueText: string | undefined; let continueKind: ContinueKind | undefined; if (aesthetics) { const phaseAesthetics = aesthetics[phase]; if (phaseAesthetics) { if (phaseAesthetics.body) bodyText = phaseAesthetics.body; if (phaseAesthetics.continueText) continueText = phaseAesthetics.continueText; if (phaseAesthetics.continueKind) continueKind = phaseAesthetics.continueKind; } } this.setState({ bodyText, continueText, continueKind }); }; private onUIAuthFinished: InteractiveAuthCallback>> = async ( success, result, ) => { if (success) return; // great! makeRequest() will be called too. if (result === ERROR_USER_CANCELLED) { this.onCancel(); return; } logger.error("Error during UI Auth:", { result }); this.setState({ errStr: _t("settings|general|error_deactivate_communication") }); }; private onUIAuthComplete = (auth: IAuthData | null): void => { // XXX: this should be returning a promise to maintain the state inside the state machine correct // but given that a deactivation is followed by a local logout and all object instances being thrown away // this isn't done. MatrixClientPeg.safeGet() .deactivateAccount(auth ?? undefined, this.state.shouldErase) .then((r) => { // Deactivation worked - logout & close this dialog defaultDispatcher.fire(Action.TriggerLogout); this.props.onFinished(true); }) .catch((e) => { logger.error(e); this.setState({ errStr: _t("settings|general|error_deactivate_communication") }); }); }; private onEraseFieldChange = (ev: React.FormEvent): void => { this.setState({ shouldErase: ev.currentTarget.checked, // Disable the auth form because we're going to have to reinitialize the auth // information. We do this because we can't modify the parameters in the UIA // session, and the user will have selected something which changes the request. // Therefore, we throw away the last auth session and try a new one. authEnabled: false, }); // As mentioned above, set up for auth again to get updated UIA session info this.initAuth(/* shouldErase= */ ev.currentTarget.checked); }; private onCancel(): void { this.props.onFinished(false); } private initAuth(shouldErase: boolean): void { MatrixClientPeg.safeGet() .deactivateAccount(undefined, shouldErase) .then((r) => { // If we got here, oops. The server didn't require any auth. // Our application lifecycle will catch the error and do the logout bits. // We'll try to log something in an vain attempt to record what happened (storage // is also obliterated on logout). logger.warn("User's account got deactivated without confirmation: Server had no auth"); this.setState({ errStr: _t("settings|general|error_deactivate_no_auth") }); }) .catch((e) => { if (e && e.httpStatus === 401 && e.data) { // Valid UIA response this.setState({ authData: e.data, authEnabled: true }); } else { this.setState({ errStr: _t("settings|general|error_deactivate_invalid_auth") }); } }); } public render(): React.ReactNode { let error: JSX.Element | undefined; if (this.state.errStr) { error =
{this.state.errStr}
; } let auth =
{_t("common|loading")}
; if (this.state.authData && this.state.authEnabled) { auth = (
{this.state.bodyText}
); } // this is on purpose not a
to prevent Enter triggering submission, to further prevent accidents return (

{_t("settings|general|deactivate_confirm_content")}

  • {_t("settings|general|deactivate_confirm_content_1")}
  • {_t("settings|general|deactivate_confirm_content_2")}
  • {_t("settings|general|deactivate_confirm_content_3")}
  • {_t("settings|general|deactivate_confirm_content_4")}
  • {_t("settings|general|deactivate_confirm_content_5")}

{_t("settings|general|deactivate_confirm_content_6")}

{_t("settings|general|deactivate_confirm_erase_label")}

{error} {auth}
); } }