mirror of https://github.com/vector-im/riot-web
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>pull/28294/head
parent
6d0d237c79
commit
5b5348ec1e
|
@ -24,10 +24,6 @@ export enum Phase {
|
||||||
WaitingForDevice,
|
WaitingForDevice,
|
||||||
Verifying,
|
Verifying,
|
||||||
Error,
|
Error,
|
||||||
/**
|
|
||||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
|
||||||
*/
|
|
||||||
LegacyConnected,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Click {
|
export enum Click {
|
||||||
|
|
|
@ -9,11 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
ClientRendezvousFailureReason,
|
ClientRendezvousFailureReason,
|
||||||
LegacyRendezvousFailureReason,
|
|
||||||
MSC3886SimpleHttpRendezvousTransport,
|
|
||||||
MSC3903ECDHPayload,
|
|
||||||
MSC3903ECDHv2RendezvousChannel,
|
|
||||||
MSC3906Rendezvous,
|
|
||||||
MSC4108FailureReason,
|
MSC4108FailureReason,
|
||||||
MSC4108RendezvousSession,
|
MSC4108RendezvousSession,
|
||||||
MSC4108SecureChannel,
|
MSC4108SecureChannel,
|
||||||
|
@ -23,29 +18,21 @@ import {
|
||||||
RendezvousIntent,
|
RendezvousIntent,
|
||||||
} from "matrix-js-sdk/src/rendezvous";
|
} from "matrix-js-sdk/src/rendezvous";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
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 { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||||
import { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
|
||||||
import { _t } from "../../../languageHandler";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
mode: Mode;
|
mode: Mode;
|
||||||
legacy: boolean;
|
|
||||||
onFinished(...args: any): void;
|
onFinished(...args: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
rendezvous?: MSC3906Rendezvous | MSC4108SignInWithQR;
|
rendezvous?: MSC4108SignInWithQR;
|
||||||
mediaPermissionError?: boolean;
|
mediaPermissionError?: boolean;
|
||||||
|
|
||||||
// MSC3906
|
|
||||||
confirmationDigits?: string;
|
|
||||||
|
|
||||||
// MSC4108
|
|
||||||
verificationUri?: string;
|
verificationUri?: string;
|
||||||
userCode?: string;
|
userCode?: string;
|
||||||
checkCode?: string;
|
checkCode?: string;
|
||||||
|
@ -54,25 +41,18 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LoginWithQRFailureReason {
|
export enum LoginWithQRFailureReason {
|
||||||
/**
|
|
||||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
|
||||||
*/
|
|
||||||
RateLimited = "rate_limited",
|
RateLimited = "rate_limited",
|
||||||
CheckCodeMismatch = "check_code_mismatch",
|
CheckCodeMismatch = "check_code_mismatch",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason;
|
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.
|
* A component that allows sign in and E2EE set up with a QR code.
|
||||||
*
|
*
|
||||||
* It implements `login.reciprocate` capabilities and showing QR codes.
|
* 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> {
|
export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
private finished = false;
|
private finished = false;
|
||||||
|
@ -104,9 +84,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
if (this.state.rendezvous) {
|
if (this.state.rendezvous) {
|
||||||
const rendezvous = this.state.rendezvous;
|
const rendezvous = this.state.rendezvous;
|
||||||
rendezvous.onFailure = undefined;
|
rendezvous.onFailure = undefined;
|
||||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
|
||||||
await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
|
||||||
}
|
|
||||||
this.setState({ rendezvous: undefined });
|
this.setState({ rendezvous: undefined });
|
||||||
}
|
}
|
||||||
if (mode === Mode.Show) {
|
if (mode === Mode.Show) {
|
||||||
|
@ -119,62 +96,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
// eslint-disable-next-line react/no-direct-mutation-state
|
// eslint-disable-next-line react/no-direct-mutation-state
|
||||||
this.state.rendezvous.onFailure = undefined;
|
this.state.rendezvous.onFailure = undefined;
|
||||||
// calling cancel will call close() as well to clean up the resources
|
// 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);
|
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onFinished(success: boolean): void {
|
private onFinished(success: boolean): void {
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
|
@ -182,19 +106,10 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateAndShowCode = async (): Promise<void> => {
|
private generateAndShowCode = async (): Promise<void> => {
|
||||||
let rendezvous: MSC4108SignInWithQR | MSC3906Rendezvous;
|
let rendezvous: MSC4108SignInWithQR;
|
||||||
try {
|
try {
|
||||||
const fallbackRzServer = this.props.client?.getClientWellKnown()?.["io.element.rendezvous"]?.server;
|
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({
|
const transport = new MSC4108RendezvousSession({
|
||||||
onFailure: this.onFailure,
|
onFailure: this.onFailure,
|
||||||
client: this.props.client,
|
client: this.props.client,
|
||||||
|
@ -203,7 +118,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
await transport.send("");
|
await transport.send("");
|
||||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||||
}
|
|
||||||
|
|
||||||
await rendezvous.generateCode();
|
await rendezvous.generateCode();
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -218,10 +132,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
||||||
const confirmationDigits = await rendezvous.startAfterShowingCode();
|
|
||||||
this.setState({ phase: Phase.LegacyConnected, confirmationDigits });
|
|
||||||
} else if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
|
||||||
// MSC4108-Flow: NewScanned
|
// MSC4108-Flow: NewScanned
|
||||||
await rendezvous.negotiateProtocols();
|
await rendezvous.negotiateProtocols();
|
||||||
const { verificationUri } = await rendezvous.deviceAuthorizationGrant();
|
const { verificationUri } = await rendezvous.deviceAuthorizationGrant();
|
||||||
|
@ -234,19 +145,10 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
// we ask the user to confirm that the channel is secure
|
// we ask the user to confirm that the channel is secure
|
||||||
} catch (e: RendezvousError | unknown) {
|
} catch (e: RendezvousError | unknown) {
|
||||||
logger.error("Error whilst approving login", e);
|
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(
|
await rendezvous?.cancel(
|
||||||
e instanceof RendezvousError
|
e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown,
|
||||||
? (e.code as MSC4108FailureReason)
|
|
||||||
: ClientRendezvousFailureReason.Unknown,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private approveLogin = async (checkCode: string | undefined): Promise<void> => {
|
private approveLogin = async (checkCode: string | undefined): Promise<void> => {
|
||||||
|
@ -298,7 +200,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this.setState({
|
this.setState({
|
||||||
rendezvous: undefined,
|
rendezvous: undefined,
|
||||||
confirmationDigits: undefined,
|
|
||||||
verificationUri: undefined,
|
verificationUri: undefined,
|
||||||
failureReason: undefined,
|
failureReason: undefined,
|
||||||
userCode: 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> => {
|
private onClick = async (type: Click, checkCode?: string): Promise<void> => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Click.Cancel:
|
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.reset();
|
||||||
this.onFinished(false);
|
this.onFinished(false);
|
||||||
break;
|
break;
|
||||||
case Click.Approve:
|
case Click.Approve:
|
||||||
await (this.props.legacy ? this.legacyApproveLogin() : this.approveLogin(checkCode));
|
await this.approveLogin(checkCode);
|
||||||
break;
|
break;
|
||||||
case Click.Decline:
|
case Click.Decline:
|
||||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||||
|
@ -328,11 +225,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
this.onFinished(false);
|
this.onFinished(false);
|
||||||
break;
|
break;
|
||||||
case Click.Back:
|
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);
|
this.onFinished(false);
|
||||||
break;
|
break;
|
||||||
case Click.ShowQr:
|
case Click.ShowQr:
|
||||||
|
@ -342,20 +235,6 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
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 (
|
return (
|
||||||
<LoginWithQRFlow
|
<LoginWithQRFlow
|
||||||
onClick={this.onClick}
|
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 React, { createRef, ReactNode } from "react";
|
||||||
import {
|
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
ClientRendezvousFailureReason,
|
|
||||||
LegacyRendezvousFailureReason,
|
|
||||||
MSC4108FailureReason,
|
|
||||||
} from "matrix-js-sdk/src/rendezvous";
|
|
||||||
import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
|
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 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";
|
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 AccessibleButton from "../elements/AccessibleButton";
|
||||||
import QRCode from "../elements/QRCode";
|
import QRCode from "../elements/QRCode";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
|
||||||
import { Click, Phase } from "./LoginWithQR-types";
|
import { Click, Phase } from "./LoginWithQR-types";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
|
import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
|
||||||
import { XOR } from "../../../@types/common";
|
|
||||||
import { ErrorMessage } from "../../structures/ErrorMessage";
|
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 {
|
interface Props {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
code?: Uint8Array;
|
code?: Uint8Array;
|
||||||
|
@ -47,19 +33,15 @@ interface Props {
|
||||||
checkCode?: string;
|
checkCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
|
||||||
// However, we want to keep this implementation around for some time.
|
|
||||||
// TODO: define an end-of-life date for this implementation.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A component that implements the UI for sign in and E2EE set up with a QR code.
|
* 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>();
|
private checkCodeInput = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
public constructor(props: XOR<Props, MSC3906Props>) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,20 +86,17 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
||||||
|
|
||||||
switch (this.props.failureReason) {
|
switch (this.props.failureReason) {
|
||||||
case MSC4108FailureReason.UnsupportedProtocol:
|
case MSC4108FailureReason.UnsupportedProtocol:
|
||||||
case LegacyRendezvousFailureReason.UnsupportedProtocol:
|
|
||||||
title = _t("auth|qr_code_login|error_unsupported_protocol_title");
|
title = _t("auth|qr_code_login|error_unsupported_protocol_title");
|
||||||
message = _t("auth|qr_code_login|error_unsupported_protocol");
|
message = _t("auth|qr_code_login|error_unsupported_protocol");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSC4108FailureReason.UserCancelled:
|
case MSC4108FailureReason.UserCancelled:
|
||||||
case LegacyRendezvousFailureReason.UserCancelled:
|
|
||||||
title = _t("auth|qr_code_login|error_user_cancelled_title");
|
title = _t("auth|qr_code_login|error_user_cancelled_title");
|
||||||
message = _t("auth|qr_code_login|error_user_cancelled");
|
message = _t("auth|qr_code_login|error_user_cancelled");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSC4108FailureReason.AuthorizationExpired:
|
case MSC4108FailureReason.AuthorizationExpired:
|
||||||
case ClientRendezvousFailureReason.Expired:
|
case ClientRendezvousFailureReason.Expired:
|
||||||
case LegacyRendezvousFailureReason.Expired:
|
|
||||||
title = _t("auth|qr_code_login|error_expired_title");
|
title = _t("auth|qr_code_login|error_expired_title");
|
||||||
message = _t("auth|qr_code_login|error_expired");
|
message = _t("auth|qr_code_login|error_expired");
|
||||||
break;
|
break;
|
||||||
|
@ -162,7 +141,6 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
||||||
message = _t("auth|qr_code_login|error_etag_missing");
|
message = _t("auth|qr_code_login|error_etag_missing");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LegacyRendezvousFailureReason.HomeserverLacksSupport:
|
|
||||||
case ClientRendezvousFailureReason.HomeserverLacksSupport:
|
case ClientRendezvousFailureReason.HomeserverLacksSupport:
|
||||||
success = null;
|
success = null;
|
||||||
Icon = QrCodeIcon;
|
Icon = QrCodeIcon;
|
||||||
|
@ -200,40 +178,6 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
||||||
);
|
);
|
||||||
break;
|
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:
|
case Phase.OutOfBandConfirmation:
|
||||||
backButton = false;
|
backButton = false;
|
||||||
main = (
|
main = (
|
||||||
|
@ -288,8 +232,7 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
|
||||||
break;
|
break;
|
||||||
case Phase.ShowingQR:
|
case Phase.ShowingQR:
|
||||||
if (this.props.code) {
|
if (this.props.code) {
|
||||||
const data =
|
const data = this.props.code;
|
||||||
typeof this.props.code !== "string" ? this.props.code : Buffer.from(this.props.code ?? "");
|
|
||||||
|
|
||||||
main = (
|
main = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8,10 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
IGetLoginTokenCapability,
|
|
||||||
IServerVersions,
|
IServerVersions,
|
||||||
GET_LOGIN_TOKEN_CAPABILITY,
|
|
||||||
Capabilities,
|
|
||||||
IClientWellKnown,
|
IClientWellKnown,
|
||||||
OidcClientConfig,
|
OidcClientConfig,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
|
@ -28,27 +25,11 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onShowQr: () => void;
|
onShowQr: () => void;
|
||||||
versions?: IServerVersions;
|
versions?: IServerVersions;
|
||||||
capabilities?: Capabilities;
|
|
||||||
wellKnown?: IClientWellKnown;
|
wellKnown?: IClientWellKnown;
|
||||||
oidcClientConfig?: OidcClientConfig;
|
oidcClientConfig?: OidcClientConfig;
|
||||||
isCrossSigningReady?: boolean;
|
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(
|
export function shouldShowQr(
|
||||||
cli: MatrixClient,
|
cli: MatrixClient,
|
||||||
isCrossSigningReady: boolean,
|
isCrossSigningReady: boolean,
|
||||||
|
@ -73,15 +54,12 @@ export function shouldShowQr(
|
||||||
const LoginWithQRSection: React.FC<IProps> = ({
|
const LoginWithQRSection: React.FC<IProps> = ({
|
||||||
onShowQr,
|
onShowQr,
|
||||||
versions,
|
versions,
|
||||||
capabilities,
|
|
||||||
wellKnown,
|
wellKnown,
|
||||||
oidcClientConfig,
|
oidcClientConfig,
|
||||||
isCrossSigningReady,
|
isCrossSigningReady,
|
||||||
}) => {
|
}) => {
|
||||||
const cli = useMatrixClientContext();
|
const cli = useMatrixClientContext();
|
||||||
const offerShowQr = oidcClientConfig
|
const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown);
|
||||||
? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown)
|
|
||||||
: shouldShowQrLegacy(versions, wellKnown, capabilities);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||||
|
|
|
@ -181,7 +181,6 @@ const SessionManagerTab: React.FC<{
|
||||||
const userId = matrixClient?.getUserId();
|
const userId = matrixClient?.getUserId();
|
||||||
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
||||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||||
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
|
|
||||||
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
|
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
|
||||||
const oidcClientConfig = useAsyncMemo(async () => {
|
const oidcClientConfig = useAsyncMemo(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -292,12 +291,7 @@ const SessionManagerTab: React.FC<{
|
||||||
if (signInWithQrMode) {
|
if (signInWithQrMode) {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Spinner />}>
|
<Suspense fallback={<Spinner />}>
|
||||||
<LoginWithQR
|
<LoginWithQR mode={signInWithQrMode} onFinished={onQrFinish} client={matrixClient} />
|
||||||
mode={signInWithQrMode}
|
|
||||||
onFinished={onQrFinish}
|
|
||||||
client={matrixClient}
|
|
||||||
legacy={!oidcClientConfig && !showMsc4108QrCode}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -308,7 +302,6 @@ const SessionManagerTab: React.FC<{
|
||||||
<LoginWithQRSection
|
<LoginWithQRSection
|
||||||
onShowQr={onShowQrClicked}
|
onShowQr={onShowQrClicked}
|
||||||
versions={clientVersions}
|
versions={clientVersions}
|
||||||
capabilities={capabilities}
|
|
||||||
wellKnown={wellKnown}
|
wellKnown={wellKnown}
|
||||||
oidcClientConfig={oidcClientConfig}
|
oidcClientConfig={oidcClientConfig}
|
||||||
isCrossSigningReady={isCrossSigningReady}
|
isCrossSigningReady={isCrossSigningReady}
|
||||||
|
|
|
@ -250,13 +250,11 @@
|
||||||
"phone_label": "Phone",
|
"phone_label": "Phone",
|
||||||
"phone_optional_label": "Phone (optional)",
|
"phone_optional_label": "Phone (optional)",
|
||||||
"qr_code_login": {
|
"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_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_heading": "Enter the number shown on your other device",
|
||||||
"check_code_input_label": "2-digit code",
|
"check_code_input_label": "2-digit code",
|
||||||
"check_code_mismatch": "The numbers don't match",
|
"check_code_mismatch": "The numbers don't match",
|
||||||
"completing_setup": "Completing set up of your new device",
|
"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_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": "Sign in expired. Please try again.",
|
||||||
"error_expired_title": "The sign in was not completed in time",
|
"error_expired_title": "The sign in was not completed in time",
|
||||||
|
@ -284,7 +282,6 @@
|
||||||
"security_code": "Security code",
|
"security_code": "Security code",
|
||||||
"security_code_prompt": "If asked, enter the code below on your other device.",
|
"security_code_prompt": "If asked, enter the code below on your other device.",
|
||||||
"select_qr_code": "Select \"%(scanQRCode)s\"",
|
"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_explainer": "Your account provider doesn't support signing into a new device with a QR code.",
|
||||||
"unsupported_heading": "QR code not supported",
|
"unsupported_heading": "QR code not supported",
|
||||||
"waiting_for_device": "Waiting for device to sign in"
|
"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 { cleanup, render, waitFor } from "jest-matrix-react";
|
||||||
import { MockedObject, mocked } from "jest-mock";
|
import { mocked, MockedObject } from "jest-mock";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
MSC3906Rendezvous,
|
|
||||||
LegacyRendezvousFailureReason,
|
|
||||||
ClientRendezvousFailureReason,
|
ClientRendezvousFailureReason,
|
||||||
MSC4108SignInWithQR,
|
|
||||||
MSC4108FailureReason,
|
MSC4108FailureReason,
|
||||||
|
MSC4108SignInWithQR,
|
||||||
|
RendezvousError,
|
||||||
} from "matrix-js-sdk/src/rendezvous";
|
} 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 LoginWithQR from "../../../../../../src/components/views/auth/LoginWithQR";
|
||||||
import { Click, Mode, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
|
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");
|
||||||
jest.mock("matrix-js-sdk/src/rendezvous/transports");
|
jest.mock("matrix-js-sdk/src/rendezvous/transports");
|
||||||
|
@ -65,9 +63,6 @@ describe("<LoginWithQR />", () => {
|
||||||
mode: Mode.Show,
|
mode: Mode.Show,
|
||||||
onFinished: jest.fn(),
|
onFinished: jest.fn(),
|
||||||
};
|
};
|
||||||
const mockConfirmationDigits = "mock-confirmation-digits";
|
|
||||||
const mockRendezvousCode = "mock-rendezvous-code";
|
|
||||||
const newDeviceId = "new-device-id";
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedFlow.mockReset();
|
mockedFlow.mockReset();
|
||||||
|
@ -82,264 +77,10 @@ describe("<LoginWithQR />", () => {
|
||||||
cleanup();
|
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", () => {
|
describe("MSC4108", () => {
|
||||||
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
|
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<LoginWithQR {...defaultProps} {...props} legacy={false} />
|
<LoginWithQR {...defaultProps} {...props} />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -363,7 +104,7 @@ describe("<LoginWithQR />", () => {
|
||||||
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
const onClick = mockedFlow.mock.calls[0][0].onClick;
|
||||||
await onClick(Click.Back);
|
await onClick(Click.Back);
|
||||||
expect(onFinished).toHaveBeenCalledWith(false);
|
expect(onFinished).toHaveBeenCalledWith(false);
|
||||||
expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled);
|
expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UserCancelled);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("failed to connect", async () => {
|
test("failed to connect", async () => {
|
||||||
|
@ -404,6 +145,27 @@ describe("<LoginWithQR />", () => {
|
||||||
expect(global.window.open).toHaveBeenCalledWith("mock-verification-uri", "_blank");
|
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 () => {
|
test("handles errors during reciprocation", async () => {
|
||||||
render(getComponent({ client }));
|
render(getComponent({ client }));
|
||||||
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
|
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 { cleanup, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
ClientRendezvousFailureReason,
|
|
||||||
LegacyRendezvousFailureReason,
|
|
||||||
MSC4108FailureReason,
|
|
||||||
} from "matrix-js-sdk/src/rendezvous";
|
|
||||||
|
|
||||||
import LoginWithQRFlow from "../../../../../../src/components/views/auth/LoginWithQRFlow";
|
import LoginWithQRFlow from "../../../../../../src/components/views/auth/LoginWithQRFlow";
|
||||||
import { LoginWithQRFailureReason, FailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
|
import { LoginWithQRFailureReason, FailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
|
||||||
|
@ -29,8 +25,7 @@ describe("<LoginWithQRFlow />", () => {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
onClick?: () => Promise<void>;
|
onClick?: () => Promise<void>;
|
||||||
failureReason?: FailureReason;
|
failureReason?: FailureReason;
|
||||||
code?: string;
|
code?: Uint8Array;
|
||||||
confirmationDigits?: string;
|
|
||||||
}) => <LoginWithQRFlow {...defaultProps} {...props} />;
|
}) => <LoginWithQRFlow {...defaultProps} {...props} />;
|
||||||
|
|
||||||
beforeEach(() => {});
|
beforeEach(() => {});
|
||||||
|
@ -54,24 +49,14 @@ describe("<LoginWithQRFlow />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders QR code", async () => {
|
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:
|
// QR code is rendered async so we wait for it:
|
||||||
await waitFor(() => screen.getAllByAltText("QR Code").length === 1);
|
await waitFor(() => screen.getAllByAltText("QR Code").length === 1);
|
||||||
expect(container).toMatchSnapshot();
|
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 () => {
|
it("renders spinner while signing in", async () => {
|
||||||
const { container } = render(getComponent({ phase: Phase.WaitingForDevice }));
|
const { container } = render(getComponent({ phase: Phase.WaitingForDevice }));
|
||||||
expect(screen.getAllByTestId("cancel-button")).toHaveLength(1);
|
expect(screen.getAllByTestId("cancel-button")).toHaveLength(1);
|
||||||
|
@ -92,7 +77,6 @@ describe("<LoginWithQRFlow />", () => {
|
||||||
|
|
||||||
describe("errors", () => {
|
describe("errors", () => {
|
||||||
for (const failureReason of [
|
for (const failureReason of [
|
||||||
...Object.values(LegacyRendezvousFailureReason),
|
|
||||||
...Object.values(MSC4108FailureReason),
|
...Object.values(MSC4108FailureReason),
|
||||||
...Object.values(LoginWithQRFailureReason),
|
...Object.values(LoginWithQRFailureReason),
|
||||||
...Object.values(ClientRendezvousFailureReason),
|
...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 { render } from "jest-matrix-react";
|
||||||
import { mocked } from "jest-mock";
|
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 React from "react";
|
||||||
import fetchMock from "fetch-mock-jest";
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
|
@ -51,73 +51,6 @@ describe("<LoginWithQRSection />", () => {
|
||||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(makeClient({}));
|
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", () => {
|
||||||
describe("MSC4108", () => {
|
describe("MSC4108", () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
|
|
@ -252,48 +252,6 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -374,86 +332,6 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> errors renders insecure_channel_detected 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -761,90 +639,6 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -887,48 +681,6 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -971,48 +723,6 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -1055,48 +765,6 @@ exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> renders QR code 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -1256,58 +924,6 @@ exports[`<LoginWithQRFlow /> renders check code confirmation 1`] = `
|
||||||
</div>
|
</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`] = `
|
exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<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");
|
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", () => {
|
describe("MSC4108 QR code login", () => {
|
||||||
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||||
const issuer = "https://issuer.org";
|
const issuer = "https://issuer.org";
|
||||||
|
|
Loading…
Reference in New Issue