1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-06 02:23:44 +00:00

[PM-5537] Migrate Biometric Prompts (#7771)

* Fix nextMock arguments

* Add state for biometric prompts

* Use biometric state for prompts

* Migrate biometric prompt data

* wire up biometric state to logouts

* Add migrator to migrate list

* Remove usages of prompt automatically

Explicitly list non-nulled state as intentional

* `npm run prettier` 🤖

* Fix web lock component
This commit is contained in:
Matt Gibson
2024-02-23 09:21:18 -05:00
committed by GitHub
parent 19a373d87e
commit 9775e77079
32 changed files with 549 additions and 180 deletions

View File

@@ -21,12 +21,11 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogService } from "@bitwarden/components";
import { ElectronStateService } from "../platform/services/electron-state.service.abstraction";
import { LockComponent } from "./lock.component";
// ipc mock global
@@ -43,14 +42,15 @@ const isWindowVisibleMock = jest.fn();
describe("LockComponent", () => {
let component: LockComponent;
let fixture: ComponentFixture<LockComponent>;
let stateServiceMock: MockProxy<ElectronStateService>;
let stateServiceMock: MockProxy<StateService>;
const biometricStateService = mock<BiometricStateService>();
let messagingServiceMock: MockProxy<MessagingService>;
let broadcasterServiceMock: MockProxy<BroadcasterService>;
let platformUtilsServiceMock: MockProxy<PlatformUtilsService>;
let activatedRouteMock: MockProxy<ActivatedRoute>;
beforeEach(() => {
stateServiceMock = mock<ElectronStateService>();
beforeEach(async () => {
stateServiceMock = mock<StateService>();
stateServiceMock.activeAccount$ = of(null);
messagingServiceMock = mock<MessagingService>();
@@ -60,9 +60,11 @@ describe("LockComponent", () => {
activatedRouteMock = mock<ActivatedRoute>();
activatedRouteMock.queryParams = mock<ActivatedRoute["queryParams"]>();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
TestBed.configureTestingModule({
biometricStateService.dismissedRequirePasswordOnStartCallout$ = of(false);
biometricStateService.promptAutomatically$ = of(false);
biometricStateService.promptCancelled$ = of(false);
await TestBed.configureTestingModule({
declarations: [LockComponent, I18nPipe],
providers: [
{
@@ -94,7 +96,7 @@ describe("LockComponent", () => {
useValue: mock<EnvironmentService>(),
},
{
provide: ElectronStateService,
provide: StateService,
useValue: stateServiceMock,
},
{
@@ -143,18 +145,19 @@ describe("LockComponent", () => {
},
{
provide: BiometricStateService,
useValue: mock<BiometricStateService>(),
useValue: biometricStateService,
},
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LockComponent);
component = fixture.componentInstance;
fixture.detectChanges();
jest.clearAllMocks();
});
afterEach(() => {
jest.resetAllMocks();
});
describe("ngOnInit", () => {
@@ -164,15 +167,15 @@ describe("LockComponent", () => {
expect(superNgOnInitSpy).toHaveBeenCalledTimes(1);
});
it('should set "autoPromptBiometric" to true if "stateService.getDisableAutoBiometricsPrompt()" resolves to false', async () => {
stateServiceMock.getDisableAutoBiometricsPrompt.mockResolvedValue(false);
it('should set "autoPromptBiometric" to true if "biometricState.promptAutomatically$" resolves to true', async () => {
biometricStateService.promptAutomatically$ = of(true);
await component.ngOnInit();
expect(component["autoPromptBiometric"]).toBe(true);
});
it('should set "autoPromptBiometric" to false if "stateService.getDisableAutoBiometricsPrompt()" resolves to true', async () => {
stateServiceMock.getDisableAutoBiometricsPrompt.mockResolvedValue(true);
it('should set "autoPromptBiometric" to false if "biometricState.promptAutomatically$" resolves to false', async () => {
biometricStateService.promptAutomatically$ = of(false);
await component.ngOnInit();
expect(component["autoPromptBiometric"]).toBe(false);

View File

@@ -1,6 +1,6 @@
import { Component, NgZone } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { switchMap } from "rxjs";
import { firstValueFrom, switchMap } from "rxjs";
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
@@ -19,12 +19,11 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogService } from "@bitwarden/components";
import { ElectronStateService } from "../platform/services/electron-state.service.abstraction";
const BroadcasterSubscriptionId = "LockComponent";
@Component({
@@ -46,7 +45,7 @@ export class LockComponent extends BaseLockComponent {
vaultTimeoutService: VaultTimeoutService,
vaultTimeoutSettingsService: VaultTimeoutSettingsService,
environmentService: EnvironmentService,
protected override stateService: ElectronStateService,
protected override stateService: StateService,
apiService: ApiService,
private route: ActivatedRoute,
private broadcasterService: BroadcasterService,
@@ -59,7 +58,7 @@ export class LockComponent extends BaseLockComponent {
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
userVerificationService: UserVerificationService,
pinCryptoService: PinCryptoServiceAbstraction,
private biometricStateService: BiometricStateService,
biometricStateService: BiometricStateService,
) {
super(
router,
@@ -81,12 +80,15 @@ export class LockComponent extends BaseLockComponent {
deviceTrustCryptoService,
userVerificationService,
pinCryptoService,
biometricStateService,
);
}
async ngOnInit() {
await super.ngOnInit();
this.autoPromptBiometric = !(await this.stateService.getDisableAutoBiometricsPrompt());
this.autoPromptBiometric = await firstValueFrom(
this.biometricStateService.promptAutomatically$,
);
this.biometricReady = await this.canUseBiometric();
await this.displayBiometricUpdateWarning();
@@ -140,7 +142,7 @@ export class LockComponent extends BaseLockComponent {
return;
}
if (await this.stateService.getBiometricPromptCancelled()) {
if (await firstValueFrom(this.biometricStateService.promptCancelled$)) {
return;
}
@@ -162,7 +164,7 @@ export class LockComponent extends BaseLockComponent {
}
private async displayBiometricUpdateWarning(): Promise<void> {
if (await this.stateService.getDismissedBiometricRequirePasswordOnStart()) {
if (await firstValueFrom(this.biometricStateService.dismissedRequirePasswordOnStartCallout$)) {
return;
}
@@ -179,10 +181,10 @@ export class LockComponent extends BaseLockComponent {
await this.biometricStateService.setRequirePasswordOnStart(response);
if (response) {
await this.stateService.setDisableAutoBiometricsPrompt(true);
await this.biometricStateService.setPromptAutomatically(false);
}
this.supportsBiometric = await this.canUseBiometric();
await this.stateService.setDismissedBiometricRequirePasswordOnStart();
await this.biometricStateService.setDismissedRequirePasswordOnStartCallout();
}
}