diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 32817623..16c7bd26 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -379,19 +379,15 @@ async function loadClient(): Promise { } catch (err) { if (err instanceof MatrixError && err.errcode === "M_UNKNOWN_TOKEN") { // We can't use this session anymore, so let's log it out - try { - const client = await initClient(initClientParams, false); // Don't need the crypto store just to log out) - await client.logout(true); - } catch (err) { - logger.warn( - "The previous session was unable to login, and we couldn't log it out: " + - err, - ); - } + logger.log( + "The session from local store is invalid; continuing without a client", + ); + clearSession(); + // returning null = "no client` pls register" (undefined = "loading" which is the current value when reaching this line) + return null; } throw err; } - /* eslint-enable camelcase */ } catch (err) { clearSession(); throw err; diff --git a/src/auth/LoginPage.tsx b/src/auth/LoginPage.tsx index 5db36781..5cfa2c1b 100644 --- a/src/auth/LoginPage.tsx +++ b/src/auth/LoginPage.tsx @@ -32,8 +32,8 @@ export const LoginPage: FC = () => { const { t } = useTranslation(); usePageTitle(t("login_title")); - const { setClient } = useClient(); - const login = useInteractiveLogin(); + const { client, setClient } = useClient(); + const login = useInteractiveLogin(client); const homeserver = Config.defaultHomeserverUrl(); // TODO: Make this configurable const usernameRef = useRef(null); const passwordRef = useRef(null); diff --git a/src/auth/useInteractiveLogin.ts b/src/auth/useInteractiveLogin.ts index 28b005ff..7877b285 100644 --- a/src/auth/useInteractiveLogin.ts +++ b/src/auth/useInteractiveLogin.ts @@ -24,8 +24,15 @@ import { import { initClient } from "../utils/matrix"; import { Session } from "../ClientContext"; - -export function useInteractiveLogin(): ( +/** + * This provides the login method to login using user credentials. + * @param oldClient If there is an already authenticated client it should be passed to this hook + * this allows the interactive login to sign out the client before logging in. + * @returns A async method that can be called/awaited to log in with the provided credentials. + */ +export function useInteractiveLogin( + oldClient?: MatrixClient, +): ( homeserver: string, username: string, password: string, @@ -36,47 +43,52 @@ export function useInteractiveLogin(): ( username: string, password: string, ) => Promise<[MatrixClient, Session]> - >(async (homeserver: string, username: string, password: string) => { - const authClient = createClient({ baseUrl: homeserver }); + >( + async (homeserver: string, username: string, password: string) => { + const authClient = createClient({ baseUrl: homeserver }); - const interactiveAuth = new InteractiveAuth({ - matrixClient: authClient, - doRequest: (): Promise => - authClient.login("m.login.password", { - identifier: { - type: "m.id.user", - user: username, - }, - password, - }), - stateUpdated: (): void => {}, - requestEmailToken: (): Promise<{ sid: string }> => { - return Promise.resolve({ sid: "" }); - }, - }); + const interactiveAuth = new InteractiveAuth({ + matrixClient: authClient, + doRequest: (): Promise => + authClient.login("m.login.password", { + identifier: { + type: "m.id.user", + user: username, + }, + password, + }), + stateUpdated: (): void => {}, + requestEmailToken: (): Promise<{ sid: string }> => { + return Promise.resolve({ sid: "" }); + }, + }); - // XXX: This claims to return an IAuthData which contains none of these - // things - the js-sdk types may be wrong? - /* eslint-disable camelcase,@typescript-eslint/no-explicit-any */ - const { user_id, access_token, device_id } = - (await interactiveAuth.attemptAuth()) as any; - const session = { - user_id, - access_token, - device_id, - passwordlessUser: false, - }; + // XXX: This claims to return an IAuthData which contains none of these + // things - the js-sdk types may be wrong? + /* eslint-disable camelcase,@typescript-eslint/no-explicit-any */ + const { user_id, access_token, device_id } = + (await interactiveAuth.attemptAuth()) as any; + const session = { + user_id, + access_token, + device_id, + passwordlessUser: false, + }; - const client = await initClient( - { - baseUrl: homeserver, - accessToken: access_token, - userId: user_id, - deviceId: device_id, - }, - false, - ); - /* eslint-enable camelcase */ - return [client, session]; - }, []); + // To not confuse the rust crypto sessions we need to logout the old client before initializing the new one. + await oldClient?.logout(true); + const client = await initClient( + { + baseUrl: homeserver, + accessToken: access_token, + userId: user_id, + deviceId: device_id, + }, + false, + ); + /* eslint-enable camelcase */ + return [client, session]; + }, + [oldClient], + ); } diff --git a/src/utils/matrix.ts b/src/utils/matrix.ts index d5735e00..f27067bf 100644 --- a/src/utils/matrix.ts +++ b/src/utils/matrix.ts @@ -71,10 +71,12 @@ async function waitForSync(client: MatrixClient): Promise { /** * Initialises and returns a new standalone Matrix Client. - * If false is passed for the 'restore' parameter, corresponding crypto - * data is cleared before the client initialization. + * This can only be called safely if no other client is running + * otherwise rust crypto will throw since it is not ready to initialize a new session. + * If another client is running make sure `.logout()` is called before executing this function. * @param clientOptions Object of options passed through to the client - * @param restore Whether the session is being restored from storage + * @param restore If the rust crypto should be reset before the cient initialization or + * if the initialization should try to restore the crypto state from the indexDB. * @returns The MatrixClient instance */ export async function initClient( @@ -130,20 +132,17 @@ export async function initClient( fallbackICEServerAllowed: fallbackICEServerAllowed, }); - // In case of registering a new matrix account caused by broken store state. This is particularly needed for: + // In case of logging in a new matrix account but there is still crypto local store. This is needed for: // - We lost the auth tokens and cannot restore the client resulting in registering a new user. - // - Need to make sure any possible leftover crypto store gets cleared. + // - We start the sign in flow but are registered with a guest user. (It should additionally log out the guest before) // - A new account is created because of missing LocalStorage: "matrix-auth-store", but the crypto IndexDB is still available. - // This would result in conflicting crypto store userId vs matrixClient userId. Caused by EC 0.6.1 if (!restore) { - client.clearStores(); + await client.clearStores(); } // Start client store. // Note: The `client.store` is used to store things like sync results. It's independent of // the cryptostore, and uses a separate indexeddb database. - - // start the client store (totally independent to the crypto store) try { await client.store.startup(); } catch (error) { @@ -156,7 +155,14 @@ export async function initClient( } // Also creates and starts any crypto related stores. - await client.initRustCrypto(); + try { + await client.initRustCrypto(); + } catch (err) { + logger.warn( + err, + "Make sure to clear client stores before initializing the rust crypto.", + ); + } client.setGlobalErrorOnUnknownDevices(false); // Once startClient is called, syncs are run asynchronously.