Files
ThreadNet-Web/src/components/views/dialogs/InteractiveAuthDialog.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

202 lines
7.1 KiB
TypeScript
Raw Normal View History

2016-10-11 18:04:55 +01:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
2024-09-09 14:57:16 +01:00
Copyright 2017 Vector Creations Ltd
Copyright 2016 OpenMarket Ltd
2016-10-11 18:04:55 +01:00
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
2016-10-11 18:04:55 +01:00
*/
import React from "react";
2025-02-05 13:25:06 +00:00
import { type MatrixClient, type UIAResponse } from "matrix-js-sdk/src/matrix";
import { type AuthType } from "matrix-js-sdk/src/interactive-auth";
2016-10-11 18:04:55 +01:00
2017-05-25 18:20:48 +01:00
import { _t } from "../../../languageHandler";
2017-02-13 16:03:21 +00:00
import AccessibleButton from "../elements/AccessibleButton";
import InteractiveAuth, {
ERROR_USER_CANCELLED,
2025-02-05 13:25:06 +00:00
type InteractiveAuthCallback,
type InteractiveAuthProps,
} from "../../structures/InteractiveAuth";
2025-02-05 13:25:06 +00:00
import { type ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
2021-09-05 16:57:36 +02:00
import BaseDialog from "./BaseDialog";
import { Linkify } from "../../../Linkify";
2021-09-05 16:57:36 +02:00
2023-02-13 11:39:16 +00:00
type DialogAesthetics = Partial<{
[x in AuthType]: {
2021-09-13 18:12:47 +02:00
[x: number]: {
title: string;
body: string;
continueText: string;
continueKind: ContinueKind;
2021-09-13 18:12:47 +02:00
};
};
2023-02-13 11:39:16 +00:00
}>;
2021-09-13 18:12:47 +02:00
export interface InteractiveAuthDialogProps<T = unknown>
extends Pick<InteractiveAuthProps<T>, "makeRequest" | "authData"> {
2021-09-05 16:57:36 +02:00
// matrix client to use for UI auth requests
matrixClient: MatrixClient;
// Optional title and body to show when not showing a particular stage
title?: string;
body?: string;
// Optional title and body pairs for particular stages and phases within
// those stages. Object structure/example is:
// {
// "org.example.stage_type": {
// 1: {
// "body": "This is a body for phase 1" of org.example.stage_type,
// "title": "Title for phase 1 of org.example.stage_type"
// },
// 2: {
// "body": "This is a body for phase 2 of org.example.stage_type",
// "title": "Title for phase 2 of org.example.stage_type"
// "continueText": "Confirm identity with Example Auth",
// "continueKind": "danger"
// }
// }
// }
//
// Default is defined in _getDefaultDialogAesthetics()
2023-02-13 11:39:16 +00:00
aestheticsForStagePhases?: DialogAesthetics;
onFinished(success?: boolean, result?: UIAResponse<T> | Error | null): void;
2021-09-05 16:57:36 +02:00
}
interface IState {
authError: Error | null;
2021-09-05 16:57:36 +02:00
// See _onUpdateStagePhase()
2023-02-13 11:39:16 +00:00
uiaStage: AuthType | null;
uiaStagePhase: number | null;
2021-09-05 16:57:36 +02:00
}
2016-10-11 18:04:55 +01:00
export default class InteractiveAuthDialog<T> extends React.Component<InteractiveAuthDialogProps<T>, IState> {
public constructor(props: InteractiveAuthDialogProps<T>) {
2021-09-05 16:57:36 +02:00
super(props);
2021-09-05 16:57:36 +02:00
this.state = {
authError: null,
2016-10-11 18:04:55 +01:00
2021-09-05 16:57:36 +02:00
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
};
}
2016-10-11 18:04:55 +01:00
2023-02-13 11:39:16 +00:00
private getDefaultDialogAesthetics(): DialogAesthetics {
const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("auth|uia|sso_title"),
body: _t("auth|uia|sso_preauth_body"),
continueText: _t("auth|sso"),
continueKind: "primary",
},
[SSOAuthEntry.PHASE_POSTAUTH]: {
title: _t("auth|uia|sso_postauth_title"),
body: _t("auth|uia|sso_postauth_body"),
continueText: _t("action|confirm"),
continueKind: "primary",
},
};
return {
[SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics,
2020-05-06 14:27:32 -06:00
};
2020-08-29 12:14:16 +01:00
}
private onAuthFinished: InteractiveAuthCallback<T> = async (success, result): Promise<void> => {
2017-03-03 12:08:26 +00:00
if (success) {
2017-03-03 14:31:52 +00:00
this.props.onFinished(true, result);
2017-03-03 12:08:26 +00:00
} else {
if (result === ERROR_USER_CANCELLED) {
this.props.onFinished(false, null);
} else {
this.setState({
2023-02-13 11:39:16 +00:00
authError: result as Error,
});
}
2017-03-03 12:08:26 +00:00
}
2020-08-29 12:14:16 +01:00
};
2017-03-03 12:08:26 +00:00
2023-02-13 11:39:16 +00:00
private onUpdateStagePhase = (newStage: AuthType, newPhase: number): void => {
// We copy the stage and stage phase params into state for title selection in render()
2021-06-29 13:11:58 +01:00
this.setState({ uiaStage: newStage, uiaStagePhase: newPhase });
2020-08-29 12:14:16 +01:00
};
2021-09-05 16:57:36 +02:00
private onDismissClick = (): void => {
2017-03-03 12:08:26 +00:00
this.props.onFinished(false);
2020-08-29 12:14:16 +01:00
};
2017-03-03 12:08:26 +00:00
public render(): React.ReactNode {
// Let's pick a title, body, and other params text that we'll show to the user. The order
// is most specific first, so stagePhase > our props > defaults.
let title = this.state.authError ? "Error" : this.props.title || _t("common|authentication");
let body = this.state.authError ? null : this.props.body;
let continueText: string | undefined;
let continueKind: ContinueKind | undefined;
2021-09-05 16:57:36 +02:00
const dialogAesthetics = this.props.aestheticsForStagePhases || this.getDefaultDialogAesthetics();
if (!this.state.authError && dialogAesthetics) {
if (
this.state.uiaStage !== null &&
this.state.uiaStagePhase !== null &&
dialogAesthetics[this.state.uiaStage]
) {
const aesthetics = dialogAesthetics[this.state.uiaStage]![this.state.uiaStagePhase];
if (aesthetics) {
if (aesthetics.title) title = aesthetics.title;
if (aesthetics.body) body = aesthetics.body;
if (aesthetics.continueText) continueText = aesthetics.continueText;
if (aesthetics.continueKind) continueKind = aesthetics.continueKind;
}
}
}
let content: JSX.Element;
2017-03-03 12:08:26 +00:00
if (this.state.authError) {
content = (
<div id="mx_Dialog_content">
<Linkify>
<div role="alert">{this.state.authError.message || this.state.authError.toString()}</div>
</Linkify>
2017-03-03 12:08:26 +00:00
<br />
2021-09-05 16:57:36 +02:00
<AccessibleButton onClick={this.onDismissClick} className="mx_GeneralButton" autoFocus={true}>
{_t("action|dismiss")}
2017-03-03 12:08:26 +00:00
</AccessibleButton>
</div>
);
} else {
content = (
<div id="mx_Dialog_content">
{body}
<InteractiveAuth
matrixClient={this.props.matrixClient}
2017-02-13 16:03:21 +00:00
authData={this.props.authData}
makeRequest={this.props.makeRequest}
2021-09-05 16:57:36 +02:00
onAuthFinished={this.onAuthFinished}
onStagePhaseChange={this.onUpdateStagePhase}
continueText={continueText}
continueKind={continueKind}
2017-02-13 16:03:21 +00:00
/>
2016-10-11 18:04:55 +01:00
</div>
2017-03-03 12:08:26 +00:00
);
}
return (
<BaseDialog
className="mx_InteractiveAuthDialog"
onFinished={this.props.onFinished}
title={title}
contentId="mx_Dialog_content"
2017-03-03 12:08:26 +00:00
>
2017-10-11 17:56:17 +01:00
{content}
2017-01-24 15:54:05 +00:00
</BaseDialog>
2016-10-11 18:04:55 +01:00
);
2020-08-29 12:14:16 +01:00
}
}