1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

chore(feature-flag) [PM-22604] Remove 2FA persistence feature flag

* Removed flag.

* Fixed tests to no longer reference flag.

* Fixed test.

* Removed duplicate test class.

* Moved files into folders for yubikey and authenticator

* Removed TwoFactorAuthEmailComponentService since it is no longer needed

* Removed export

* Fixed export
This commit is contained in:
Todd Martin
2025-07-10 15:00:49 -04:00
committed by GitHub
parent 318040233c
commit c5be837b51
21 changed files with 9 additions and 581 deletions

View File

@@ -1,112 +0,0 @@
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
jest.mock("../popup/utils/auth-popout-window", () => {
const originalModule = jest.requireActual("../popup/utils/auth-popout-window");
return {
...originalModule, // avoid losing the original module's exports
openTwoFactorAuthEmailPopout: jest.fn(),
};
});
jest.mock("../../platform/browser/browser-popup-utils", () => ({
inPopup: jest.fn(),
}));
// FIXME (PM-22628): Popup imports are forbidden in background
// eslint-disable-next-line no-restricted-imports
import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window";
import BrowserPopupUtils from "../../platform/browser/browser-popup-utils";
import { ExtensionTwoFactorAuthEmailComponentService } from "./extension-two-factor-auth-email-component.service";
describe("ExtensionTwoFactorAuthEmailComponentService", () => {
let extensionTwoFactorAuthEmailComponentService: 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);
// Act
await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa();
// Assert
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "warning" },
content: { key: "popup2faCloseMessage" },
type: "warning",
});
expect(openTwoFactorAuthEmailPopout).toHaveBeenCalled();
});
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);
// Act
await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa();
// Assert
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "warning" },
content: { key: "popup2faCloseMessage" },
type: "warning",
});
expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled();
});
it("should not open a popout if not in the popup", async () => {
// Arrange
configService.getFeatureFlag.mockResolvedValue(false);
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false);
// Act
await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa();
// Assert
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

@@ -1,49 +0,0 @@
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";
// FIXME (PM-22628): Popup imports are forbidden in background
// eslint-disable-next-line no-restricted-imports
import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window";
import BrowserPopupUtils from "../../platform/browser/browser-popup-utils";
// TODO: popup state persistence should eventually remove the need for this service
export class ExtensionTwoFactorAuthEmailComponentService
extends DefaultTwoFactorAuthEmailComponentService
implements TwoFactorAuthEmailComponentService
{
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" },
content: { key: "popup2faCloseMessage" },
type: "warning",
});
if (confirmed) {
await openTwoFactorAuthEmailPopout();
this.window.close();
}
}
}
}

View File

