1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-02 17:53:41 +00:00

Added log message on every logout with a reason.

This commit is contained in:
Todd Martin
2025-11-22 19:04:44 -05:00
parent 5b9e8432d8
commit f69602850f
13 changed files with 38 additions and 49 deletions

View File

@@ -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(["/"]);
}

View File

@@ -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<MessagingService>;
let accountSwitcherService: MockProxy<AccountSwitcherService>;
let logService: MockProxy<LogService>;
let primaryUserId: UserId;
let secondaryUserId: UserId;
@@ -25,7 +27,12 @@ describe("ExtensionLogoutService", () => {
messagingService = mock<MessagingService>();
accountSwitcherService = mock<AccountSwitcherService>();
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);

View File

@@ -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<NewActiveUser | undefined> {
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);

View File

@@ -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,

View File

@@ -675,7 +675,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: LogoutService,
useClass: ExtensionLogoutService,
deps: [MessagingServiceAbstraction, AccountSwitcherService],
deps: [MessagingServiceAbstraction, AccountSwitcherService, LogService],
}),
safeProvider({
provide: CompactModeService,

View File

@@ -596,6 +596,7 @@ export class AppComponent implements OnInit, OnDestroy {
let toastOptions: ToastOptions;
switch (logoutReason) {
case "invalidAccessToken":
case "invalidSecurityStamp":
case "sessionExpired": {
toastOptions = {

View File

@@ -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) {

View File

@@ -1697,7 +1697,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: LogoutService,
useClass: DefaultLogoutService,
deps: [MessagingServiceAbstraction],
deps: [MessagingServiceAbstraction, LogService],
}),
safeProvider({
provide: DocumentLangSetter,

View File

@@ -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(["/"]);
}

View File

@@ -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<NewActiveUser | undefined>;
abstract logout(userId: UserId, logoutReason: LogoutReason): Promise<NewActiveUser | undefined>;
}

View File

@@ -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<MessagingService>;
let logService: MockProxy<LogService>;
beforeEach(() => {
messagingService = mock<MessagingService>();
logoutService = new DefaultLogoutService(messagingService);
logService = mock<LogService>();
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);

View File

@@ -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<NewActiveUser | undefined> {
constructor(
protected messagingService: MessagingService,
protected logService: LogService,
) {}
async logout(userId: UserId, logoutReason: LogoutReason): Promise<NewActiveUser | undefined> {
this.logService.info("Logging out user %s for reason: %s", userId, logoutReason);
this.messagingService.send("logout", { userId, logoutReason });
return undefined;
}
}

View File

@@ -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(["/"]);
}