1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

feat(auth): implement view data persistence in 2FA flows

Add persistence to two-factor authentication in the extension login flow. Implements caching of form state to improve user experience when navigating between authentication steps. Includes feature flag for controlled rollout.
This commit is contained in:
Alec Rippberger
2025-04-25 10:02:54 -05:00
committed by GitHub
parent a7b69bf8ce
commit ab7016fd6b
16 changed files with 911 additions and 23 deletions

View File

@@ -1,5 +1,6 @@
import { MockProxy, mock } from "jest-mock-extended";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { DialogService } from "@bitwarden/components";
// Must mock modules before importing
@@ -26,22 +27,26 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => {
let dialogService: MockProxy<DialogService>;
let window: MockProxy<Window>;
let configService: MockProxy<ConfigService>;
beforeEach(() => {
jest.clearAllMocks();
dialogService = mock<DialogService>();
window = mock<Window>();
configService = mock<ConfigService>();
extensionTwoFactorAuthEmailComponentService = new ExtensionTwoFactorAuthEmailComponentService(
dialogService,
window,
configService,
);
});
describe("openPopoutIfApprovedForEmail2fa", () => {
it("should open a popout if the user confirms the warning to popout the extension when in the popup", async () => {
// Arrange
configService.getFeatureFlag.mockResolvedValue(false);
dialogService.openSimpleDialog.mockResolvedValue(true);
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true);
@@ -61,6 +66,7 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => {
it("should not open a popout if the user cancels the warning to popout the extension when in the popup", async () => {
// Arrange
configService.getFeatureFlag.mockResolvedValue(false);
dialogService.openSimpleDialog.mockResolvedValue(false);
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true);
@@ -80,6 +86,7 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => {
it("should not open a popout if not in the popup", async () => {
// Arrange
configService.getFeatureFlag.mockResolvedValue(false);
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false);
// Act
@@ -89,5 +96,15 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => {
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled();
});
it("does not prompt or open a popout if the feature flag is enabled", async () => {
configService.getFeatureFlag.mockResolvedValue(true);
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true);
await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa();
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled();
});
});
});

View File

@@ -2,6 +2,8 @@ import {
DefaultTwoFactorAuthEmailComponentService,
TwoFactorAuthEmailComponentService,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { DialogService } from "@bitwarden/components";
import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window";
@@ -15,11 +17,21 @@ export class ExtensionTwoFactorAuthEmailComponentService
constructor(
private dialogService: DialogService,
private window: Window,
private configService: ConfigService,
) {
super();
}
async openPopoutIfApprovedForEmail2fa(): Promise<void> {
const isTwoFactorFormPersistenceEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
if (isTwoFactorFormPersistenceEnabled) {
// If the feature flag is enabled, we don't need to prompt the user to open the popout
return;
}
if (BrowserPopupUtils.inPopup(this.window)) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "warning" },

View File

@@ -557,7 +557,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: TwoFactorAuthEmailComponentService,
useClass: ExtensionTwoFactorAuthEmailComponentService,
deps: [DialogService, WINDOW],
deps: [DialogService, WINDOW, ConfigService],
}),
safeProvider({
provide: TwoFactorAuthWebAuthnComponentService,