Remove abandoned MSC3886, MSC3903, MSC3906 implementations (#28274)
* Remove abandoned MSC3886, MSC3903, MSC3906 implementations Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove stale snapshots * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
6d0d237c79
commit
5b5348ec1e
13 changed files with 60 additions and 1373 deletions
|
@ -24,10 +24,6 @@ export enum Phase {
|
|||
WaitingForDevice,
|
||||
Verifying,
|
||||
Error,
|
||||
/**
|
||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
||||
*/
|
||||
LegacyConnected,
|
||||
}
|
||||
|
||||
export enum Click {
|
||||
|
|
|
@ -9,11 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
|||
import React from "react";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
LegacyRendezvousFailureReason,
|
||||
MSC3886SimpleHttpRendezvousTransport,
|
||||
MSC3903ECDHPayload,
|
||||
MSC3903ECDHv2RendezvousChannel,
|
||||
MSC3906Rendezvous,
|
||||
MSC4108FailureReason,
|
||||
MSC4108RendezvousSession,
|
||||
MSC4108SecureChannel,
|
||||
|
@ -23,29 +18,21 @@ import {
|
|||
RendezvousIntent,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||
import { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
interface IProps {
|
||||
client: MatrixClient;
|
||||
mode: Mode;
|
||||
legacy: boolean;
|
||||
onFinished(...args: any): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
rendezvous?: MSC3906Rendezvous | MSC4108SignInWithQR;
|
||||
rendezvous?: MSC4108SignInWithQR;
|
||||
mediaPermissionError?: boolean;
|
||||
|
||||
// MSC3906
|
||||
confirmationDigits?: string;
|
||||
|
||||
// MSC4108
|
||||
verificationUri?: string;
|
||||
userCode?: string;
|
||||
checkCode?: string;
|
||||
|
@ -54,25 +41,18 @@ interface IState {
|
|||
}
|
||||
|
||||
export enum LoginWithQRFailureReason {
|
||||
/**
|
||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
||||
*/
|
||||
RateLimited = "rate_limited",
|
||||
CheckCodeMismatch = "check_code_mismatch",
|
||||
}
|
||||
|
||||
export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason;
|
||||
|
||||
// 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 allows sign in and E2EE set up with a QR code.
|
||||
*
|
||||
* It implements `login.reciprocate` capabilities and showing QR codes.
|
||||
*
|
||||
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
||||
* This uses the unstable feature of MSC4108: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
|
||||
*/
|
||||
export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
private finished = false;
|
||||
|
@ -104,9 +84,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
if (this.state.rendezvous) {
|
||||
const rendezvous = this.state.rendezvous;
|
||||
rendezvous.onFailure = undefined;
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
}
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
if (mode === Mode.Show) {
|
||||
|
@ -119,60 +96,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
this.state.rendezvous.onFailure = undefined;
|
||||
// calling cancel will call close() as well to clean up the resources
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
this.state.rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async legacyApproveLogin(): Promise<void> {
|
||||
if (!(this.state.rendezvous instanceof MSC3906Rendezvous)) {
|
||||
throw new Error("Rendezvous not found");
|
||||
}
|
||||
if (!this.props.client) {
|
||||
throw new Error("No client to approve login with");
|
||||
}
|
||||
this.setState({ phase: Phase.Loading });
|
||||
|
||||
try {
|
||||
logger.info("Requesting login token");
|
||||
|
||||
const { login_token: loginToken } = await wrapRequestWithDialog(this.props.client.requestLoginToken, {
|
||||
matrixClient: this.props.client,
|
||||
title: _t("auth|qr_code_login|sign_in_new_device"),
|
||||
})();
|
||||
|
||||
this.setState({ phase: Phase.WaitingForDevice });
|
||||
|
||||
const newDeviceId = await this.state.rendezvous.approveLoginOnExistingDevice(loginToken);
|
||||
if (!newDeviceId) {
|
||||
// user denied
|
||||
return;
|
||||
}
|
||||
if (!this.props.client.getCrypto()) {
|
||||
// no E2EE to set up
|
||||
this.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({ phase: Phase.Verifying });
|
||||
await this.state.rendezvous.verifyNewDeviceOnExistingDevice();
|
||||
// clean up our state:
|
||||
try {
|
||||
await this.state.rendezvous.close();
|
||||
} finally {
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
this.onFinished(true);
|
||||
} catch (e) {
|
||||
logger.error("Error whilst approving sign in", e);
|
||||
if (e instanceof HTTPError && e.httpStatus === 429) {
|
||||
// 429: rate limit
|
||||
this.setState({ phase: Phase.Error, failureReason: LoginWithQRFailureReason.RateLimited });
|
||||
return;
|
||||
}
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown });
|
||||
this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,28 +106,18 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private generateAndShowCode = async (): Promise<void> => {
|
||||
let rendezvous: MSC4108SignInWithQR | MSC3906Rendezvous;
|
||||
let rendezvous: MSC4108SignInWithQR;
|
||||
try {
|
||||
const fallbackRzServer = this.props.client?.getClientWellKnown()?.["io.element.rendezvous"]?.server;
|
||||
|
||||
if (this.props.legacy) {
|
||||
const transport = new MSC3886SimpleHttpRendezvousTransport<MSC3903ECDHPayload>({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
const channel = new MSC3903ECDHv2RendezvousChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC3906Rendezvous(channel, this.props.client, this.onFailure);
|
||||
} else {
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
}
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
|
||||
await rendezvous.generateCode();
|
||||
this.setState({
|
||||
|
@ -218,10 +132,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
try {
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
const confirmationDigits = await rendezvous.startAfterShowingCode();
|
||||
this.setState({ phase: Phase.LegacyConnected, confirmationDigits });
|
||||
} else if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
||||
if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
||||
// MSC4108-Flow: NewScanned
|
||||
await rendezvous.negotiateProtocols();
|
||||
const { verificationUri } = await rendezvous.deviceAuthorizationGrant();
|
||||
|
@ -234,18 +145,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
// we ask the user to confirm that the channel is secure
|
||||
} catch (e: RendezvousError | unknown) {
|
||||
logger.error("Error whilst approving login", e);
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
// only set to error phase if it hasn't already been set by onFailure or similar
|
||||
if (this.state.phase !== Phase.Error) {
|
||||
this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown });
|
||||
}
|
||||
} else {
|
||||
await rendezvous?.cancel(
|
||||
e instanceof RendezvousError
|
||||
? (e.code as MSC4108FailureReason)
|
||||
: ClientRendezvousFailureReason.Unknown,
|
||||
);
|
||||
}
|
||||
await rendezvous?.cancel(
|
||||
e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -298,7 +200,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
public reset(): void {
|
||||
this.setState({
|
||||
rendezvous: undefined,
|
||||
confirmationDigits: undefined,
|
||||
verificationUri: undefined,
|
||||
failureReason: undefined,
|
||||
userCode: undefined,
|
||||
|
@ -311,16 +212,12 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
private onClick = async (type: Click, checkCode?: string): Promise<void> => {
|
||||
switch (type) {
|
||||
case Click.Cancel:
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
this.reset();
|
||||
this.onFinished(false);
|
||||
break;
|
||||
case Click.Approve:
|
||||
await (this.props.legacy ? this.legacyApproveLogin() : this.approveLogin(checkCode));
|
||||
await this.approveLogin(checkCode);
|
||||
break;
|
||||
case Click.Decline:
|
||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||
|
@ -328,11 +225,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
this.onFinished(false);
|
||||
break;
|
||||
case Click.Back:
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
this.onFinished(false);
|
||||
break;
|
||||
case Click.ShowQr:
|
||||
|
@ -342,20 +235,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
return (
|
||||
<LoginWithQRFlow
|
||||
onClick={this.onClick}
|
||||
phase={this.state.phase}
|
||||
code={this.state.phase === Phase.ShowingQR ? this.state.rendezvous?.code : undefined}
|
||||
confirmationDigits={
|
||||
this.state.phase === Phase.LegacyConnected ? this.state.confirmationDigits : undefined
|
||||
}
|
||||
failureReason={this.state.failureReason}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginWithQRFlow
|
||||
onClick={this.onClick}
|
||||
|
|
|
@ -7,11 +7,7 @@ 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 { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||
import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
|
||||
import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
|
||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||
|
@ -23,21 +19,11 @@ 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<Props, "phase" | "onClick" | "failureReason"> {
|
||||
code?: string;
|
||||
confirmationDigits?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
phase: Phase;
|
||||
code?: Uint8Array;
|
||||
|
@ -47,19 +33,15 @@ interface Props {
|
|||
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
|
||||
* This supports the unstable features of MSC4108
|
||||
*/
|
||||
export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906Props>> {
|
||||
export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
private checkCodeInput = createRef<HTMLInputElement>();
|
||||
|
||||
public constructor(props: XOR<Props, MSC3906Props>) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
@ -104,20 +86,17 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
|||
|
||||
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;
|
||||
|
@ -162,7 +141,6 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
|||
message = _t("auth|qr_code_login|error_etag_missing");
|
||||
break;
|
||||
|
||||
case LegacyRendezvousFailureReason.HomeserverLacksSupport:
|
||||
case ClientRendezvousFailureReason.HomeserverLacksSupport:
|
||||
success = null;
|
||||
Icon = QrCodeIcon;
|
||||
|
@ -200,40 +178,6 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
|||
);
|
||||
break;
|
||||
}
|
||||
case Phase.LegacyConnected:
|
||||
backButton = false;
|
||||
main = (
|
||||
<>
|
||||
<p>{_t("auth|qr_code_login|confirm_code_match")}</p>
|
||||
<div className="mx_LoginWithQR_confirmationDigits">{this.props.confirmationDigits}</div>
|
||||
<div className="mx_LoginWithQR_confirmationAlert">
|
||||
<div>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
<div>{_t("auth|qr_code_login|approve_access_warning")}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
data-testid="approve-login-button"
|
||||
kind="primary"
|
||||
onClick={this.handleClick(Click.Approve)}
|
||||
>
|
||||
{_t("action|approve")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.handleClick(Click.Decline)}
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case Phase.OutOfBandConfirmation:
|
||||
backButton = false;
|
||||
main = (
|
||||
|
@ -288,8 +232,7 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
|||
break;
|
||||
case Phase.ShowingQR:
|
||||
if (this.props.code) {
|
||||
const data =
|
||||
typeof this.props.code !== "string" ? this.props.code : Buffer.from(this.props.code ?? "");
|
||||
const data = this.props.code;
|
||||
|
||||
main = (
|
||||
<>
|
||||
|
|
|
@ -8,10 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import React from "react";
|
||||
import {
|
||||
IGetLoginTokenCapability,
|
||||
IServerVersions,
|
||||
GET_LOGIN_TOKEN_CAPABILITY,
|
||||
Capabilities,
|
||||
IClientWellKnown,
|
||||
OidcClientConfig,
|
||||
MatrixClient,
|
||||
|
@ -28,27 +25,11 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext
|
|||
interface IProps {
|
||||
onShowQr: () => void;
|
||||
versions?: IServerVersions;
|
||||
capabilities?: Capabilities;
|
||||
wellKnown?: IClientWellKnown;
|
||||
oidcClientConfig?: OidcClientConfig;
|
||||
isCrossSigningReady?: boolean;
|
||||
}
|
||||
|
||||
function shouldShowQrLegacy(
|
||||
versions?: IServerVersions,
|
||||
wellKnown?: IClientWellKnown,
|
||||
capabilities?: Capabilities,
|
||||
): boolean {
|
||||
// Needs server support for (get_login_token or OIDC Device Authorization Grant) and MSC3886:
|
||||
// in r0 of MSC3882 it is exposed as a feature flag, but in stable and unstable r1 it is a capability
|
||||
const loginTokenCapability = GET_LOGIN_TOKEN_CAPABILITY.findIn<IGetLoginTokenCapability>(capabilities);
|
||||
const getLoginTokenSupported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3882"] || !!loginTokenCapability?.enabled;
|
||||
const msc3886Supported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3886"] || !!wellKnown?.["io.element.rendezvous"]?.server;
|
||||
return getLoginTokenSupported && msc3886Supported;
|
||||
}
|
||||
|
||||
export function shouldShowQr(
|
||||
cli: MatrixClient,
|
||||
isCrossSigningReady: boolean,
|
||||
|
@ -73,15 +54,12 @@ export function shouldShowQr(
|
|||
const LoginWithQRSection: React.FC<IProps> = ({
|
||||
onShowQr,
|
||||
versions,
|
||||
capabilities,
|
||||
wellKnown,
|
||||
oidcClientConfig,
|
||||
isCrossSigningReady,
|
||||
}) => {
|
||||
const cli = useMatrixClientContext();
|
||||
const offerShowQr = oidcClientConfig
|
||||
? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown)
|
||||
: shouldShowQrLegacy(versions, wellKnown, capabilities);
|
||||
const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown);
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||
|
|
|
@ -181,7 +181,6 @@ const SessionManagerTab: React.FC<{
|
|||
const userId = matrixClient?.getUserId();
|
||||
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
|
||||
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
|
||||
const oidcClientConfig = useAsyncMemo(async () => {
|
||||
try {
|
||||
|
@ -292,12 +291,7 @@ const SessionManagerTab: React.FC<{
|
|||
if (signInWithQrMode) {
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LoginWithQR
|
||||
mode={signInWithQrMode}
|
||||
onFinished={onQrFinish}
|
||||
client={matrixClient}
|
||||
legacy={!oidcClientConfig && !showMsc4108QrCode}
|
||||
/>
|
||||
<LoginWithQR mode={signInWithQrMode} onFinished={onQrFinish} client={matrixClient} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
@ -308,7 +302,6 @@ const SessionManagerTab: React.FC<{
|
|||
<LoginWithQRSection
|
||||
onShowQr={onShowQrClicked}
|
||||
versions={clientVersions}
|
||||
capabilities={capabilities}
|
||||
wellKnown={wellKnown}
|
||||
oidcClientConfig={oidcClientConfig}
|
||||
isCrossSigningReady={isCrossSigningReady}
|
||||
|
|
|
@ -250,13 +250,11 @@
|
|||
"phone_label": "Phone",
|
||||
"phone_optional_label": "Phone (optional)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "By approving access for this device, it will have full access to your account.",
|
||||
"check_code_explainer": "This will verify that the connection to your other device is secure.",
|
||||
"check_code_heading": "Enter the number shown on your other device",
|
||||
"check_code_input_label": "2-digit code",
|
||||
"check_code_mismatch": "The numbers don't match",
|
||||
"completing_setup": "Completing set up of your new device",
|
||||
"confirm_code_match": "Check that the code below matches with your other device:",
|
||||
"error_etag_missing": "An unexpected error occurred. This may be due to a browser extension, proxy server, or server misconfiguration.",
|
||||
"error_expired": "Sign in expired. Please try again.",
|
||||
"error_expired_title": "The sign in was not completed in time",
|
||||
|
@ -284,7 +282,6 @@
|
|||
"security_code": "Security code",
|
||||
"security_code_prompt": "If asked, enter the code below on your other device.",
|
||||
"select_qr_code": "Select \"%(scanQRCode)s\"",
|
||||
"sign_in_new_device": "Sign in new device",
|
||||
"unsupported_explainer": "Your account provider doesn't support signing into a new device with a QR code.",
|
||||
"unsupported_heading": "QR code not supported",
|
||||
"waiting_for_device": "Waiting for device to sign in"
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 { AuthDict } from "matrix-js-sdk/src/interactive-auth";
|
||||
import { UIAResponse } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import Modal from "../Modal";
|
||||
import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "../components/views/dialogs/InteractiveAuthDialog";
|
||||
|
||||
type FunctionWithUIA<R, A> = (auth?: AuthDict, ...args: A[]) => Promise<UIAResponse<R>>;
|
||||
|
||||
export function wrapRequestWithDialog<R, A = any>(
|
||||
requestFunction: FunctionWithUIA<R, A>,
|
||||
opts: Omit<InteractiveAuthDialogProps<R>, "makeRequest" | "onFinished">,
|
||||
): (...args: A[]) => Promise<R> {
|
||||
return async function (...args): Promise<R> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const boundFunction = requestFunction.bind(opts.matrixClient) as FunctionWithUIA<R, A>;
|
||||
boundFunction(undefined, ...args)
|
||||
.then((res) => resolve(res as R))
|
||||
.catch((error) => {
|
||||
if (error.httpStatus !== 401 || !error.data?.flows) {
|
||||
// doesn't look like an interactive-auth failure
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
...opts,
|
||||
authData: error.data,
|
||||
makeRequest: (authData: AuthDict) => boundFunction(authData, ...args),
|
||||
onFinished: (success, result) => {
|
||||
if (success) {
|
||||
resolve(result as R);
|
||||
} else {
|
||||
reject(result);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
|
@ -7,20 +7,18 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import { cleanup, render, waitFor } from "jest-matrix-react";
|
||||
import { MockedObject, mocked } from "jest-mock";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import React from "react";
|
||||
import {
|
||||
MSC3906Rendezvous,
|
||||
LegacyRendezvousFailureReason,
|
||||
ClientRendezvousFailureReason,
|
||||
MSC4108SignInWithQR,
|
||||
MSC4108FailureReason,
|
||||
MSC4108SignInWithQR,
|
||||
RendezvousError,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import LoginWithQR from "../../../../../../src/components/views/auth/LoginWithQR";
|
||||
import { Click, Mode, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/rendezvous");
|
||||
jest.mock("matrix-js-sdk/src/rendezvous/transports");
|
||||
|
@ -65,9 +63,6 @@ describe("<LoginWithQR />", () => {
|
|||
mode: Mode.Show,
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const mockConfirmationDigits = "mock-confirmation-digits";
|
||||
const mockRendezvousCode = "mock-rendezvous-code";
|
||||
const newDeviceId = "new-device-id";
|
||||
|
||||
beforeEach(() => {
|
||||
mockedFlow.mockReset();
|
||||
|
@ -82,264 +77,10 @@ describe("<LoginWithQR />", () => {
|
|||
cleanup();
|
||||
});
|
||||
|
||||
describe("MSC3906", () => {
|
||||
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
|
||||
<React.StrictMode>
|
||||
<LoginWithQR {...defaultProps} {...props} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockResolvedValue();
|
||||
// @ts-ignore
|
||||
// workaround for https://github.com/facebook/jest/issues/9675
|
||||
MSC3906Rendezvous.prototype.code = mockRendezvousCode;
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "cancel").mockResolvedValue();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockResolvedValue(mockConfirmationDigits);
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "declineLoginOnExistingDevice").mockResolvedValue();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "approveLoginOnExistingDevice").mockResolvedValue(newDeviceId);
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockResolvedValue(undefined);
|
||||
client.requestLoginToken.mockResolvedValue({
|
||||
login_token: "token",
|
||||
expires_in_ms: 1000 * 1000,
|
||||
} as LoginTokenPostResponse); // we force the type here so that it works with versions of js-sdk that don't have r1 support yet
|
||||
});
|
||||
|
||||
test("no homeserver support", async () => {
|
||||
// simulate no support
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockRejectedValue("");
|
||||
render(getComponent({ client }));
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.Error,
|
||||
failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport,
|
||||
onClick: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("failed to connect", async () => {
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockRejectedValue("");
|
||||
render(getComponent({ client }));
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.Error,
|
||||
failureReason: ClientRendezvousFailureReason.Unknown,
|
||||
onClick: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("render QR then back", async () => {
|
||||
const onFinished = jest.fn();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockReturnValue(unresolvedPromise());
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.ShowingQR,
|
||||
}),
|
||||
),
|
||||
);
|
||||
// display QR code
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.ShowingQR,
|
||||
code: mockRendezvousCode,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
|
||||
// back
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Back);
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled);
|
||||
});
|
||||
|
||||
test("render QR then decline", async () => {
|
||||
const onFinished = jest.fn();
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.LegacyConnected,
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.LegacyConnected,
|
||||
confirmationDigits: mockConfirmationDigits,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
|
||||
// decline
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Decline);
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
expect(rendezvous.declineLoginOnExistingDevice).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("approve - no crypto", async () => {
|
||||
(client as any).getCrypto = () => undefined;
|
||||
const onFinished = jest.fn();
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.LegacyConnected,
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.LegacyConnected,
|
||||
confirmationDigits: mockConfirmationDigits,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
|
||||
// approve
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Approve);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.WaitingForDevice,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test("approve + verifying", async () => {
|
||||
const onFinished = jest.fn();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockImplementation(() =>
|
||||
unresolvedPromise(),
|
||||
);
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.LegacyConnected,
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.LegacyConnected,
|
||||
confirmationDigits: mockConfirmationDigits,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
|
||||
// approve
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
onClick(Click.Approve);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.Verifying,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
|
||||
expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled();
|
||||
// expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test("approve + verify", async () => {
|
||||
const onFinished = jest.fn();
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.LegacyConnected,
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.LegacyConnected,
|
||||
confirmationDigits: mockConfirmationDigits,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
|
||||
// approve
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Approve);
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
|
||||
expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled();
|
||||
expect(rendezvous.close).toHaveBeenCalled();
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test("approve - rate limited", async () => {
|
||||
mocked(client.requestLoginToken).mockRejectedValue(new HTTPError("rate limit reached", 429));
|
||||
const onFinished = jest.fn();
|
||||
render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.LegacyConnected,
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith({
|
||||
phase: Phase.LegacyConnected,
|
||||
confirmationDigits: mockConfirmationDigits,
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
|
||||
|
||||
// approve
|
||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Approve);
|
||||
|
||||
// the 429 error should be handled and mapped
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.Error,
|
||||
failureReason: "rate_limited",
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MSC4108", () => {
|
||||
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
|
||||
<React.StrictMode>
|
||||
<LoginWithQR {...defaultProps} {...props} legacy={false} />
|
||||
<LoginWithQR {...defaultProps} {...props} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
@ -363,7 +104,7 @@ describe("<LoginWithQR />", () => {
|
|||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||
await onClick(Click.Back);
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled);
|
||||
expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UserCancelled);
|
||||
});
|
||||
|
||||
test("failed to connect", async () => {
|
||||
|
@ -404,6 +145,27 @@ describe("<LoginWithQR />", () => {
|
|||
expect(global.window.open).toHaveBeenCalledWith("mock-verification-uri", "_blank");
|
||||
});
|
||||
|
||||
test("handles errors during protocol negotiation", async () => {
|
||||
render(getComponent({ client }));
|
||||
jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
|
||||
const err = new RendezvousError("Unknown Failure", MSC4108FailureReason.UnsupportedProtocol);
|
||||
// @ts-ignore work-around for lazy mocks
|
||||
err.code = MSC4108FailureReason.UnsupportedProtocol;
|
||||
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockRejectedValue(err);
|
||||
await waitFor(() =>
|
||||
expect(mockedFlow).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
phase: Phase.ShowingQR,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0];
|
||||
expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UnsupportedProtocol);
|
||||
});
|
||||
});
|
||||
|
||||
test("handles errors during reciprocation", async () => {
|
||||
render(getComponent({ client }));
|
||||
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
|
||||
|
|
|
@ -8,11 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
LegacyRendezvousFailureReason,
|
||||
MSC4108FailureReason,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||
|
||||
import LoginWithQRFlow from "../../../../../../src/components/views/auth/LoginWithQRFlow";
|
||||
import { LoginWithQRFailureReason, FailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
|
||||
|
@ -29,8 +25,7 @@ describe("<LoginWithQRFlow />", () => {
|
|||
phase: Phase;
|
||||
onClick?: () => Promise<void>;
|
||||
failureReason?: FailureReason;
|
||||
code?: string;
|
||||
confirmationDigits?: string;
|
||||
code?: Uint8Array;
|
||||
}) => <LoginWithQRFlow {...defaultProps} {...props} />;
|
||||
|
||||
beforeEach(() => {});
|
||||
|
@ -54,24 +49,14 @@ describe("<LoginWithQRFlow />", () => {
|
|||
});
|
||||
|
||||
it("renders QR code", async () => {
|
||||
const { container } = render(getComponent({ phase: Phase.ShowingQR, code: "mock-code" }));
|
||||
const { container } = render(
|
||||
getComponent({ phase: Phase.ShowingQR, code: new TextEncoder().encode("mock-code") }),
|
||||
);
|
||||
// QR code is rendered async so we wait for it:
|
||||
await waitFor(() => screen.getAllByAltText("QR Code").length === 1);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders code when connected", async () => {
|
||||
const { container } = render(getComponent({ phase: Phase.LegacyConnected, confirmationDigits: "mock-digits" }));
|
||||
expect(screen.getAllByText("mock-digits")).toHaveLength(1);
|
||||
expect(screen.getAllByTestId("decline-login-button")).toHaveLength(1);
|
||||
expect(screen.getAllByTestId("approve-login-button")).toHaveLength(1);
|
||||
expect(container).toMatchSnapshot();
|
||||
fireEvent.click(screen.getByTestId("decline-login-button"));
|
||||
expect(onClick).toHaveBeenCalledWith(Click.Decline, undefined);
|
||||
fireEvent.click(screen.getByTestId("approve-login-button"));
|
||||
expect(onClick).toHaveBeenCalledWith(Click.Approve, undefined);
|
||||
});
|
||||
|
||||
it("renders spinner while signing in", async () => {
|
||||
const { container } = render(getComponent({ phase: Phase.WaitingForDevice }));
|
||||
expect(screen.getAllByTestId("cancel-button")).toHaveLength(1);
|
||||
|
@ -92,7 +77,6 @@ describe("<LoginWithQRFlow />", () => {
|
|||
|
||||
describe("errors", () => {
|
||||
for (const failureReason of [
|
||||
...Object.values(LegacyRendezvousFailureReason),
|
||||
...Object.values(MSC4108FailureReason),
|
||||
...Object.values(LoginWithQRFailureReason),
|
||||
...Object.values(ClientRendezvousFailureReason),
|
||||
|
|
|
@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import { render } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { IClientWellKnown, IServerVersions, MatrixClient, GET_LOGIN_TOKEN_CAPABILITY } from "matrix-js-sdk/src/matrix";
|
||||
import { IClientWellKnown, IServerVersions, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
|
@ -51,73 +51,6 @@ describe("<LoginWithQRSection />", () => {
|
|||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient({}));
|
||||
});
|
||||
|
||||
describe("MSC3906", () => {
|
||||
const defaultProps = {
|
||||
onShowQr: () => {},
|
||||
versions: makeVersions({}),
|
||||
wellKnown: {},
|
||||
};
|
||||
|
||||
const getComponent = (props = {}) => <LoginWithQRSection {...defaultProps} {...props} />;
|
||||
|
||||
describe("should not render", () => {
|
||||
it("no support at all", () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("only get_login_token enabled", async () => {
|
||||
const { container } = render(
|
||||
getComponent({ capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } } }),
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("MSC3886 + get_login_token disabled", async () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
versions: makeVersions({ "org.matrix.msc3886": true }),
|
||||
capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: false } },
|
||||
}),
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("should render panel", () => {
|
||||
it("get_login_token + MSC3886", async () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
versions: makeVersions({
|
||||
"org.matrix.msc3886": true,
|
||||
}),
|
||||
capabilities: {
|
||||
[GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("get_login_token + .well-known", async () => {
|
||||
const wellKnown = {
|
||||
"io.element.rendezvous": {
|
||||
server: "https://rz.local",
|
||||
},
|
||||
};
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient(wellKnown));
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
versions: makeVersions({}),
|
||||
capabilities: { [GET_LOGIN_TOKEN_CAPABILITY.name]: { enabled: true } },
|
||||
wellKnown,
|
||||
}),
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MSC4108", () => {
|
||||
describe("MSC4108", () => {
|
||||
const defaultProps = {
|
||||
|
|
|
@ -252,48 +252,6 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders expired 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
The sign in was not completed in time
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
Sign in expired. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -374,86 +332,6 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_heading"
|
||||
>
|
||||
<div
|
||||
aria-label="Back"
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_breadcrumbs"
|
||||
>
|
||||
Sessions
|
||||
/
|
||||
Link new device
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
QR code not supported
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
Your account provider doesn't support signing into a new device with a QR code.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders insecure_channel_detected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -761,90 +639,6 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders unknown 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Something went wrong!
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Something went wrong!
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
An unexpected error occurred. The request to connect your other device has been cancelled.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -887,48 +681,6 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Other device not compatible
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
This device does not support signing in to the other device with a QR code.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -971,48 +723,6 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders user_cancelled 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Sign in request cancelled
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The sign in was cancelled on the other device.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -1055,48 +765,6 @@ exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> errors renders user_declined 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR mx_LoginWithQR_error"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
>
|
||||
Sign in declined
|
||||
</h1>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
You or the account provider declined the sign in request.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -1256,58 +924,6 @@ exports[`<LoginWithQRFlow /> renders check code confirmation 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p>
|
||||
Check that the code below matches with your other device:
|
||||
</p>
|
||||
<div
|
||||
class="mx_LoginWithQR_confirmationDigits"
|
||||
>
|
||||
mock-digits
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_confirmationAlert"
|
||||
>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div>
|
||||
By approving access for this device, it will have full access to your account.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
data-testid="approve-login-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Approve
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
data-testid="decline-login-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
|
|
@ -1,307 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LoginWithQRSection /> MSC3906 should not render MSC3886 + get_login_token disabled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Link new device
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
Use a QR code to sign in to another device and set up secure messaging.
|
||||
</p>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
|
||||
disabled=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
Show QR code
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
|
||||
>
|
||||
Not supported by your account provider
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRSection /> MSC3906 should not render no support at all 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Link new device
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
Use a QR code to sign in to another device and set up secure messaging.
|
||||
</p>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
|
||||
disabled=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
Show QR code
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
|
||||
>
|
||||
Not supported by your account provider
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRSection /> MSC3906 should not render only get_login_token enabled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Link new device
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
Use a QR code to sign in to another device and set up secure messaging.
|
||||
</p>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
|
||||
disabled=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
Show QR code
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
|
||||
>
|
||||
Not supported by your account provider
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRSection /> MSC3906 should render panel get_login_token + .well-known 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Link new device
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
Use a QR code to sign in to another device and set up secure messaging.
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
Show QR code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQRSection /> MSC3906 should render panel get_login_token + MSC3886 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Link new device
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
Use a QR code to sign in to another device and set up secure messaging.
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M15 16v-3h-2v3h2Z"
|
||||
/>
|
||||
<path
|
||||
d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
|
||||
/>
|
||||
</svg>
|
||||
Show QR code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1650,46 +1650,6 @@ describe("<SessionManagerTab />", () => {
|
|||
expect(checkbox.getAttribute("aria-checked")).toEqual("false");
|
||||
});
|
||||
|
||||
describe("MSC3906 QR code login", () => {
|
||||
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||
|
||||
beforeEach(() => {
|
||||
settingsValueSpy.mockClear().mockReturnValue(false);
|
||||
// enable server support for qr login
|
||||
mockClient.getVersions.mockResolvedValue({
|
||||
versions: [],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3886": true,
|
||||
},
|
||||
});
|
||||
mockClient.getCapabilities.mockResolvedValue({
|
||||
[GET_LOGIN_TOKEN_CAPABILITY.name]: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("renders qr code login section", async () => {
|
||||
const { getByText } = render(getComponent());
|
||||
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
expect(getByText("Link new device")).toBeTruthy();
|
||||
expect(getByText("Show QR code")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("enters qr code login section when show QR code button clicked", async () => {
|
||||
const { getByText, findByTestId } = render(getComponent());
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(getByText("Show QR code"));
|
||||
|
||||
await expect(findByTestId("login-with-qr")).resolves.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("MSC4108 QR code login", () => {
|
||||
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||
const issuer = "https://issuer.org";
|
||||
|
|
Loading…
Reference in a new issue