mirror of
https://github.com/vector-im/element-web.git
synced 2026-04-19 12:31:08 +02:00
PoC Sign in with QR on new EW using generated QR for MSC4108 v2024
This commit is contained in:
parent
e0cf78b5b8
commit
00c41264b5
@ -81,7 +81,7 @@
|
||||
"lodash": "npm:lodash-es@^4.17.21",
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#hughns/msc4108-2024-signin-on-new-device",
|
||||
"matrix-widget-api": "^1.16.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
|
||||
@ -297,7 +297,43 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
|
||||
const { accessToken, refreshToken, homeserverUrl, identityServerUrl, idToken, clientId, issuer } =
|
||||
await completeOidcLogin(queryParams);
|
||||
|
||||
const {
|
||||
await configureFromCompletedOAuthLogin({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
clientId,
|
||||
issuer,
|
||||
idToken,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Failed to login via OIDC", error);
|
||||
|
||||
onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureFromCompletedOAuthLogin({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
clientId,
|
||||
issuer,
|
||||
idToken,
|
||||
}: {
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
homeserverUrl: string;
|
||||
identityServerUrl?: string;
|
||||
clientId: string;
|
||||
issuer: string;
|
||||
idToken: string;
|
||||
}): Promise<IMatrixClientCreds> {
|
||||
const {
|
||||
user_id: userId,
|
||||
device_id: deviceId,
|
||||
is_guest: isGuest,
|
||||
@ -317,14 +353,8 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
|
||||
await onSuccessfulDelegatedAuthLogin(credentials);
|
||||
// this needs to happen after success handler which clears storages
|
||||
persistOidcAuthenticatedSettings(clientId, issuer, idToken);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Failed to login via OIDC", error);
|
||||
|
||||
onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
|
||||
return false;
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the owner of a given access token.
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
type ISSOFlow,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { isSignInWithQRAvailable } from "matrix-js-sdk/src/rendezvous";
|
||||
|
||||
import { type IMatrixClientCreds } from "./MatrixClientPeg";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
@ -31,7 +32,12 @@ import { isUserRegistrationSupported } from "./utils/oidc/isUserRegistrationSupp
|
||||
* LoginFlow type use the client API /login endpoint
|
||||
* OidcNativeFlow is specific to this client
|
||||
*/
|
||||
export type ClientLoginFlow = LoginFlow | OidcNativeFlow;
|
||||
export type ClientLoginFlow = LoginFlow | OidcNativeFlow | LoginWithQrFlow;
|
||||
|
||||
export interface LoginWithQrFlow {
|
||||
type: "loginWithQrFlow";
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
interface ILoginOptions {
|
||||
defaultDeviceDisplayName?: string;
|
||||
@ -116,7 +122,17 @@ export default class Login {
|
||||
SdkConfig.get().oidc_static_clients,
|
||||
isRegistration,
|
||||
);
|
||||
return [oidcFlow];
|
||||
let possibleQrFlow: LoginWithQrFlow | undefined;
|
||||
try {
|
||||
// TODO: this seems wasteful
|
||||
const tempClient = this.createTemporaryClient();
|
||||
// we reuse the clientId from the oidcFlow for QR login
|
||||
// it might be that we later find that the homeserver is different and we initialise a new client
|
||||
possibleQrFlow = await tryInitLoginWithQRFlow(tempClient, oidcFlow.clientId);
|
||||
} catch (e) {
|
||||
logger.warn("Could not fetch server versions for login with QR support, assuming unsupported", e);
|
||||
}
|
||||
return possibleQrFlow ? [possibleQrFlow, oidcFlow] : [oidcFlow];
|
||||
} catch (error) {
|
||||
logger.error("Failed to get oidc native flow", error);
|
||||
}
|
||||
@ -288,3 +304,20 @@ export async function sendLoginRequest(
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
const tryInitLoginWithQRFlow = async (
|
||||
tempClient: MatrixClient,
|
||||
clientId: string,
|
||||
): Promise<LoginWithQrFlow | undefined> => {
|
||||
// This could fail because the server doesn't support the API or it requires authentication
|
||||
const canUseServer = await isSignInWithQRAvailable(tempClient);
|
||||
|
||||
if (!canUseServer) return undefined;
|
||||
|
||||
const flow = {
|
||||
type: "loginWithQrFlow",
|
||||
clientId,
|
||||
} satisfies LoginWithQrFlow;
|
||||
|
||||
return flow;
|
||||
};
|
||||
|
||||
@ -2130,9 +2130,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
* Note: SSO users (and any others using token login) currently do not pass through
|
||||
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||
*/
|
||||
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds): Promise<void> => {
|
||||
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, alreadySignedIn = false): Promise<void> => {
|
||||
// Create and start the client
|
||||
await Lifecycle.setLoggedIn(credentials);
|
||||
if (!alreadySignedIn) {
|
||||
await Lifecycle.setLoggedIn(credentials);
|
||||
}
|
||||
await this.postLoginSetup();
|
||||
|
||||
PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN);
|
||||
|
||||
@ -9,11 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX, memo, type ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type SSOFlow, SSOAction } from "matrix-js-sdk/src/matrix";
|
||||
import { type SSOFlow, type MatrixClient, SSOAction } from "matrix-js-sdk/src/matrix";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import { _t, UserFriendlyError } from "../../../languageHandler";
|
||||
import Login, { type ClientLoginFlow, type OidcNativeFlow } from "../../../Login";
|
||||
import Login, { type LoginWithQrFlow, type ClientLoginFlow, type OidcNativeFlow } from "../../../Login";
|
||||
import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils";
|
||||
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
@ -33,6 +35,9 @@ import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig
|
||||
import { filterBoolean } from "../../../utils/arrays";
|
||||
import { startOidcLogin } from "../../../utils/oidc/authorize";
|
||||
import { ModuleApi } from "../../../modules/Api.ts";
|
||||
import LoginWithQR from "../../views/auth/LoginWithQR.tsx";
|
||||
import { Mode } from "../../views/auth/LoginWithQR-types.ts";
|
||||
import createMatrixClient from "../../../utils/createMatrixClient.ts";
|
||||
|
||||
interface IProps {
|
||||
serverConfig: ValidatedServerConfig;
|
||||
@ -51,7 +56,8 @@ interface IProps {
|
||||
|
||||
// Called when the user has logged in. Params:
|
||||
// - The object returned by the login API
|
||||
onLoggedIn(data: IMatrixClientCreds): void;
|
||||
// - alreadySignedIn: true if the user was already signed in (e.g. QR login) and only the post login setup is needed
|
||||
onLoggedIn(data: IMatrixClientCreds, alreadySignedIn?: boolean): void;
|
||||
|
||||
// login shouldn't know or care how registration, password recovery, etc is done.
|
||||
onRegisterClick?(): void;
|
||||
@ -79,6 +85,9 @@ interface IState {
|
||||
serverIsAlive: boolean;
|
||||
serverErrorIsFatal: boolean;
|
||||
serverDeadError?: ReactNode;
|
||||
|
||||
loginWithQrInProgress: boolean;
|
||||
loginWithQrClient?: MatrixClient;
|
||||
}
|
||||
|
||||
type OnPasswordLogin = {
|
||||
@ -110,6 +119,8 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
serverIsAlive: true,
|
||||
serverErrorIsFatal: false,
|
||||
serverDeadError: "",
|
||||
|
||||
loginWithQrInProgress: false,
|
||||
};
|
||||
|
||||
// map from login step type to a function which will render a control
|
||||
@ -123,6 +134,7 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
"m.login.sso": () => this.renderSsoStep("sso"),
|
||||
"oidcNativeFlow": () => this.renderOidcNativeStep(),
|
||||
"loginWithQrFlow": () => this.renderLoginWithQRStep(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -402,7 +414,7 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
if (!this.state.flows) return null;
|
||||
|
||||
// this is the ideal order we want to show the flows in
|
||||
const order = ["oidcNativeFlow", "m.login.password", "m.login.sso"];
|
||||
const order = ["loginWithQrFlow", "oidcNativeFlow", "m.login.password", "m.login.sso"];
|
||||
|
||||
const flows = filterBoolean(order.map((type) => this.state.flows?.find((flow) => flow.type === type)));
|
||||
return (
|
||||
@ -451,7 +463,7 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{_t("action|continue")}
|
||||
{_t("Sign in manually")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@ -472,6 +484,42 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
);
|
||||
};
|
||||
|
||||
private startLoginWithQR = (): void => {
|
||||
if (this.state.loginWithQrInProgress) return;
|
||||
// pick our device ID
|
||||
const deviceId = secureRandomString(10);
|
||||
const loginWithQrClient = createMatrixClient({
|
||||
baseUrl: this.loginLogic.getHomeserverUrl(),
|
||||
idBaseUrl: this.loginLogic.getIdentityServerUrl(),
|
||||
deviceId,
|
||||
});
|
||||
this.setState({ loginWithQrInProgress: true, loginWithQrClient });
|
||||
};
|
||||
|
||||
private renderLoginWithQRStep = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<Button className="mx_Login_fullWidthButton" kind="primary" size="sm" onClick={this.startLoginWithQR}>
|
||||
<QrCodeIcon />
|
||||
{_t("Sign in with QR code")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
private onLoginWithQRFinished = (success: boolean, credentials?: IMatrixClientCreds): void => {
|
||||
if (credentials) {
|
||||
this.props.onLoggedIn(credentials, true);
|
||||
} else if (!success) {
|
||||
this.state.loginWithQrClient?.stopClient();
|
||||
this.setState({ loginWithQrInProgress: false, loginWithQrClient: undefined });
|
||||
}
|
||||
};
|
||||
|
||||
private get qrClientId(): string {
|
||||
return (this.state.flows?.find((flow) => flow.type === "loginWithQrFlow") as LoginWithQrFlow).clientId ?? "";
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const loader =
|
||||
this.isBusy() && !this.state.busyLoggingIn ? (
|
||||
@ -532,20 +580,34 @@ class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
<AuthPage>
|
||||
<AuthHeader disableLanguageSelector={this.props.isSyncing || this.state.busyLoggingIn} />
|
||||
<AuthBody>
|
||||
<h1>
|
||||
{_t("action|sign_in")}
|
||||
{loader}
|
||||
</h1>
|
||||
{errorTextSection}
|
||||
{serverDeadSection}
|
||||
<ServerPicker
|
||||
serverConfig={this.props.serverConfig}
|
||||
onServerConfigChange={this.props.onServerConfigChange}
|
||||
disabled={this.isBusy()}
|
||||
/>
|
||||
{this.renderLoginComponentForFlows()}
|
||||
{this.props.children}
|
||||
{footer}
|
||||
{this.state.loginWithQrInProgress ? (
|
||||
<>
|
||||
<LoginWithQR
|
||||
onFinished={this.onLoginWithQRFinished}
|
||||
mode={Mode.Show}
|
||||
client={this.state.loginWithQrClient!}
|
||||
clientId={this.qrClientId}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{" "}
|
||||
<h1>
|
||||
{_t("action|sign_in")}
|
||||
{loader}
|
||||
</h1>
|
||||
{errorTextSection}
|
||||
{serverDeadSection}
|
||||
<ServerPicker
|
||||
serverConfig={this.props.serverConfig}
|
||||
onServerConfigChange={this.props.onServerConfigChange}
|
||||
disabled={this.isBusy()}
|
||||
/>
|
||||
{this.renderLoginComponentForFlows()}
|
||||
{this.props.children}
|
||||
{footer}
|
||||
</>
|
||||
)}
|
||||
</AuthBody>
|
||||
</AuthPage>
|
||||
);
|
||||
|
||||
@ -9,24 +9,27 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
linkNewDeviceByGeneratingQR,
|
||||
MSC4108FailureReason,
|
||||
MSC4108RendezvousSession,
|
||||
MSC4108SecureChannel,
|
||||
MSC4108SignInWithQR,
|
||||
RendezvousError,
|
||||
type RendezvousFailureReason,
|
||||
RendezvousIntent,
|
||||
signInByGeneratingQR,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { AutoDiscovery, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||
import { configureFromCompletedOAuthLogin, restoreSessionFromStorage } from "../../../Lifecycle";
|
||||
import { type IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
interface IProps {
|
||||
client: MatrixClient;
|
||||
clientId: string;
|
||||
mode: Mode;
|
||||
onFinished(...args: any): void;
|
||||
onFinished(success: boolean, credentials?: IMatrixClientCreds): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@ -37,6 +40,8 @@ interface IState {
|
||||
userCode?: string;
|
||||
checkCode?: string;
|
||||
failureReason?: FailureReason;
|
||||
homeserverName?: string;
|
||||
newClient?: MatrixClient;
|
||||
}
|
||||
|
||||
export enum LoginWithQRFailureReason {
|
||||
@ -65,7 +70,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
private get ourIntent(): RendezvousIntent {
|
||||
return RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE;
|
||||
return this.props.client.getUserId()
|
||||
? RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
|
||||
: RendezvousIntent.LOGIN_ON_NEW_DEVICE;
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
@ -99,23 +106,18 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
private onFinished(success: boolean): void {
|
||||
private onFinished(success: boolean, credentials?: IMatrixClientCreds): void {
|
||||
this.finished = true;
|
||||
this.props.onFinished(success);
|
||||
this.props.onFinished(success, credentials);
|
||||
}
|
||||
|
||||
private generateAndShowCode = async (): Promise<void> => {
|
||||
let rendezvous: MSC4108SignInWithQR;
|
||||
try {
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
|
||||
await rendezvous.generateCode();
|
||||
rendezvous =
|
||||
this.ourIntent === RendezvousIntent.LOGIN_ON_NEW_DEVICE
|
||||
? await signInByGeneratingQR(this.props.client, this.onFailure)
|
||||
: await linkNewDeviceByGeneratingQR(this.props.client, this.onFailure);
|
||||
this.setState({
|
||||
phase: Phase.ShowingQR,
|
||||
rendezvous,
|
||||
@ -136,6 +138,12 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
phase: Phase.OutOfBandConfirmation,
|
||||
verificationUri,
|
||||
});
|
||||
} else {
|
||||
const { serverName } = await rendezvous.negotiateProtocols();
|
||||
this.setState({
|
||||
phase: Phase.OutOfBandConfirmation,
|
||||
homeserverName: serverName,
|
||||
});
|
||||
}
|
||||
|
||||
// we ask the user to confirm that the channel is secure
|
||||
@ -175,8 +183,75 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
// done
|
||||
this.onFinished(true);
|
||||
} else {
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown });
|
||||
throw new Error("New device flows around OIDC are not yet implemented");
|
||||
if (!this.state.homeserverName) {
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown });
|
||||
throw new Error("Homeserver name not found in state");
|
||||
}
|
||||
|
||||
// in the 2025 version we would check if the homeserver is on a different base URL, but for the 2024 version
|
||||
// we can't do this as the temporary client doesn't know the server name.
|
||||
|
||||
const metadata = await this.props.client.getAuthMetadata();
|
||||
const deviceId = this.props.client.getDeviceId()!;
|
||||
const { userCode } = await this.state.rendezvous.deviceAuthorizationGrant({
|
||||
metadata,
|
||||
clientId: this.props.clientId,
|
||||
deviceId,
|
||||
});
|
||||
this.setState({ phase: Phase.WaitingForDevice, userCode });
|
||||
|
||||
const datr = await this.state.rendezvous.completeLoginOnNewDevice({
|
||||
clientId: this.props.clientId,
|
||||
});
|
||||
|
||||
if (datr) {
|
||||
// the 2024 version of the spec only gives the server name, but the 2025 version will give the base URL
|
||||
// so, we do a discovery for now.
|
||||
const homeserverUrl = (await AutoDiscovery.findClientConfig(this.state.homeserverName))?.["m.homeserver"]?.base_url;
|
||||
|
||||
if (!homeserverUrl) {
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown });
|
||||
logger.error("Failed to discover homeserver URL");
|
||||
throw new Error("Failed to discover homeserver URL");
|
||||
}
|
||||
|
||||
// TODO: this is not the right way to do this
|
||||
|
||||
// store and use the new credentials
|
||||
const credentials = await configureFromCompletedOAuthLogin({
|
||||
accessToken: datr.access_token,
|
||||
refreshToken: datr.refresh_token,
|
||||
homeserverUrl,
|
||||
clientId: this.props.clientId,
|
||||
idToken: datr.id_token ?? "", // I'm not sure the idToken is actually required
|
||||
issuer: metadata!.issuer,
|
||||
identityServerUrl: undefined, // PROTOTYPE: we should have stored this from before
|
||||
});
|
||||
|
||||
const { secrets } = await this.state.rendezvous.shareSecrets();
|
||||
|
||||
await restoreSessionFromStorage();
|
||||
|
||||
if (secrets) {
|
||||
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||
if (crypto?.importSecretsBundle) {
|
||||
await crypto.importSecretsBundle(secrets);
|
||||
// it should be sufficient to just upload the device keys with the signature
|
||||
// but this seems to do the job for now
|
||||
await crypto.crossSignDevice(deviceId);
|
||||
|
||||
// PROTOTYPE: this is a fudge to bypass the complete security step
|
||||
window.location.reload();
|
||||
} else {
|
||||
logger.warn("Crypto not initialised");
|
||||
logger.warn("Crypto not initialised or no importSecretsBundle() method, cannot import secrets from QR login");
|
||||
} } else {
|
||||
logger.warn("No secrets received from QR login");
|
||||
}
|
||||
|
||||
// done
|
||||
this.onFinished(true, credentials);
|
||||
}
|
||||
}
|
||||
} catch (e: RendezvousError | unknown) {
|
||||
logger.error("Error whilst approving sign in", e);
|
||||
|
||||
@ -11,15 +11,16 @@ import {
|
||||
type IServerVersions,
|
||||
type OidcClientConfig,
|
||||
type MatrixClient,
|
||||
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 { isSignInWithQRAvailable } from "matrix-js-sdk/src/rendezvous";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import { SettingsSubsection } from "../shared/SettingsSubsection";
|
||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
|
||||
|
||||
interface IProps {
|
||||
onShowQr: () => void;
|
||||
@ -28,19 +29,16 @@ interface IProps {
|
||||
isCrossSigningReady?: boolean;
|
||||
}
|
||||
|
||||
export function shouldShowQr(
|
||||
export async function shouldShowQrForLinkNewDevice(
|
||||
cli: MatrixClient,
|
||||
isCrossSigningReady: boolean,
|
||||
oidcClientConfig?: OidcClientConfig,
|
||||
versions?: IServerVersions,
|
||||
): boolean {
|
||||
const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
|
||||
|
||||
const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
|
||||
): Promise<boolean> {
|
||||
const doesServerHaveSupport = await isSignInWithQRAvailable(cli);
|
||||
|
||||
return (
|
||||
!!deviceAuthorizationGrantSupported &&
|
||||
msc4108Supported &&
|
||||
doesServerHaveSupport &&
|
||||
!!cli.getCrypto()?.exportSecretsBundle &&
|
||||
isCrossSigningReady
|
||||
);
|
||||
@ -48,7 +46,7 @@ export function shouldShowQr(
|
||||
|
||||
const LoginWithQRSection: React.FC<IProps> = ({ onShowQr, versions, oidcClientConfig, isCrossSigningReady }) => {
|
||||
const cli = useMatrixClientContext();
|
||||
const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions);
|
||||
const offerShowQr = useAsyncMemo(() => shouldShowQrForLinkNewDevice(cli, !!isCrossSigningReady, oidcClientConfig, versions), [cli, isCrossSigningReady, oidcClientConfig, versions], false);
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@ -434,8 +434,8 @@ importers:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
matrix-js-sdk:
|
||||
specifier: github:matrix-org/matrix-js-sdk#develop
|
||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/aed74c5a7259497de590600e2f25af4b39b7734b
|
||||
specifier: github:matrix-org/matrix-js-sdk#hughns/msc4108-2024-signin-on-new-device
|
||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3fa77611104e0ae11217056cc5b0dd62177617e3
|
||||
matrix-widget-api:
|
||||
specifier: ^1.17.0
|
||||
version: 1.17.0
|
||||
@ -9754,9 +9754,9 @@ packages:
|
||||
matrix-events-sdk@0.0.1:
|
||||
resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==}
|
||||
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/aed74c5a7259497de590600e2f25af4b39b7734b:
|
||||
resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/aed74c5a7259497de590600e2f25af4b39b7734b}
|
||||
version: 41.2.0
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3fa77611104e0ae11217056cc5b0dd62177617e3:
|
||||
resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3fa77611104e0ae11217056cc5b0dd62177617e3}
|
||||
version: 41.3.0
|
||||
engines: {node: '>=22.0.0'}
|
||||
|
||||
matrix-web-i18n@3.6.0:
|
||||
@ -10348,9 +10348,9 @@ packages:
|
||||
resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
p-retry@7.1.1:
|
||||
resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==}
|
||||
engines: {node: '>=20'}
|
||||
p-retry@8.0.0:
|
||||
resolution: {integrity: sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
@ -23349,7 +23349,7 @@ snapshots:
|
||||
|
||||
matrix-events-sdk@0.0.1: {}
|
||||
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/aed74c5a7259497de590600e2f25af4b39b7734b:
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3fa77611104e0ae11217056cc5b0dd62177617e3:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@matrix-org/matrix-sdk-crypto-wasm': 18.0.0
|
||||
@ -23361,7 +23361,7 @@ snapshots:
|
||||
matrix-events-sdk: 0.0.1
|
||||
matrix-widget-api: 1.17.0
|
||||
oidc-client-ts: 3.5.0
|
||||
p-retry: 7.1.1
|
||||
p-retry: 8.0.0
|
||||
sdp-transform: 3.0.0
|
||||
unhomoglyph: 1.0.6
|
||||
uuid: 13.0.0
|
||||
@ -24129,7 +24129,7 @@ snapshots:
|
||||
is-network-error: 1.3.0
|
||||
retry: 0.13.1
|
||||
|
||||
p-retry@7.1.1:
|
||||
p-retry@8.0.0:
|
||||
dependencies:
|
||||
is-network-error: 1.3.0
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user