Skip to content

Commit

Permalink
Remove AccountPasswordStore and related flows (#28750)
Browse files Browse the repository at this point in the history
* Remove AccountPasswordStore and related flows

As they are no longer needed since MSC3967

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

* Iterate

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

* Iterate

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

* Improve coverage

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

* Update src/CreateCrossSigning.ts

Co-authored-by: Richard van der Hoff <[email protected]>

* Update comment

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

---------

Signed-off-by: Michael Telatynski <[email protected]>
Co-authored-by: Richard van der Hoff <[email protected]>
  • Loading branch information
t3chguy and richvdh authored Dec 19, 2024
1 parent 2c4a079 commit cd7cf86
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 257 deletions.
60 changes: 10 additions & 50 deletions src/CreateCrossSigning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
Expand All @@ -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"),
Expand Down
2 changes: 0 additions & 2 deletions src/SecurityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
12 changes: 3 additions & 9 deletions src/components/structures/MatrixChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
7 changes: 2 additions & 5 deletions src/components/structures/auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 8 additions & 14 deletions src/components/structures/auth/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
9 changes: 0 additions & 9 deletions src/contexts/SDKContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand Down
35 changes: 0 additions & 35 deletions src/stores/AccountPasswordStore.ts

This file was deleted.

32 changes: 4 additions & 28 deletions src/stores/InitialCryptoSetupStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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"));

Expand All @@ -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");
}

Expand All @@ -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();
Expand All @@ -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");
Expand Down
2 changes: 0 additions & 2 deletions src/stores/SetupEncryptionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -239,7 +238,6 @@ export class SetupEncryptionStore extends EventEmitter {
{
forceReset: true,
resetCrossSigning: true,
accountPassword: SdkContextClass.instance.accountPasswordStore.getPassword(),
},
);
} catch (e) {
Expand Down
Loading

0 comments on commit cd7cf86

Please sign in to comment.