@@ -24,7 +24,6 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.
import {
LoginComponentService,
TwoFactorAuthComponentService,
TwoFactorAuthEmailComponentService,
TwoFactorAuthDuoComponentService,
TwoFactorAuthWebAuthnComponentService,
SsoComponentService,
@@ -147,7 +146,6 @@ import { ExtensionSsoComponentService } from "../../auth/popup/login/extension-s
import { ExtensionLogoutService } from "../../auth/popup/logout/extension-logout.service";
import { ExtensionTwoFactorAuthComponentService } from "../../auth/services/extension-two-factor-auth-component.service";
import { ExtensionTwoFactorAuthDuoComponentService } from "../../auth/services/extension-two-factor-auth-duo-component.service";
import { ExtensionTwoFactorAuthEmailComponentService } from "../../auth/services/extension-two-factor-auth-email-component.service";
import { ExtensionTwoFactorAuthWebAuthnComponentService } from "../../auth/services/extension-two-factor-auth-webauthn-component.service";
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
import AutofillService from "../../autofill/services/autofill.service";
@@ -560,11 +558,6 @@ const safeProviders: SafeProvider[] = [
useClass: ExtensionTwoFactorAuthComponentService,
deps: [WINDOW],
}),
safeProvider({
provide: TwoFactorAuthEmailComponentService,
useClass: ExtensionTwoFactorAuthEmailComponentService,
deps: [DialogService, WINDOW, ConfigService],
}),
safeProvider({
provide: TwoFactorAuthWebAuthnComponentService,
useClass: ExtensionTwoFactorAuthWebAuthnComponentService,

View File

@@ -24,14 +24,12 @@ import {
DefaultRegistrationFinishService,
DefaultSetPasswordJitService,
DefaultTwoFactorAuthComponentService,
DefaultTwoFactorAuthEmailComponentService,
DefaultTwoFactorAuthWebAuthnComponentService,
LoginComponentService,
LoginDecryptionOptionsService,
RegistrationFinishService as RegistrationFinishServiceAbstraction,
SetPasswordJitService,
TwoFactorAuthComponentService,
TwoFactorAuthEmailComponentService,
TwoFactorAuthWebAuthnComponentService,
} from "@bitwarden/auth/angular";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@@ -1471,11 +1469,6 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultTwoFactorAuthWebAuthnComponentService,
deps: [],
}),
safeProvider({
provide: TwoFactorAuthEmailComponentService,
useClass: DefaultTwoFactorAuthEmailComponentService,
deps: [],
}),
safeProvider({
provide: ViewCacheService,
useExisting: NoopViewCacheService,

View File

@@ -1,3 +1,2 @@
export * from "./two-factor-auth-email";
export * from "./two-factor-auth-duo";
export * from "./two-factor-auth-webauthn";

View File

@@ -1,6 +0,0 @@
import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service";
export class DefaultTwoFactorAuthEmailComponentService
implements TwoFactorAuthEmailComponentService {
// no default implementation
}

View File

@@ -1,2 +0,0 @@
export * from "./default-two-factor-auth-email-component.service";
export * from "./two-factor-auth-email-component.service";

View File

@@ -1,165 +0,0 @@
import { TestBed } from "@angular/core/testing";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
TwoFactorAuthEmailComponentCache,
TwoFactorAuthEmailComponentCacheService,
} from "./two-factor-auth-email-component-cache.service";
describe("TwoFactorAuthEmailCache", () => {
describe("fromJSON", () => {
it("returns null when input is null", () => {
const result = TwoFactorAuthEmailComponentCache.fromJSON(null as any);
expect(result).toBeNull();
});
it("creates a TwoFactorAuthEmailCache instance from valid JSON", () => {
const jsonData = { emailSent: true };
const result = TwoFactorAuthEmailComponentCache.fromJSON(jsonData);
expect(result).not.toBeNull();
expect(result).toBeInstanceOf(TwoFactorAuthEmailComponentCache);
expect(result?.emailSent).toBe(true);
});
});
});
describe("TwoFactorAuthEmailComponentCacheService", () => {
let service: TwoFactorAuthEmailComponentCacheService;
let mockViewCacheService: MockProxy<ViewCacheService>;
let mockConfigService: MockProxy<ConfigService>;
let cacheData: BehaviorSubject<TwoFactorAuthEmailComponentCache | null>;
let mockSignal: any;
beforeEach(() => {
mockViewCacheService = mock<ViewCacheService>();
mockConfigService = mock<ConfigService>();
cacheData = new BehaviorSubject<TwoFactorAuthEmailComponentCache | null>(null);
mockSignal = jest.fn(() => cacheData.getValue());
mockSignal.set = jest.fn((value: TwoFactorAuthEmailComponentCache | null) =>
cacheData.next(value),
);
mockViewCacheService.signal.mockReturnValue(mockSignal);
TestBed.configureTestingModule({
providers: [
TwoFactorAuthEmailComponentCacheService,
{ provide: ViewCacheService, useValue: mockViewCacheService },
{ provide: ConfigService, useValue: mockConfigService },
],
});
service = TestBed.inject(TwoFactorAuthEmailComponentCacheService);
});
it("creates the service", () => {
expect(service).toBeTruthy();
});
describe("init", () => {
it("sets featureEnabled to true when flag is enabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ emailSent: true });
expect(mockSignal.set).toHaveBeenCalled();
});
it("sets featureEnabled to false when flag is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ emailSent: true });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("cacheData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("caches email sent state when feature is enabled", () => {
service.cacheData({ emailSent: true });
expect(mockSignal.set).toHaveBeenCalledWith({
emailSent: true,
});
});
it("does not cache data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.cacheData({ emailSent: true });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("clearCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("clears cached data when feature is enabled", () => {
service.clearCachedData();
expect(mockSignal.set).toHaveBeenCalledWith(null);
});
it("does not clear cached data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.clearCachedData();
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("getCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("returns cached data when feature is enabled", () => {
const testData = new TwoFactorAuthEmailComponentCache();
testData.emailSent = true;
cacheData.next(testData);
const result = service.getCachedData();
expect(result).toEqual(testData);
expect(mockSignal).toHaveBeenCalled();
});
it("returns null when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
const result = service.getCachedData();
expect(result).toBeNull();
expect(mockSignal).not.toHaveBeenCalled();
});
});
});

View File

@@ -3,7 +3,6 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
@@ -61,87 +60,26 @@ describe("TwoFactorAuthEmailComponentCacheService", () => {
expect(service).toBeTruthy();
});
describe("init", () => {
it("sets featureEnabled to true when flag is enabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ emailSent: true });
expect(mockSignal.set).toHaveBeenCalled();
});
it("sets featureEnabled to false when flag is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ emailSent: true });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("cacheData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("caches email sent state when feature is enabled", () => {
it("caches email sent state", () => {
service.cacheData({ emailSent: true });
expect(mockSignal.set).toHaveBeenCalledWith({
emailSent: true,
});
});
it("does not cache data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.cacheData({ emailSent: true });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("clearCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("clears cached data when feature is enabled", () => {
it("clears cached data", () => {
service.clearCachedData();
expect(mockSignal.set).toHaveBeenCalledWith(null);
});
it("does not clear cached data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.clearCachedData();
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("getCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("returns cached data when feature is enabled", () => {
it("returns cached data", () => {
const testData = new TwoFactorAuthEmailComponentCache();
testData.emailSent = true;
cacheData.next(testData);
@@ -151,15 +89,5 @@ describe("TwoFactorAuthEmailComponentCacheService", () => {
expect(result).toEqual(testData);
expect(mockSignal).toHaveBeenCalled();
});
it("returns null when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
const result = service.getCachedData();
expect(result).toBeNull();
expect(mockSignal).not.toHaveBeenCalled();
});
});
});

View File

@@ -2,8 +2,6 @@ import { inject, Injectable, WritableSignal } from "@angular/core";
import { Jsonify } from "type-fest";
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
/**
* The key for the email two factor auth component cache.
@@ -34,10 +32,6 @@ export class TwoFactorAuthEmailComponentCache {
@Injectable()
export class TwoFactorAuthEmailComponentCacheService {
private viewCacheService: ViewCacheService = inject(ViewCacheService);
private configService: ConfigService = inject(ConfigService);
/** True when the feature flag is enabled */
private featureEnabled: boolean = false;
/**
* Signal for the cached email state.
@@ -49,23 +43,10 @@ export class TwoFactorAuthEmailComponentCacheService {
deserializer: TwoFactorAuthEmailComponentCache.fromJSON,
});
/**
* Must be called once before interacting with the cached data.
*/
async init() {
this.featureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
}
/**
* Cache the email sent state.
*/
cacheData(data: { emailSent: boolean }): void {
if (!this.featureEnabled) {
return;
}
this.emailCache.set({
emailSent: data.emailSent,
} as TwoFactorAuthEmailComponentCache);
@@ -75,10 +56,6 @@ export class TwoFactorAuthEmailComponentCacheService {
* Clear the cached email data.
*/
clearCachedData(): void {
if (!this.featureEnabled) {
return;
}
this.emailCache.set(null);
}
@@ -86,10 +63,6 @@ export class TwoFactorAuthEmailComponentCacheService {
* Get whether the email has been sent.
*/
getCachedData(): TwoFactorAuthEmailComponentCache | null {
if (!this.featureEnabled) {
return null;
}
return this.emailCache();
}
}

View File

@@ -1,10 +0,0 @@
/**
* A service that manages all cross client functionality for the email 2FA component.
*/
export abstract class TwoFactorAuthEmailComponentService {
/**
* Optionally shows a warning to the user that they might need to popout the
* window to complete email 2FA.
*/
abstract openPopoutIfApprovedForEmail2fa?(): Promise<void>;
}

View File

@@ -25,7 +25,6 @@ import {
} from "@bitwarden/components";
import { TwoFactorAuthEmailComponentCacheService } from "./two-factor-auth-email-component-cache.service";
import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service";
@Component({
selector: "app-two-factor-auth-email",
@@ -66,14 +65,10 @@ export class TwoFactorAuthEmailComponent implements OnInit {
protected apiService: ApiService,
protected appIdService: AppIdService,
private toastService: ToastService,
private twoFactorAuthEmailComponentService: TwoFactorAuthEmailComponentService,
private cacheService: TwoFactorAuthEmailComponentCacheService,
) {}
async ngOnInit(): Promise<void> {
await this.twoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa?.();
await this.cacheService.init();
// Check if email was already sent
const cachedData = this.cacheService.getCachedData();
if (cachedData?.emailSent) {

View File

@@ -4,8 +4,6 @@ import { BehaviorSubject } from "rxjs";
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
TwoFactorAuthComponentCache,
@@ -40,13 +38,11 @@ describe("TwoFactorAuthCache", () => {
describe("TwoFactorAuthComponentCacheService", () => {
let service: TwoFactorAuthComponentCacheService;
let mockViewCacheService: MockProxy<ViewCacheService>;
let mockConfigService: MockProxy<ConfigService>;
let cacheData: BehaviorSubject<TwoFactorAuthComponentCache | null>;
let mockSignal: any;
beforeEach(() => {
mockViewCacheService = mock<ViewCacheService>();
mockConfigService = mock<ConfigService>();
cacheData = new BehaviorSubject<TwoFactorAuthComponentCache | null>(null);
mockSignal = jest.fn(() => cacheData.getValue());
mockSignal.set = jest.fn((value: TwoFactorAuthComponentCache | null) => cacheData.next(value));
@@ -56,7 +52,6 @@ describe("TwoFactorAuthComponentCacheService", () => {
providers: [
TwoFactorAuthComponentCacheService,
{ provide: ViewCacheService, useValue: mockViewCacheService },
{ provide: ConfigService, useValue: mockConfigService },
],
});
@@ -67,41 +62,8 @@ describe("TwoFactorAuthComponentCacheService", () => {
expect(service).toBeTruthy();
});
describe("init", () => {
it("sets featureEnabled to true when flag is enabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ token: "123456" });
expect(mockSignal.set).toHaveBeenCalled();
});
it("sets featureEnabled to false when flag is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
service.cacheData({ token: "123456" });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("cacheData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("caches complete data when feature is enabled", () => {
it("caches complete data", () => {
const testData: TwoFactorAuthComponentData = {
token: "123456",
remember: true,
@@ -117,7 +79,7 @@ describe("TwoFactorAuthComponentCacheService", () => {
});
});
it("caches partial data when feature is enabled", () => {
it("caches partial data", () => {
service.cacheData({ token: "123456" });
expect(mockSignal.set).toHaveBeenCalledWith({
@@ -126,46 +88,18 @@ describe("TwoFactorAuthComponentCacheService", () => {
selectedProviderType: undefined,
});
});
it("does not cache data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.cacheData({ token: "123456" });
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("clearCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("clears cached data when feature is enabled", () => {
it("clears cached data", () => {
service.clearCachedData();
expect(mockSignal.set).toHaveBeenCalledWith(null);
});
it("does not clear cached data when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
service.clearCachedData();
expect(mockSignal.set).not.toHaveBeenCalled();
});
});
describe("getCachedData", () => {
beforeEach(async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
await service.init();
});
it("returns cached data when feature is enabled", () => {
it("returns cached data", () => {
const testData = new TwoFactorAuthComponentCache();
testData.token = "123456";
testData.remember = true;
@@ -177,15 +111,5 @@ describe("TwoFactorAuthComponentCacheService", () => {
expect(result).toEqual(testData);
expect(mockSignal).toHaveBeenCalled();
});
it("returns null when feature is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
await service.init();
const result = service.getCachedData();
expect(result).toBeNull();
expect(mockSignal).not.toHaveBeenCalled();
});
});
});

View File

@@ -3,8 +3,6 @@ import { Jsonify } from "type-fest";
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
const TWO_FACTOR_AUTH_COMPONENT_CACHE_KEY = "two-factor-auth-component-cache";
@@ -40,10 +38,6 @@ export interface TwoFactorAuthComponentData {
@Injectable()
export class TwoFactorAuthComponentCacheService {
private viewCacheService: ViewCacheService = inject(ViewCacheService);
private configService: ConfigService = inject(ConfigService);
/** True when the `PM9115_TwoFactorExtensionDataPersistence` flag is enabled */
private featureEnabled: boolean = false;
/**
* Signal for the cached TwoFactorAuthData.
@@ -57,23 +51,10 @@ export class TwoFactorAuthComponentCacheService {
constructor() {}
/**
* Must be called once before interacting with the cached data.
*/
async init() {
this.featureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM9115_TwoFactorExtensionDataPersistence,
);
}
/**
* Update the cache with the new TwoFactorAuthData.
*/
cacheData(data: TwoFactorAuthComponentData): void {
if (!this.featureEnabled) {
return;
}
this.twoFactorAuthComponentCache.set({
token: data.token,
remember: data.remember,
@@ -85,10 +66,6 @@ export class TwoFactorAuthComponentCacheService {
* Clears the cached TwoFactorAuthData.
*/
clearCachedData(): void {
if (!this.featureEnabled) {
return;
}
this.twoFactorAuthComponentCache.set(null);
}
@@ -96,10 +73,6 @@ export class TwoFactorAuthComponentCacheService {
* Returns the cached TwoFactorAuthData (when available).
*/
getCachedData(): TwoFactorAuthComponentCache | null {
if (!this.featureEnabled) {
return null;
}
return this.twoFactorAuthComponentCache();
}
}

View File

@@ -121,7 +121,6 @@ describe("TwoFactorAuthComponent", () => {
mockTwoFactorAuthCompCacheService = mock<TwoFactorAuthComponentCacheService>();
mockTwoFactorAuthCompCacheService.getCachedData.mockReturnValue(null);
mockTwoFactorAuthCompCacheService.init.mockResolvedValue();
mockUserDecryptionOpts = {
noMasterPassword: new UserDecryptionOptions({

View File

@@ -60,11 +60,11 @@ import {
TwoFactorAuthDuoIcon,
} from "../icons/two-factor-auth";
import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component";
import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator/two-factor-auth-authenticator.component";
import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component";
import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component";
import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component";
import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component";
import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey/two-factor-auth-yubikey.component";
import {
TwoFactorAuthComponentCacheService,
TwoFactorAuthComponentData,
@@ -180,9 +180,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
this.listenForAuthnSessionTimeout();
// Initialize the cache
await this.twoFactorAuthComponentCacheService.init();
// Load cached form data if available
let loadedCachedProviderType = false;
const cachedData = this.twoFactorAuthComponentCacheService.getCachedData();

View File

@@ -17,7 +17,6 @@ export enum FeatureFlag {
/* Auth */
PM16117_SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor",
PM16117_ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor",
PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence",
PM14938_BrowserExtensionLoginApproval = "pm-14938-browser-extension-login-approvals",
/* Autofill */
@@ -107,7 +106,6 @@ export const DefaultFeatureFlagValue = {
/* Auth */
[FeatureFlag.PM16117_SetInitialPasswordRefactor]: FALSE,
[FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE,
[FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE,
[FeatureFlag.PM14938_BrowserExtensionLoginApproval]: FALSE,
/* Billing */