Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Fixes authentication when user is registered via module API
Browse files Browse the repository at this point in the history
Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
  • Loading branch information
Mikhail Aheichyk committed Mar 1, 2023
1 parent 9e5c4e9 commit a25920f
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 4 deletions.
18 changes: 14 additions & 4 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ dis.register((payload) => {
onLoggedOut();
} else if (payload.action === Action.OverwriteLogin) {
const typed = <OverwriteLoginPayload>payload;
// is invoked without dispatching of "on_logging_in"
// noinspection JSIgnoredPromiseFromCall - we don't care if it fails
doSetLoggedIn(typed.credentials, true);
doSetLoggedIn(typed.credentials, true, false);
}
});

Expand Down Expand Up @@ -573,10 +574,14 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise<M
*
* @param {IMatrixClientCreds} credentials
* @param {Boolean} clearStorageEnabled
*
* @param {Boolean} dispatchOnLoggingIn if true then "on_logging_in" is dispatched
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnabled: boolean): Promise<MatrixClient> {
async function doSetLoggedIn(
credentials: IMatrixClientCreds,
clearStorageEnabled: boolean,
dispatchOnLoggingIn = true,
): Promise<MatrixClient> {
credentials.guest = Boolean(credentials.guest);

const softLogout = isSoftLogout();
Expand All @@ -602,7 +607,12 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable
//
// we fire it *synchronously* to make sure it fires before on_logged_in.
// (dis.dispatch uses `window.setTimeout`, which does not guarantee ordering.)
dis.dispatch({ action: "on_logging_in" }, true);
//
// can be disabled to resolve "Cannot dispatch in the middle of a dispatch."
// error when it is invoked via another dispatch that is not yet finished.
if (dispatchOnLoggingIn) {
dis.dispatch({ action: "on_logging_in" }, true);
}

if (clearStorageEnabled) {
await clearStorage();
Expand Down
18 changes: 18 additions & 0 deletions src/modules/ProxiedModuleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { getCachedRoomIDForAlias } from "../RoomAliasCache";
import { Action } from "../dispatcher/actions";
import { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload";
import { ActionPayload } from "../dispatcher/payloads";

/**
* Glue between the `ModuleApi` interface and the react-sdk. Anticipates one instance
Expand All @@ -44,6 +45,18 @@ import { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayl
export class ProxiedModuleApi implements ModuleApi {
private cachedTranslations: Optional<TranslationStringsObject>;

private overrideLoginResolve?: () => void;

public constructor() {
dispatcher.register(this.onAction);
}

private onAction = (payload: ActionPayload): void => {
if (payload.action === Action.OnLoggedIn) {
this.overrideLoginResolve?.();
}
};

/**
* All custom translations used by the associated module.
*/
Expand Down Expand Up @@ -154,6 +167,11 @@ export class ProxiedModuleApi implements ModuleApi {
},
true,
); // require to be sync to match inherited interface behaviour

// wait for login to complete
await new Promise<void>((resolve) => {
this.overrideLoginResolve = resolve;
});
}

/**
Expand Down
86 changes: 86 additions & 0 deletions test/Lifecycle-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright 2023 Mikhail Aheichyk
Copyright 2023 Nordeck IT + Consulting GmbH.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
import { logger } from "matrix-js-sdk/src/logger";

import defaultDispatcher from "../src/dispatcher/dispatcher";
import { OverwriteLoginPayload } from "../src/dispatcher/payloads/OverwriteLoginPayload";
import { Action } from "../src/dispatcher/actions";
import { setLoggedIn } from "../src/Lifecycle";
import { stubClient } from "./test-utils";
import { ActionPayload } from "../src/dispatcher/payloads";

jest.mock("../src/utils/createMatrixClient", () => ({
__esModule: true,
default: jest.fn().mockReturnValue({
clearStores: jest.fn(),
}),
}));

describe("Lifecycle", () => {
stubClient();

jest.spyOn(logger, "error").mockReturnValue(undefined);
jest.spyOn(logger, "warn").mockReturnValue(undefined);
jest.spyOn(logger, "log").mockImplementation(undefined);

it("should call 'overwrite_login' callback and receive 'on_logged_in'", async () => {
// promise to wait 'on_logged_in'
const loggedInPromise = new Promise((resolve, reject) => {
defaultDispatcher.register((payload: ActionPayload) => {
if (payload.action === Action.OnLoggedIn) {
resolve(undefined);
}
});
});

// dispatch 'overwrite_login'
const accountInfo = {} as unknown as AccountAuthInfo;
defaultDispatcher.dispatch<OverwriteLoginPayload>(
{
action: Action.OverwriteLogin,
credentials: {
...accountInfo,
guest: false,
},
},
true,
);

await expect(loggedInPromise).resolves.toBeUndefined();
});

it("should setLoggedIn", async () => {
// promise to wait 'on_logging_in'
const loggingInPromise = new Promise((resolve, reject) => {
defaultDispatcher.register((payload: ActionPayload) => {
if (payload.action === "on_logging_in") {
resolve(undefined);
}
});
});

await setLoggedIn({
accessToken: "some_token",
homeserverUrl: "https://example.com",
userId: "@some_user_id",
});

await expect(loggingInPromise).resolves.toBeUndefined();
});
});
26 changes: 26 additions & 0 deletions test/modules/ProxiedModuleApi-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ limitations under the License.
*/

import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";

import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi";
import { stubClient } from "../test-utils";
import { setLanguage } from "../../src/languageHandler";
import { ModuleRunner } from "../../src/modules/ModuleRunner";
import { registerMockModule } from "./MockModule";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
import { Action } from "../../src/dispatcher/actions";

describe("ProxiedApiModule", () => {
afterEach(() => {
Expand All @@ -44,6 +47,29 @@ describe("ProxiedApiModule", () => {
expect(api.translations).toBe(translations);
});

it("should overwriteAccountAuth", async () => {
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");

const api = new ProxiedModuleApi();
const accountInfo = {} as unknown as AccountAuthInfo;
const promise = api.overwriteAccountAuth(accountInfo);

expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
action: Action.OverwriteLogin,
credentials: {
...accountInfo,
guest: false,
},
}),
expect.anything(),
);

defaultDispatcher.fire(Action.OnLoggedIn);

await expect(promise).resolves.toBeUndefined();
});

describe("integration", () => {
it("should translate strings using translation system", async () => {
// Test setup
Expand Down
3 changes: 3 additions & 0 deletions test/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ export function createTestClient(): MatrixClient {
room_id: roomId,
});
}),
startClient: jest.fn().mockResolvedValue(undefined),
stopClient: jest.fn().mockReturnValue(undefined),
removeAllListeners: jest.fn().mockReturnValue(undefined),
} as unknown as MatrixClient;

client.reEmitter = new ReEmitter(client);
Expand Down

0 comments on commit a25920f

Please sign in to comment.