Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Commit

Permalink
Always show link new device flow even if unsupported (#147)
Browse files Browse the repository at this point in the history
* Always show link new device flow even if unsupported

Signed-off-by: Michael Telatynski <[email protected]>

* Iterate

Signed-off-by: Michael Telatynski <[email protected]>

* Update screenshot

Signed-off-by: Michael Telatynski <[email protected]>

---------

Signed-off-by: Michael Telatynski <[email protected]>
  • Loading branch information
t3chguy authored Oct 15, 2024
1 parent 51a5cf6 commit d58c9fa
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 110 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 16 additions & 2 deletions res/css/views/auth/_LoginWithQR.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ Please see LICENSE files in the repository root for full details.
margin-bottom: $spacing-16;
}

.mx_LoginWithQRSection {
.mx_AccessibleButton_kind_primary + p {
color: var(--cpd-color-text-secondary);
margin-top: var(--cpd-space-2x);
}
}

.mx_LoginWithQRSection .mx_AccessibleButton svg {
margin-right: $spacing-12;
}
Expand Down Expand Up @@ -135,9 +142,16 @@ Please see LICENSE files in the repository root for full details.
padding: var(--cpd-space-3x);
gap: 10px;

background-color: var(--cpd-color-bg-success-subtle);
background-color: var(--cpd-color-bg-subtle-secondary);
svg {
color: var(--cpd-color-icon-success-primary);
color: var(--cpd-color-icon-secondary);
}

&.mx_LoginWithQR_icon--success {
background-color: var(--cpd-color-bg-success-subtle);
svg {
color: var(--cpd-color-icon-success-primary);
}
}

&.mx_LoginWithQR_icon--critical {
Expand Down
61 changes: 8 additions & 53 deletions src/components/structures/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React, { createRef, ReactNode } from "react";
import { discoverAndValidateOIDCIssuerWellKnown, Room } from "matrix-js-sdk/src/matrix";
import { Room } from "matrix-js-sdk/src/matrix";

import { MatrixClientPeg } from "../../MatrixClientPeg";
import defaultDispatcher from "../../dispatcher/dispatcher";
Expand Down Expand Up @@ -44,8 +44,6 @@ import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg";
import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast";
import { SDKContext } from "../../contexts/SDKContext";
import { shouldShowFeedback } from "../../utils/Feedback";
import { shouldShowQr } from "../views/settings/devices/LoginWithQRSection";
import { Features } from "../../settings/Settings";

interface IProps {
isPanelCollapsed: boolean;
Expand All @@ -60,8 +58,6 @@ interface IState {
isHighContrast: boolean;
selectedSpace?: Room | null;
showLiveAvatarAddon: boolean;
showQrLogin: boolean;
supportsQrLogin: boolean;
}

const toRightOf = (rect: PartialDOMRect): MenuProps => {
Expand Down Expand Up @@ -98,8 +94,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
isHighContrast: this.isUserOnHighContrastTheme(),
selectedSpace: SpaceStore.instance.activeSpaceRoom,
showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(),
showQrLogin: false,
supportsQrLogin: false,
};

OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
Expand All @@ -123,7 +117,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
);
this.dispatcherRef = defaultDispatcher.register(this.onAction);
this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged);
this.checkQrLoginSupport();
}

public componentWillUnmount(): void {
Expand All @@ -138,29 +131,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
);
}

private checkQrLoginSupport = async (): Promise<void> => {
if (!this.context.client || !SettingsStore.getValue(Features.OidcNativeFlow)) return;

const { issuer } = await this.context.client.getAuthIssuer().catch(() => ({ issuer: undefined }));
if (issuer) {
const [oidcClientConfig, versions, wellKnown, isCrossSigningReady] = await Promise.all([
discoverAndValidateOIDCIssuerWellKnown(issuer),
this.context.client.getVersions(),
this.context.client.waitForClientWellKnown(),
this.context.client.getCrypto()?.isCrossSigningReady(),
]);

const supportsQrLogin = shouldShowQr(
this.context.client,
!!isCrossSigningReady,
oidcClientConfig,
versions,
wellKnown,
);
this.setState({ supportsQrLogin, showQrLogin: true });
}
};

private isUserOnDarkTheme(): boolean {
if (SettingsStore.getValue("use_system_theme")) {
return window.matchMedia("(prefers-color-scheme: dark)").matches;
Expand Down Expand Up @@ -363,28 +333,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
);
}

let linkNewDeviceButton: JSX.Element | undefined;
if (this.state.showQrLogin) {
const extraProps: Omit<
React.ComponentProps<typeof IconizedContextMenuOption>,
"iconClassname" | "label" | "onClick"
> = {};
if (!this.state.supportsQrLogin) {
extraProps.disabled = true;
extraProps.title = _t("user_menu|link_new_device_not_supported");
extraProps.caption = _t("user_menu|link_new_device_not_supported_caption");
extraProps.placement = "right";
}

linkNewDeviceButton = (
<IconizedContextMenuOption
{...extraProps}
iconClassName="mx_UserMenu_iconQr"
label={_t("user_menu|link_new_device")}
onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })}
/>
);
}
const linkNewDeviceButton = (
<IconizedContextMenuOption
iconClassName="mx_UserMenu_iconQr"
label={_t("user_menu|link_new_device")}
onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })}
/>
);

