Revert deletion of hydrateSession (#29703)

* Revert deletion of hydrateSession

* remove line break to make prettier happy :-)

* add tests for hydrateSession on soft logout

* fix coding style

---------

Co-authored-by: Florian Duros <florianduros@element.io>
This commit is contained in:
Julien CLEMENT
2025-04-11 10:40:00 +02:00
committed by GitHub
parent d4f25e8e13
commit d594441b53
3 changed files with 78 additions and 8 deletions

View File

@@ -701,6 +701,43 @@ export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<Matr
return doSetLoggedIn(credentials, true, true);
}
/**
* Hydrates an existing session by using the credentials provided. This will
* not clear any local storage, unlike setLoggedIn().
*
* Stops the existing Matrix client (without clearing its data) and starts a
* new one in its place. This additionally starts all other react-sdk services
* which use the new Matrix client.
*
* If the credentials belong to a different user from the session already stored,
* the old session will be cleared automatically.
*
* @param {IMatrixClientCreds} credentials The credentials to use
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
export async function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
const oldUserId = MatrixClientPeg.safeGet().getUserId();
const oldDeviceId = MatrixClientPeg.safeGet().getDeviceId();
stopMatrixClient(); // unsets MatrixClientPeg.get()
localStorage.removeItem("mx_soft_logout");
_isLoggingOut = false;
const overwrite = credentials.userId !== oldUserId || credentials.deviceId !== oldDeviceId;
if (overwrite) {
logger.warn("Clearing all data: Old session belongs to a different user/session");
}
if (!credentials.pickleKey && credentials.deviceId !== undefined) {
logger.info("Lifecycle#hydrateSession: Pickle key not provided - trying to get one");
credentials.pickleKey =
(await PlatformPeg.get()?.getPickleKey(credentials.userId, credentials.deviceId)) ?? undefined;
}
return doSetLoggedIn(credentials, overwrite, false);
}
/**
* When we have a authenticated via OIDC-native flow and have a refresh token
* try to create a token refresher.

View File

@@ -168,7 +168,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
return;
}
Lifecycle.setLoggedIn(credentials).catch((e) => {
Lifecycle.hydrateSession(credentials).catch((e) => {
logger.error(e);
this.setState({ busy: false, errorText: _t("auth|failed_soft_logout_auth") });
});
@@ -204,7 +204,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
return false;
}
return Lifecycle.setLoggedIn(credentials)
return Lifecycle.hydrateSession(credentials)
.then(() => {
if (this.props.onTokenLoginCompleted) {
this.props.onTokenLoginCompleted();

View File

@@ -38,6 +38,12 @@ const webCrypto = new Crypto();
const windowCrypto = window.crypto;
describe("Lifecycle", () => {
const homeserverUrl = "https://domain";
const identityServerUrl = "https://is.org";
const userId = "@alice:domain";
const deviceId = "abc123";
const accessToken = "test-access-token";
let mockPlatform: MockedObject<BasePlatform>;
const realLocalStorage = global.localStorage;
@@ -53,7 +59,7 @@ describe("Lifecycle", () => {
removeAllListeners: jest.fn(),
clearStores: jest.fn(),
getAccountData: jest.fn(),
getDeviceId: jest.fn(),
getDeviceId: jest.fn().mockReturnValue(deviceId),
isVersionSupported: jest.fn().mockResolvedValue(true),
getCrypto: jest.fn(),
getClientWellKnown: jest.fn(),
@@ -156,11 +162,6 @@ describe("Lifecycle", () => {
});
};
const homeserverUrl = "https://server.org";
const identityServerUrl = "https://is.org";
const userId = "@alice:server.org";
const deviceId = "abc123";
const accessToken = "test-access-token";
const localStorageSession = {
mx_hs_url: homeserverUrl,
mx_is_url: identityServerUrl,
@@ -605,6 +606,38 @@ describe("Lifecycle", () => {
expect(MatrixClientPeg.start).toHaveBeenCalled();
});
describe("after a soft-logout", () => {
beforeEach(async () => {
await setLoggedIn(credentials);
localStorage.setItem("mx_soft_logout", "true");
});
it("should not clear the storage if device is the same", async () => {
await Lifecycle.hydrateSession(credentials);
expect(localStorage.removeItem).toHaveBeenCalledWith("mx_soft_logout");
expect(mockClient.getUserId).toHaveReturnedWith(userId);
expect(mockClient.getDeviceId).toHaveReturnedWith(deviceId);
expect(mockClient.clearStores).toHaveBeenCalledTimes(1);
});
it("should clear the storage if device is not the same", async () => {
const fakeCredentials = {
homeserverUrl,
identityServerUrl,
userId: "@bob:domain",
deviceId,
accessToken,
};
await Lifecycle.hydrateSession(fakeCredentials);
expect(localStorage.removeItem).toHaveBeenCalledWith("mx_soft_logout");
expect(mockClient.getUserId).toHaveReturnedWith(userId);
expect(mockClient.getDeviceId).toHaveReturnedWith(deviceId);
expect(mockClient.clearStores).toHaveBeenCalledTimes(2);
});
});
describe("without a pickle key", () => {
beforeEach(() => {
jest.spyOn(mockPlatform, "createPickleKey").mockResolvedValue(null);