1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 18:53:29 +00:00

fix(Multi-Account-Logout: [Auth/PM-19555] Fix multi account logout on lock screens not redirecting properly (#14630)

* PM-19555 - LogoutService - build abstraction, default, and extension service and register with service modules

* PM-19555 - Lock Comp - use logoutService

* PM-19555 - LoginDecryptionOptions - Use logout service which removed need for extension-login-decryption-options.service

* PM-19555 - AccountSwitcher logic update - (1) Use logout service + redirect guard routing (2) Remove logout method from account switcher service (3) use new NewActiveUser type

* PM-19555 - Extension - Acct Switcher comp - clean up TODOs

* PM-19555 - Add TODOs for remaining tech debt

* PM-19555 - Add tests for new logout services.

* PM-19555 - Extension - LoginInitiated - show acct switcher b/c user is AuthN

* PM-19555 - Add TODO to replace LogoutCallback with LogoutService

* PM-19555 WIP

* PM-19555 - Extension App Comp - account switching to account in TDE locked state works now.

* PM-19555 - Extension App Comp - add docs

* PM-19555 - Extension App Comp - add early return

* PM-19555 - Desktop App Comp - add handling for TDE lock case to switch account logic.

* PM-19555 - Extension - Account Component - if account unlocked go to vault

* PM-19555 - Per PR feedback, clean up unnecessary nullish coalescing operator.

* PM-19555 - Extension - AppComponent - fix everHadUserKey merge issue

* PM-19555 - PR feedback - refactor switchAccount and locked message handling on browser & desktop to require user id. I audited all callsites for both to ensure this *shouldn't* error.
This commit is contained in:
Jared Snider
2025-06-13 13:22:04 -04:00
committed by GitHub
parent b6f402faa8
commit bfb0b874ed
23 changed files with 334 additions and 211 deletions

View File

@@ -6,3 +6,4 @@ export * from "./user-decryption-options.service.abstraction";
export * from "./auth-request.service.abstraction";
export * from "./login-approval-component.service.abstraction";
export * from "./login-success-handler.service";
export * from "./logout.service";

View File

@@ -0,0 +1,19 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { UserId } from "@bitwarden/common/types/guid";
import { LogoutReason } from "../types";
export interface NewActiveUser {
userId: UserId;
authenticationStatus: AuthenticationStatus;
}
export abstract class LogoutService {
/**
* Logs out the user.
* @param userId The user id.
* @param logoutReason The optional reason for logging out.
* @returns The new active user or undefined if there isn't a new active account.
*/
abstract logout(userId: UserId, logoutReason?: LogoutReason): Promise<NewActiveUser | undefined>;
}

View File

@@ -7,3 +7,4 @@ export * from "./auth-request/auth-request-api.service";
export * from "./accounts/lock.service";
export * from "./login-success-handler/default-login-success-handler.service";
export * from "./sso-redirect/sso-url.service";
export * from "./logout/default-logout.service";

View File

@@ -0,0 +1,40 @@
import { MockProxy, mock } from "jest-mock-extended";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { UserId } from "@bitwarden/common/types/guid";
import { LogoutService } from "../../abstractions";
import { LogoutReason } from "../../types";
import { DefaultLogoutService } from "./default-logout.service";
describe("DefaultLogoutService", () => {
let logoutService: LogoutService;
let messagingService: MockProxy<MessagingService>;
beforeEach(() => {
messagingService = mock<MessagingService>();
logoutService = new DefaultLogoutService(messagingService);
});
it("instantiates", () => {
expect(logoutService).not.toBeFalsy();
});
describe("logout", () => {
it("sends logout message without a logout reason when not provided", async () => {
const userId = "1" as UserId;
await logoutService.logout(userId);
expect(messagingService.send).toHaveBeenCalledWith("logout", { userId });
});
it("sends logout message with a logout reason when provided", async () => {
const userId = "1" as UserId;
const logoutReason: LogoutReason = "vaultTimeout";
await logoutService.logout(userId, logoutReason);
expect(messagingService.send).toHaveBeenCalledWith("logout", { userId, logoutReason });
});
});
});

View File

@@ -0,0 +1,14 @@
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { UserId } from "@bitwarden/common/types/guid";
import { LogoutService, NewActiveUser } from "../../abstractions/logout.service";
import { LogoutReason } from "../../types";
export class DefaultLogoutService implements LogoutService {
constructor(protected messagingService: MessagingService) {}
async logout(userId: UserId, logoutReason?: LogoutReason): Promise<NewActiveUser | undefined> {
this.messagingService.send("logout", { userId, logoutReason });
return undefined;
}
}