diff --git a/.gitignore b/.gitignore index b8db61b24..54d944eda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea # build artifacts *.tsbuildinfo diff --git a/src/UserManager.test.ts b/src/UserManager.test.ts index a079a7c31..73d49c0de 100644 --- a/src/UserManager.test.ts +++ b/src/UserManager.test.ts @@ -2,7 +2,13 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. import { once } from "events"; -import { RedirectNavigator, type PopupWindow, PopupNavigator, IFrameNavigator } from "./navigators"; +import { + RedirectNavigator, + type PopupWindow, + PopupNavigator, + IFrameNavigator, + type NavigateResponse, +} from "./navigators"; import type { SigninResponse } from "./SigninResponse"; import type { SignoutResponse } from "./SignoutResponse"; import { UserManager, type SigninPopupArgs, type SigninRedirectArgs, type SigninSilentArgs, type SignoutSilentArgs } from "./UserManager"; @@ -33,6 +39,7 @@ describe("UserManager", () => { userStore: userStoreMock, metadata: { authorization_endpoint: "http://sts/oidc/authorize", + end_session_endpoint: "http://sts/oidc/logout", token_endpoint: "http://sts/oidc/token", revocation_endpoint: "http://sts/oidc/revoke", }, @@ -838,6 +845,53 @@ describe("UserManager", () => { }); }); + describe("signoutRedirect", () => { + it("should not unload user to avoid race condition between actual signout and signout event handlers", async () => { + // arrange + const navigateMock = jest.fn().mockReturnValue(Promise.resolve({ + url: "http://localhost:8080", + } as NavigateResponse)); + jest.spyOn(subject["_redirectNavigator"], "prepare").mockReturnValue(Promise.resolve({ + navigate: navigateMock, + close: () => {}, + })); + const user = new User({ + access_token: "access_token", + token_type: "token_type", + profile: {} as UserProfile, + }); + await subject.storeUser(user); + + // act + await subject.signoutRedirect(); + + // assert + expect(navigateMock).toHaveBeenCalledTimes(1); + const storageString = await subject.settings.userStore.get(subject["_userStoreKey"]); + expect(storageString).not.toBeNull(); + }); + }); + + describe("signoutRedirectCallback", () => { + it("should unload user", async () => { + // arrange + const user = new User({ + access_token: "access_token", + token_type: "token_type", + profile: {} as UserProfile, + }); + await subject.storeUser(user); + + expect(await subject.settings.userStore.get(subject["_userStoreKey"])).not.toBeNull(); + + // act + await subject.signoutRedirectCallback(); + + // assert + expect(await subject.settings.userStore.get(subject["_userStoreKey"])).toBeNull(); + }); + }); + describe("storeUser", () => { it("should add user to store", async () => { // arrange diff --git a/src/UserManager.ts b/src/UserManager.ts index 81af6738c..204b73c3c 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -620,9 +620,6 @@ export class UserManager { args.id_token_hint = id_token; } - await this.removeUser(); - logger.debug("user removed, creating signout request"); - const signoutRequest = await this._client.createSignoutRequest(args); logger.debug("got signout request"); @@ -638,11 +635,15 @@ export class UserManager { throw err; } } + protected async _signoutEnd(url: string): Promise { const logger = this._logger.create("_signoutEnd"); const signoutResponse = await this._client.processSignoutResponse(url); logger.debug("got signout response"); + await this.removeUser(); + logger.debug("user removed"); + return signoutResponse; }