* Move state update listeners from constructor to componentDidMount Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
233 lines
9.4 KiB
TypeScript
233 lines
9.4 KiB
TypeScript
/*
|
|
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<IProps, IState> {
|
|
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<Awaited<ReturnType<MatrixClient["deactivateAccount"]>>> = 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<HTMLInputElement>): 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 = <div className="error">{this.state.errStr}</div>;
|
|
}
|
|
|
|
let auth = <div>{_t("common|loading")}</div>;
|
|
if (this.state.authData && this.state.authEnabled) {
|
|
auth = (
|
|
<div>
|
|
{this.state.bodyText}
|
|
<InteractiveAuth
|
|
matrixClient={MatrixClientPeg.safeGet()}
|
|
authData={this.state.authData}
|
|
// XXX: onUIAuthComplete breaches the expected method contract, it gets away with it because it
|
|
// knows the entire app is about to die as a result of the account deactivation.
|
|
makeRequest={this.onUIAuthComplete as any}
|
|
onAuthFinished={this.onUIAuthFinished}
|
|
onStagePhaseChange={this.onStagePhaseChange}
|
|
continueText={this.state.continueText}
|
|
continueKind={this.state.continueKind}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// this is on purpose not a <form /> to prevent Enter triggering submission, to further prevent accidents
|
|
return (
|
|
<BaseDialog
|
|
className="mx_DeactivateAccountDialog"
|
|
onFinished={this.props.onFinished}
|
|
titleClass="danger"
|
|
title={_t("settings|general|deactivate_section")}
|
|
screenName="DeactivateAccount"
|
|
>
|
|
<div className="mx_Dialog_content">
|
|
<p>{_t("settings|general|deactivate_confirm_content")}</p>
|
|
<ul>
|
|
<li>{_t("settings|general|deactivate_confirm_content_1")}</li>
|
|
<li>{_t("settings|general|deactivate_confirm_content_2")}</li>
|
|
<li>{_t("settings|general|deactivate_confirm_content_3")}</li>
|
|
<li>{_t("settings|general|deactivate_confirm_content_4")}</li>
|
|
<li>{_t("settings|general|deactivate_confirm_content_5")}</li>
|
|
</ul>
|
|
<p>{_t("settings|general|deactivate_confirm_content_6")}</p>
|
|
|
|
<div className="mx_DeactivateAccountDialog_input_section">
|
|
<p>
|
|
<StyledCheckbox checked={this.state.shouldErase} onChange={this.onEraseFieldChange}>
|
|
{_t("settings|general|deactivate_confirm_erase_label")}
|
|
</StyledCheckbox>
|
|
</p>
|
|
{error}
|
|
{auth}
|
|
</div>
|
|
</div>
|
|
</BaseDialog>
|
|
);
|
|
}
|
|
}
|