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

Commit d58c9fa

Browse files
authored
Always show link new device flow even if unsupported (#147)
* Always show link new device flow even if unsupported Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
1 parent 51a5cf6 commit d58c9fa

File tree

9 files changed

+350
-110
lines changed

9 files changed

+350
-110
lines changed
1.71 KB
Loading

res/css/views/auth/_LoginWithQR.pcss

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ Please see LICENSE files in the repository root for full details.
1111
margin-bottom: $spacing-16;
1212
}
1313

14+
.mx_LoginWithQRSection {
15+
.mx_AccessibleButton_kind_primary + p {
16+
color: var(--cpd-color-text-secondary);
17+
margin-top: var(--cpd-space-2x);
18+
}
19+
}
20+
1421
.mx_LoginWithQRSection .mx_AccessibleButton svg {
1522
margin-right: $spacing-12;
1623
}
@@ -135,9 +142,16 @@ Please see LICENSE files in the repository root for full details.
135142
padding: var(--cpd-space-3x);
136143
gap: 10px;
137144

138-
background-color: var(--cpd-color-bg-success-subtle);
145+
background-color: var(--cpd-color-bg-subtle-secondary);
139146
svg {
140-
color: var(--cpd-color-icon-success-primary);
147+
color: var(--cpd-color-icon-secondary);
148+
}
149+
150+
&.mx_LoginWithQR_icon--success {
151+
background-color: var(--cpd-color-bg-success-subtle);
152+
svg {
153+
color: var(--cpd-color-icon-success-primary);
154+
}
141155
}
142156

143157
&.mx_LoginWithQR_icon--critical {

src/components/structures/UserMenu.tsx

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

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

1212
import { MatrixClientPeg } from "../../MatrixClientPeg";
1313
import defaultDispatcher from "../../dispatcher/dispatcher";
@@ -44,8 +44,6 @@ import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg";
4444
import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast";
4545
import { SDKContext } from "../../contexts/SDKContext";
4646
import { shouldShowFeedback } from "../../utils/Feedback";
47-
import { shouldShowQr } from "../views/settings/devices/LoginWithQRSection";
48-
import { Features } from "../../settings/Settings";
4947

5048
interface IProps {
5149
isPanelCollapsed: boolean;
@@ -60,8 +58,6 @@ interface IState {
6058
isHighContrast: boolean;
6159
selectedSpace?: Room | null;
6260
showLiveAvatarAddon: boolean;
63-
showQrLogin: boolean;
64-
supportsQrLogin: boolean;
6561
}
6662

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

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

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

141-
private checkQrLoginSupport = async (): Promise<void> => {
142-
if (!this.context.client || !SettingsStore.getValue(Features.OidcNativeFlow)) return;
143-
144-
const { issuer } = await this.context.client.getAuthIssuer().catch(() => ({ issuer: undefined }));
145-
if (issuer) {
146-
const [oidcClientConfig, versions, wellKnown, isCrossSigningReady] = await Promise.all([
147-
discoverAndValidateOIDCIssuerWellKnown(issuer),
148-
this.context.client.getVersions(),
149-
this.context.client.waitForClientWellKnown(),
150-
this.context.client.getCrypto()?.isCrossSigningReady(),
151-
]);
152-
153-
const supportsQrLogin = shouldShowQr(
154-
this.context.client,
155-
!!isCrossSigningReady,
156-
oidcClientConfig,
157-
versions,
158-
wellKnown,
159-
);
160-
this.setState({ supportsQrLogin, showQrLogin: true });
161-
}
162-
};
163-
164134
private isUserOnDarkTheme(): boolean {
165135
if (SettingsStore.getValue("use_system_theme")) {
166136
return window.matchMedia("(prefers-color-scheme: dark)").matches;
@@ -363,28 +333,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
363333
);
364334
}
365335

366-
let linkNewDeviceButton: JSX.Element | undefined;
367-
if (this.state.showQrLogin) {
368-
const extraProps: Omit<
369-
React.ComponentProps<typeof IconizedContextMenuOption>,
370-
"iconClassname" | "label" | "onClick"
371-
> = {};
372-
if (!this.state.supportsQrLogin) {
373-
extraProps.disabled = true;
374-
extraProps.title = _t("user_menu|link_new_device_not_supported");
375-
extraProps.caption = _t("user_menu|link_new_device_not_supported_caption");
376-
extraProps.placement = "right";
377-
}
378-
379-
linkNewDeviceButton = (
380-
<IconizedContextMenuOption
381-
{...extraProps}
382-
iconClassName="mx_UserMenu_iconQr"
383-
label={_t("user_menu|link_new_device")}
384-
onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })}
385-
/>
386-
);
387-
}
336+
const linkNewDeviceButton = (
337+
<IconizedContextMenuOption
338+
iconClassName="mx_UserMenu_iconQr"
339+
label={_t("user_menu|link_new_device")}
340+
onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })}
341+
/>
342+
);
388343

