Remove AccountPasswordStore and related flows (#28750)

* Remove AccountPasswordStore and related flows

As they are no longer needed since MSC3967

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update src/CreateCrossSigning.ts

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Update comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
Michael Telatynski
2024-12-19 11:55:05 +00:00
committed by GitHub
parent 2c4a079153
commit cd7cf86b96
13 changed files with 71 additions and 257 deletions

View File

@@ -7,60 +7,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { AuthDict, CrossSigningKeys, MatrixClient, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
import { AuthDict, MatrixClient, MatrixError, UIAResponse } from "matrix-js-sdk/src/matrix";
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
import Modal from "./Modal";
import { _t } from "./languageHandler";
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
/**
* Determine if the homeserver allows uploading device keys with only password auth, or with no auth at
* all (ie. if the homeserver supports MSC3967).
* @param cli The Matrix Client to use
* @returns True if the homeserver allows uploading device keys with only password auth or with no auth
* at all, otherwise false
*/
async function canUploadKeysWithPasswordOnly(cli: MatrixClient): Promise<boolean> {
try {
await cli.uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys);
// If we get here, it's because the server is allowing us to upload keys without
// auth the first time due to MSC3967. Therefore, yes, we can upload keys
// (with or without password, technically, but that's fine).
return true;
} catch (error) {
if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
logger.log("uploadDeviceSigningKeys advertised no flows!");
return false;
}
const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => {
return f.stages.length === 1 && f.stages[0] === "m.login.password";
});
return canUploadKeysWithPasswordOnly;
}
}
/**
* Ensures that cross signing keys are created and uploaded for the user.
* The homeserver may require user-interactive auth to upload the keys, in
* which case the user will be prompted to authenticate. If the homeserver
* allows uploading keys with just an account password and one is provided,
* the keys will be uploaded without user interaction.
* which case the user will be prompted to authenticate.
*
* This function does not set up backups of the created cross-signing keys
* (or message keys): the cross-signing keys are stored locally and will be
* lost requiring a crypto reset, if the user logs out or loses their session.
*
* @param cli The Matrix Client to use
* @param isTokenLogin True if the user logged in via a token login, otherwise false
* @param accountPassword The password that the user logged in with
*/
export async function createCrossSigning(
cli: MatrixClient,
isTokenLogin: boolean,
accountPassword?: string,
): Promise<void> {
export async function createCrossSigning(cli: MatrixClient): Promise<void> {
const cryptoApi = cli.getCrypto();
if (!cryptoApi) {
throw new Error("No crypto API found!");
@@ -69,19 +34,14 @@ export async function createCrossSigning(
const doBootstrapUIAuth = async (
makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
): Promise<void> => {
if (accountPassword && (await canUploadKeysWithPasswordOnly(cli))) {
await makeRequest({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: cli.getUserId(),
},
password: accountPassword,
});
} else if (isTokenLogin) {
// We are hoping the grace period is active
try {
await makeRequest({});
} else {
} catch (error) {
if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
// Not a UIA response
throw error;
}
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("auth|uia|sso_title"),

View File

@@ -191,8 +191,6 @@ export interface AccessSecretStorageOpts {
forceReset?: boolean;
/** Create new cross-signing keys. Only applicable if `forceReset` is `true`. */
resetCrossSigning?: boolean;
/** The cached account password, if available. */
accountPassword?: string;
}
/**

View File

@@ -431,8 +431,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// if cross-signing is not yet set up, do so now if possible.
InitialCryptoSetupStore.sharedInstance().startInitialCryptoSetup(
cli,
Boolean(this.tokenLogin),
this.stores,
this.onCompleteSecurityE2eSetupFinished,
);
this.setStateForNewView({ view: Views.E2E_SETUP });
@@ -504,8 +502,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
UIStore.destroy();
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
window.removeEventListener("resize", this.onWindowResized);
this.stores.accountPasswordStore.clearPassword();
}
private onWindowResized = (): void => {
@@ -1935,8 +1931,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.showScreen("forgot_password");
};
private onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string): Promise<void> => {
return this.onUserCompletedLoginFlow(credentials, password);
private onRegisterFlowComplete = (credentials: IMatrixClientCreds): Promise<void> => {
return this.onUserCompletedLoginFlow(credentials);
};
// returns a promise which resolves to the new MatrixClient
@@ -2003,9 +1999,7 @@ 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, password: string): Promise<void> => {
this.stores.accountPasswordStore.setPassword(password);
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds): Promise<void> => {
// Create and start the client
await Lifecycle.setLoggedIn(credentials);
await this.postLoginSetup();

View File

@@ -48,10 +48,7 @@ interface IProps {
// Called when the user has logged in. Params:
// - The object returned by the login API
// - The user's password, if applicable, (may be cached in memory for a
// short time so the user is not required to re-enter their password
// for operations like uploading cross-signing keys).
onLoggedIn(data: IMatrixClientCreds, password: string): void;
onLoggedIn(data: IMatrixClientCreds): void;
// login shouldn't know or care how registration, password recovery, etc is done.
onRegisterClick(): void;
@@ -199,7 +196,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
this.loginLogic.loginViaPassword(username, phoneCountry, phoneNumber, password).then(
(data) => {
this.setState({ serverIsAlive: true }); // it must be, we logged in.
this.props.onLoggedIn(data, password);
this.props.onLoggedIn(data);
},
(error) => {
if (this.unmounted) return;

View File

@@ -72,10 +72,7 @@ interface IProps {
mobileRegister?: boolean;
// Called when the user has logged in. Params:
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
// - The user's password, if available and applicable (may be cached in memory
// for a short time so the user is not required to re-enter their password
// for operations like uploading cross-signing keys).
onLoggedIn(params: IMatrixClientCreds, password: string): Promise<void>;
onLoggedIn(params: IMatrixClientCreds): Promise<void>;
// registration shouldn't know or care how login is done.
onLoginClick(): void;
onServerConfigChange(config: ValidatedServerConfig): void;
@@ -431,16 +428,13 @@ export default class Registration extends React.Component<IProps, IState> {
newState.busy = false;
newState.completedNoSignin = true;
} else {
await this.props.onLoggedIn(
{
userId,
deviceId: (response as RegisterResponse).device_id!,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken,
},
this.state.formVals.password!,
);
await this.props.onLoggedIn({
userId,
deviceId: (response as RegisterResponse).device_id!,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken,
});
this.setupPushers();
}

View File

@@ -13,7 +13,6 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import LegacyCallHandler from "../LegacyCallHandler";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { SlidingSyncManager } from "../SlidingSyncManager";
import { AccountPasswordStore } from "../stores/AccountPasswordStore";
import { MemberListStore } from "../stores/MemberListStore";
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
import RightPanelStore from "../stores/right-panel/RightPanelStore";
@@ -63,7 +62,6 @@ export class SdkContextClass {
protected _SpaceStore?: SpaceStoreClass;
protected _LegacyCallHandler?: LegacyCallHandler;
protected _TypingStore?: TypingStore;
protected _AccountPasswordStore?: AccountPasswordStore;
protected _UserProfilesStore?: UserProfilesStore;
protected _OidcClientStore?: OidcClientStore;
@@ -149,13 +147,6 @@ export class SdkContextClass {
return this._TypingStore;
}
public get accountPasswordStore(): AccountPasswordStore {
if (!this._AccountPasswordStore) {
this._AccountPasswordStore = new AccountPasswordStore();
}
return this._AccountPasswordStore;
}
public get userProfilesStore(): UserProfilesStore {
if (!this.client) {
throw new Error("Unable to create UserProfilesStore without a client");

View File

@@ -1,35 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 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.
*/
const PASSWORD_TIMEOUT = 5 * 60 * 1000; // five minutes
/**
* Store for the account password.
* This password can be used for a short time after login
* to avoid requestin the password all the time for instance during e2ee setup.
*/
export class AccountPasswordStore {
private password?: string;
private passwordTimeoutId?: ReturnType<typeof setTimeout>;
public setPassword(password: string): void {
this.password = password;
clearTimeout(this.passwordTimeoutId);
this.passwordTimeoutId = setTimeout(this.clearPassword, PASSWORD_TIMEOUT);
}
public getPassword(): string | undefined {
return this.password;
}
public clearPassword = (): void => {
clearTimeout(this.passwordTimeoutId);
this.passwordTimeoutId = undefined;
this.password = undefined;
};
}

View File

@@ -11,7 +11,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { useEffect, useState } from "react";
import { createCrossSigning } from "../CreateCrossSigning";
import { SdkContextClass } from "../contexts/SDKContext";
type Status = "in_progress" | "complete" | "error" | undefined;
@@ -45,8 +44,6 @@ export class InitialCryptoSetupStore extends EventEmitter {
private status: Status = undefined;
private client?: MatrixClient;
private isTokenLogin?: boolean;
private stores?: SdkContextClass;
private onFinished?: (success: boolean) => void;
public static sharedInstance(): InitialCryptoSetupStore {
@@ -62,18 +59,9 @@ export class InitialCryptoSetupStore extends EventEmitter {
* Start the initial crypto setup process.
*
* @param {MatrixClient} client The client to use for the setup
* @param {boolean} isTokenLogin True if the user logged in via a token login, otherwise false
* @param {SdkContextClass} stores The stores to use for the setup
*/
public startInitialCryptoSetup(
client: MatrixClient,
isTokenLogin: boolean,
stores: SdkContextClass,
onFinished: (success: boolean) => void,
): void {
public startInitialCryptoSetup(client: MatrixClient, onFinished: (success: boolean) => void): void {
this.client = client;
this.isTokenLogin = isTokenLogin;
this.stores = stores;
this.onFinished = onFinished;
// We just start this process: it's progress is tracked by the events rather
@@ -89,7 +77,7 @@ export class InitialCryptoSetupStore extends EventEmitter {
* @returns {boolean} True if a retry was initiated, otherwise false
*/
public retry(): boolean {
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) return false;
if (this.client === undefined) return false;
this.doSetup().catch(() => logger.error("Initial crypto setup failed"));
@@ -98,12 +86,10 @@ export class InitialCryptoSetupStore extends EventEmitter {
private reset(): void {
this.client = undefined;
this.isTokenLogin = undefined;
this.stores = undefined;
}
private async doSetup(): Promise<void> {
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) {
if (this.client === undefined) {
throw new Error("No setup is in progress");
}
@@ -115,7 +101,7 @@ export class InitialCryptoSetupStore extends EventEmitter {
try {
// Create the user's cross-signing keys
await createCrossSigning(this.client, this.isTokenLogin, this.stores.accountPasswordStore.getPassword());
await createCrossSigning(this.client);
// Check for any existing backup and enable key backup if there isn't one
const currentKeyBackup = await cryptoApi.checkKeyBackupAndEnable();
@@ -129,16 +115,6 @@ export class InitialCryptoSetupStore extends EventEmitter {
this.emit("update");
this.onFinished?.(true);
} catch (e) {
if (this.isTokenLogin) {
// ignore any failures, we are relying on grace period here
this.reset();
this.status = "complete";
this.emit("update");
this.onFinished?.(true);
return;
}
logger.error("Error bootstrapping cross-signing", e);
this.status = "error";
this.emit("update");

View File

@@ -19,7 +19,6 @@ import { Device, SecretStorage } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
import { SdkContextClass } from "../contexts/SDKContext";
import { asyncSome } from "../utils/arrays";
import { initialiseDehydration } from "../utils/device/dehydration";
@@ -239,7 +238,6 @@ export class SetupEncryptionStore extends EventEmitter {
{
forceReset: true,
resetCrossSigning: true,
accountPassword: SdkContextClass.instance.accountPasswordStore.getPassword(),
},
);
} catch (e) {