diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index d7d3c02ab14..da68b89b1d0 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -141,7 +141,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { }); if (confirmed) { - await this.logoutService.logout(userId); + await this.logoutService.logout(userId, "userInitiated"); // navigate to root so redirect guard can properly route next active user or null user to correct page await this.router.navigate(["/"]); } diff --git a/apps/browser/src/auth/popup/logout/extension-logout.service.spec.ts b/apps/browser/src/auth/popup/logout/extension-logout.service.spec.ts index 7ab7742c1c3..d6ba961fac7 100644 --- a/apps/browser/src/auth/popup/logout/extension-logout.service.spec.ts +++ b/apps/browser/src/auth/popup/logout/extension-logout.service.spec.ts @@ -4,6 +4,7 @@ import { LogoutReason, LogoutService } from "@bitwarden/auth/common"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { LogService } from "@bitwarden/logging"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; @@ -13,6 +14,7 @@ describe("ExtensionLogoutService", () => { let logoutService: LogoutService; let messagingService: MockProxy; let accountSwitcherService: MockProxy; + let logService: MockProxy; let primaryUserId: UserId; let secondaryUserId: UserId; @@ -25,7 +27,12 @@ describe("ExtensionLogoutService", () => { messagingService = mock(); accountSwitcherService = mock(); - logoutService = new ExtensionLogoutService(messagingService, accountSwitcherService); + + logoutService = new ExtensionLogoutService( + messagingService, + accountSwitcherService, + logService, + ); }); it("instantiates", () => { @@ -38,16 +45,7 @@ describe("ExtensionLogoutService", () => { accountSwitcherService.listenForSwitchAccountFinish.mockResolvedValue(null); }); - it("sends logout message without a logout reason when not provided", async () => { - const result = await logoutService.logout(primaryUserId); - - expect(accountSwitcherService.listenForSwitchAccountFinish).toHaveBeenCalledTimes(1); - expect(messagingService.send).toHaveBeenCalledWith("logout", { userId: primaryUserId }); - - expect(result).toBeUndefined(); - }); - - it("sends logout message with a logout reason when provided", async () => { + it("sends logout message with a logout reason", async () => { const result = await logoutService.logout(primaryUserId, logoutReason); expect(accountSwitcherService.listenForSwitchAccountFinish).toHaveBeenCalledTimes(1); @@ -69,20 +67,7 @@ describe("ExtensionLogoutService", () => { }); }); - it("sends logout message without a logout reason when not provided and returns the new active user", async () => { - const result = await logoutService.logout(primaryUserId); - - expect(accountSwitcherService.listenForSwitchAccountFinish).toHaveBeenCalledTimes(1); - - expect(messagingService.send).toHaveBeenCalledWith("logout", { userId: primaryUserId }); - - expect(result).toEqual({ - userId: secondaryUserId, - authenticationStatus: newActiveUserAuthenticationStatus, - }); - }); - - it("sends logout message with a logout reason when provided and returns the new active user", async () => { + it("sends logout message with a logout reason and returns the new active user", async () => { const result = await logoutService.logout(primaryUserId, logoutReason); expect(accountSwitcherService.listenForSwitchAccountFinish).toHaveBeenCalledTimes(1); diff --git a/apps/browser/src/auth/popup/logout/extension-logout.service.ts b/apps/browser/src/auth/popup/logout/extension-logout.service.ts index c43c18f157a..e9989a56669 100644 --- a/apps/browser/src/auth/popup/logout/extension-logout.service.ts +++ b/apps/browser/src/auth/popup/logout/extension-logout.service.ts @@ -6,6 +6,7 @@ import { } from "@bitwarden/auth/common"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { LogService } from "@bitwarden/logging"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; @@ -13,14 +14,17 @@ export class ExtensionLogoutService extends DefaultLogoutService implements Logo constructor( protected messagingService: MessagingService, private accountSwitcherService: AccountSwitcherService, + protected logService: LogService, ) { - super(messagingService); + super(messagingService, logService); } override async logout( userId: UserId, - logoutReason?: LogoutReason, + logoutReason: LogoutReason, ): Promise { + this.logService.info("Logging out user %s for reason: %s", userId, logoutReason); + // logout can result in an account switch to the next up user const accountSwitchFinishPromise = this.accountSwitcherService.listenForSwitchAccountFinish(null); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index fecc47af981..324c1cee8b8 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1256,7 +1256,7 @@ export default class MainBackground { this.authService, ); - const logoutService = new DefaultLogoutService(this.messagingService); + const logoutService = new DefaultLogoutService(this.messagingService, this.logService); this.lockService = new ExtensionLockService( this.accountService, this.biometricsService, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index eebf0a08a22..1e4921c8939 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -675,7 +675,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LogoutService, useClass: ExtensionLogoutService, - deps: [MessagingServiceAbstraction, AccountSwitcherService], + deps: [MessagingServiceAbstraction, AccountSwitcherService, LogService], }), safeProvider({ provide: CompactModeService, diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 6243ba1e538..143b33dbe77 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -596,6 +596,7 @@ export class AppComponent implements OnInit, OnDestroy { let toastOptions: ToastOptions; switch (logoutReason) { + case "invalidAccessToken": case "invalidSecurityStamp": case "sessionExpired": { toastOptions = { diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index 805fe3c0173..6e0d2b03b2c 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -271,7 +271,7 @@ export class SetInitialPasswordComponent implements OnInit { this.showSuccessToastByUserType(); - await this.logoutService.logout(this.userId); + await this.logoutService.logout(this.userId, "setInitialPassword"); // navigate to root so redirect guard can properly route next active user or null user to correct page await this.router.navigate(["/"]); } catch (e) { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ff5caff540c..9241f77d2b1 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1697,7 +1697,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LogoutService, useClass: DefaultLogoutService, - deps: [MessagingServiceAbstraction], + deps: [MessagingServiceAbstraction, LogService], }), safeProvider({ provide: DocumentLangSetter, diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 26293285008..8b91ade1f04 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -167,7 +167,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { message: this.i18nService.t("activeUserEmailNotFoundLoggingYouOut"), }); - await this.logoutService.logout(this.activeAccountId); + await this.logoutService.logout(this.activeAccountId, "missingEmailError"); // navigate to root so redirect guard can properly route next active user or null user to correct page await this.router.navigate(["/"]); } diff --git a/libs/auth/src/common/abstractions/logout.service.ts b/libs/auth/src/common/abstractions/logout.service.ts index 16ea3746df2..a8f33fae398 100644 --- a/libs/auth/src/common/abstractions/logout.service.ts +++ b/libs/auth/src/common/abstractions/logout.service.ts @@ -12,8 +12,8 @@ export abstract class LogoutService { /** * Logs out the user. * @param userId The user id. - * @param logoutReason The optional reason for logging out. + * @param logoutReason The 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; + abstract logout(userId: UserId, logoutReason: LogoutReason): Promise; } diff --git a/libs/auth/src/common/services/logout/default-logout.service.spec.ts b/libs/auth/src/common/services/logout/default-logout.service.spec.ts index 3febd841695..b978c65c56b 100644 --- a/libs/auth/src/common/services/logout/default-logout.service.spec.ts +++ b/libs/auth/src/common/services/logout/default-logout.service.spec.ts @@ -2,6 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { LogService } from "@bitwarden/logging"; import { LogoutService } from "../../abstractions"; import { LogoutReason } from "../../types"; @@ -11,10 +12,12 @@ import { DefaultLogoutService } from "./default-logout.service"; describe("DefaultLogoutService", () => { let logoutService: LogoutService; let messagingService: MockProxy; + let logService: MockProxy; beforeEach(() => { messagingService = mock(); - logoutService = new DefaultLogoutService(messagingService); + logService = mock(); + logoutService = new DefaultLogoutService(messagingService, logService); }); it("instantiates", () => { @@ -22,15 +25,7 @@ describe("DefaultLogoutService", () => { }); 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 () => { + it("sends logout message with a logout reason", async () => { const userId = "1" as UserId; const logoutReason: LogoutReason = "vaultTimeout"; await logoutService.logout(userId, logoutReason); diff --git a/libs/auth/src/common/services/logout/default-logout.service.ts b/libs/auth/src/common/services/logout/default-logout.service.ts index 4e96c7cfba1..e85caf528d2 100644 --- a/libs/auth/src/common/services/logout/default-logout.service.ts +++ b/libs/auth/src/common/services/logout/default-logout.service.ts @@ -1,14 +1,18 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { LogService } from "@bitwarden/logging"; 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 { + constructor( + protected messagingService: MessagingService, + protected logService: LogService, + ) {} + async logout(userId: UserId, logoutReason: LogoutReason): Promise { + this.logService.info("Logging out user %s for reason: %s", userId, logoutReason); this.messagingService.send("logout", { userId, logoutReason }); - return undefined; } } diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index 801a9d191f5..708e7f59e8b 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -371,7 +371,7 @@ export class LockComponent implements OnInit, OnDestroy { }); if (confirmed && this.activeAccount != null) { - await this.logoutService.logout(this.activeAccount.id); + await this.logoutService.logout(this.activeAccount.id, "userInitiated"); // navigate to root so redirect guard can properly route next active user or null user to correct page await this.router.navigate(["/"]); }