389344
let primaryOptionList = (
390345
<IconizedContextMenuOptionList>

src/components/views/auth/LoginWithQRFlow.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/i
1717
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
1818
import { Heading, MFAInput, Text } from "@vector-im/compound-web";
1919
import classNames from "classnames";
20+
import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
2021

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

9596
switch (this.props.phase) {
9697
case Phase.Error: {
97-
let success = false;
98+
backButton = false;
99+
100+
let Icon = ErrorIcon;
101+
let success: boolean | null = false;
98102
let title: string | undefined;
99103
let message: ReactNode | undefined;
100104

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

139143
case ClientRendezvousFailureReason.OtherDeviceAlreadySignedIn:
140144
success = true;
145+
Icon = CheckCircleSolidIcon;
141146
title = _t("auth|qr_code_login|error_other_device_already_signed_in_title");
142147
message = _t("auth|qr_code_login|error_other_device_already_signed_in");
143148
break;
@@ -157,6 +162,15 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
157162
message = _t("auth|qr_code_login|error_etag_missing");
158163
break;
159164

165+
case LegacyRendezvousFailureReason.HomeserverLacksSupport:
166+
case ClientRendezvousFailureReason.HomeserverLacksSupport:
167+
success = null;
168+
Icon = QrCodeIcon;
169+
backButton = true;
170+
title = _t("auth|qr_code_login|unsupported_heading");
171+
message = _t("auth|qr_code_login|unsupported_explainer");
172+
break;
173+
160174
case MSC4108FailureReason.DeviceAlreadyExists:
161175
case MSC4108FailureReason.DeviceNotFound:
162176
case MSC4108FailureReason.UnexpectedMessageReceived:
@@ -168,15 +182,15 @@ export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906P
168182
break;
169183
}
170184
className = "mx_LoginWithQR_error";
171-
backButton = false;
172185
main = (
173186
<>
174187
<div
175188
className={classNames("mx_LoginWithQR_icon", {
176-
"mx_LoginWithQR_icon--critical": !success,
189+
"mx_LoginWithQR_icon--critical": success === false,
190+
"mx_LoginWithQR_icon--success": success === true,
177191
})}
178192
>
179-
{success ? <CheckCircleSolidIcon width="32px" /> : <ErrorIcon width="32px" />}
193+
<Icon width="32px" height="32px" />
180194
</div>
181195
<Heading as="h1" size="sm" weight="semibold">
182196
{title}

src/components/views/settings/devices/LoginWithQRSection.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import {
1818
DEVICE_CODE_SCOPE,
1919
} from "matrix-js-sdk/src/matrix";
2020
import QrCodeIcon from "@vector-im/compound-design-tokens/assets/web/icons/qr-code";
21+
import { Text } from "@vector-im/compound-web";
2122

2223
import { _t } from "../../../../languageHandler";
2324
import AccessibleButton from "../../elements/AccessibleButton";
2425
import SettingsSubsection from "../shared/SettingsSubsection";
25-
import SettingsStore from "../../../../settings/SettingsStore";
26-
import { Features } from "../../../../settings/Settings";
2726
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
2827

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

6665
return (
67-
deviceAuthorizationGrantSupported &&
66+
!!deviceAuthorizationGrantSupported &&
6867
msc4108Supported &&
69-
SettingsStore.getValue(Features.OidcNativeFlow) &&
7068
!!cli.getCrypto()?.exportSecretsBundle &&
7169
isCrossSigningReady
7270
);
@@ -85,19 +83,15 @@ const LoginWithQRSection: React.FC<IProps> = ({
8583
? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown)
8684
: shouldShowQrLegacy(versions, wellKnown, capabilities);
8785

88-
// don't show anything if no method is available
89-
if (!offerShowQr) {
90-
return null;
91-
}
92-
9386
return (
9487
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
9588
<div className="mx_LoginWithQRSection">
9689
<p className="mx_SettingsTab_subsectionText">{_t("settings|sessions|sign_in_with_qr_description")}</p>
97-
<AccessibleButton onClick={onShowQr} kind="primary">
90+
<AccessibleButton onClick={onShowQr} kind="primary" disabled={!offerShowQr}>
9891
<QrCodeIcon height={20} width={20} />
9992
{_t("settings|sessions|sign_in_with_qr_button")}
10093
</AccessibleButton>
94+
{!offerShowQr && <Text size="sm">{_t("settings|sessions|sign_in_with_qr_unsupported")}</Text>}
10195
</div>
10296
</SettingsSubsection>
10397
);

src/i18n/strings/en_EN.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@
283283
"security_code_prompt": "If asked, enter the code below on your other device.",
284284
"select_qr_code": "Select \"%(scanQRCode)s\"",
285285
"sign_in_new_device": "Sign in new device",
286+
"unsupported_explainer": "Your account provider doesn't support signing into a new device with a QR code.",
287+
"unsupported_heading": "QR code not supported",
286288
"waiting_for_device": "Waiting for device to sign in"
287289
},
288290
"register_action": "Create Account",
@@ -2855,6 +2857,7 @@
28552857
"sign_in_with_qr": "Link new device",
28562858
"sign_in_with_qr_button": "Show QR code",
28572859
"sign_in_with_qr_description": "Use a QR code to sign in to another device and set up secure messaging.",
2860+
"sign_in_with_qr_unsupported": "Not supported by your account provider",
28582861
"sign_out": "Sign out of this session",
28592862
"sign_out_all_other_sessions": "Sign out of all other sessions (%(otherSessionsCount)s)",
28602863
"sign_out_confirm_description": {
@@ -3829,8 +3832,6 @@
38293832
},
38303833
"user_menu": {
38313834
"link_new_device": "Link new device",
3832-
"link_new_device_not_supported": "Not supported",
3833-
"link_new_device_not_supported_caption": "You need to sign in manually",
38343835
"settings": "All settings",
38353836
"switch_theme_dark": "Switch to dark mode",
38363837
"switch_theme_light": "Switch to light mode"

test/components/views/settings/devices/LoginWithQRSection-test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,19 @@ describe("<LoginWithQRSection />", () => {
137137

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

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

149149
test("failed to connect", async () => {
150150
fetchMock.catch(500);
151151
const { container } = render(getComponent({ client }));
152-
expect(container.textContent).toBe(""); // show nothing
152+
expect(container.textContent).toContain("Not supported by your account provider");
153153
});
154154
});
155155
});

0 commit comments

Comments
 (0)