let primaryOptionList = (
<IconizedContextMenuOptionList>
Expand Down
22 changes: 18 additions & 4 deletions src/components/views/auth/LoginWithQRFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/i
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
import { Heading, MFAInput, Text } from "@vector-im/compound-web";
import classNames from "classnames";
import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";

import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
Expand Down Expand Up @@ -94,7 +95,10 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P

switch (this.props.phase) {
case Phase.Error: {
let success = false;
backButton = false;

let Icon = ErrorIcon;
let success: boolean | null = false;
let title: string | undefined;
let message: ReactNode | undefined;

Expand Down Expand Up @@ -138,6 +142,7 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P

case ClientRendezvousFailureReason.OtherDeviceAlreadySignedIn:
success = true;
Icon = CheckCircleSolidIcon;
title = _t("auth|qr_code_login|error_other_device_already_signed_in_title");
message = _t("auth|qr_code_login|error_other_device_already_signed_in");
break;
Expand All @@ -157,6 +162,15 @@ 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;
backButton = true;
title = _t("auth|qr_code_login|unsupported_heading");
message = _t("auth|qr_code_login|unsupported_explainer");
break;

case MSC4108FailureReason.DeviceAlreadyExists:
case MSC4108FailureReason.DeviceNotFound:
case MSC4108FailureReason.UnexpectedMessageReceived:
Expand All @@ -168,15 +182,15 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
break;
}
className = "mx_LoginWithQR_error";
backButton = false;
main = (
<>
<div
className={classNames("mx_LoginWithQR_icon", {
"mx_LoginWithQR_icon--critical": !success,
"mx_LoginWithQR_icon--critical": success === false,
"mx_LoginWithQR_icon--success": success === true,
})}
>
{success ? <CheckCircleSolidIcon width="32px" /> : <ErrorIcon width="32px" />}
<Icon width="32px" height="32px" />
</div>
<Heading as="h1" size="sm" weight="semibold">
{title}
Expand Down
14 changes: 4 additions & 10 deletions src/components/views/settings/devices/LoginWithQRSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import {
DEVICE_CODE_SCOPE,
} from "matrix-js-sdk/src/matrix";
import QrCodeIcon from "@vector-im/compound-design-tokens/assets/web/icons/qr-code";
import { Text } from "@vector-im/compound-web";

import { _t } from "../../../../languageHandler";
import AccessibleButton from "../../elements/AccessibleButton";
import SettingsSubsection from "../shared/SettingsSubsection";
import SettingsStore from "../../../../settings/SettingsStore";
import { Features } from "../../../../settings/Settings";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";

interface IProps {
Expand Down Expand Up @@ -64,9 +63,8 @@ export function shouldShowQr(
oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE);

return (
deviceAuthorizationGrantSupported &&
!!deviceAuthorizationGrantSupported &&
msc4108Supported &&
SettingsStore.getValue(Features.OidcNativeFlow) &&
!!cli.getCrypto()?.exportSecretsBundle &&
isCrossSigningReady
);
Expand All @@ -85,19 +83,15 @@ const LoginWithQRSection: React.FC<IProps> = ({
? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown)
: shouldShowQrLegacy(versions, wellKnown, capabilities);

// don't show anything if no method is available
if (!offerShowQr) {
return null;
}

return (
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
<div className="mx_LoginWithQRSection">
<p className="mx_SettingsTab_subsectionText">{_t("settings|sessions|sign_in_with_qr_description")}</p>
<AccessibleButton onClick={onShowQr} kind="primary">
<AccessibleButton onClick={onShowQr} kind="primary" disabled={!offerShowQr}>
<QrCodeIcon height={20} width={20} />
{_t("settings|sessions|sign_in_with_qr_button")}
</AccessibleButton>
{!offerShowQr && <Text size="sm">{_t("settings|sessions|sign_in_with_qr_unsupported")}</Text>}
</div>
</SettingsSubsection>
);
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@
"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"
},
"register_action": "Create Account",
Expand Down Expand Up @@ -2855,6 +2857,7 @@
"sign_in_with_qr": "Link new device",
"sign_in_with_qr_button": "Show QR code",
"sign_in_with_qr_description": "Use a QR code to sign in to another device and set up secure messaging.",
"sign_in_with_qr_unsupported": "Not supported by your account provider",
"sign_out": "Sign out of this session",
"sign_out_all_other_sessions": "Sign out of all other sessions (%(otherSessionsCount)s)",
"sign_out_confirm_description": {
Expand Down Expand Up @@ -3829,8 +3832,6 @@
},
"user_menu": {
"link_new_device": "Link new device",
"link_new_device_not_supported": "Not supported",
"link_new_device_not_supported_caption": "You need to sign in manually",
"settings": "All settings",
"switch_theme_dark": "Switch to dark mode",
"switch_theme_light": "Switch to light mode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,19 @@ describe("<LoginWithQRSection />", () => {

test("no homeserver support", async () => {
const { container } = render(getComponent({ versions: makeVersions({ "org.matrix.msc4108": false }) }));
expect(container.textContent).toBe(""); // show nothing
expect(container.textContent).toContain("Not supported by your account provider");
});

test("no support in crypto", async () => {
client.getCrypto()!.exportSecretsBundle = undefined;
const { container } = render(getComponent({ client }));
expect(container.textContent).toBe(""); // show nothing
expect(container.textContent).toContain("Not supported by your account provider");
});

test("failed to connect", async () => {
fetchMock.catch(500);
const { container } = render(getComponent({ client }));
expect(container.textContent).toBe(""); // show nothing
expect(container.textContent).toContain("Not supported by your account provider");
});
});
});
Expand Down
Loading

0 comments on commit d58c9fa

Please sign in to comment.