mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 02:33:46 +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:
@@ -1,5 +1,6 @@
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
@@ -19,6 +20,7 @@ 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";
|
||||
|
||||
@@ -59,6 +61,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
private routerService: BrowserRouterService,
|
||||
biometricStateService: BiometricStateService,
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
@@ -80,6 +83,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
deviceTrustCryptoService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
biometricStateService,
|
||||
);
|
||||
this.successRoute = "/tabs/current";
|
||||
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
||||
@@ -100,8 +104,9 @@ export class LockComponent extends BaseLockComponent {
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
const disableAutoBiometricsPrompt =
|
||||
(await this.stateService.getDisableAutoBiometricsPrompt()) ?? true;
|
||||
const disableAutoBiometricsPrompt = await firstValueFrom(
|
||||
this.biometricStateService.promptAutomatically$,
|
||||
);
|
||||
|
||||
window.setTimeout(async () => {
|
||||
document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
PinCryptoServiceAbstraction,
|
||||
PinCryptoService,
|
||||
@@ -64,6 +66,10 @@ import {
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
||||
import {
|
||||
BiometricStateService,
|
||||
DefaultBiometricStateService,
|
||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
@@ -280,6 +286,7 @@ export default class MainBackground {
|
||||
individualVaultExportService: IndividualVaultExportServiceAbstraction;
|
||||
organizationVaultExportService: OrganizationVaultExportServiceAbstraction;
|
||||
vaultSettingsService: VaultSettingsServiceAbstraction;
|
||||
biometricStateService: BiometricStateService;
|
||||
|
||||
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
|
||||
backgroundWindow = window;
|
||||
@@ -321,7 +328,7 @@ export default class MainBackground {
|
||||
}
|
||||
};
|
||||
|
||||
const logoutCallback = async (expired: boolean, userId?: string) =>
|
||||
const logoutCallback = async (expired: boolean, userId?: UserId) =>
|
||||
await this.logout(expired, userId);
|
||||
|
||||
this.messagingService = this.popupOnlyContext
|
||||
@@ -386,6 +393,7 @@ export default class MainBackground {
|
||||
this.stateProvider,
|
||||
this.accountService,
|
||||
);
|
||||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||
|
||||
const migrationRunner = new MigrationRunner(
|
||||
this.storageService,
|
||||
@@ -1043,7 +1051,9 @@ export default class MainBackground {
|
||||
}
|
||||
}
|
||||
|
||||
async logout(expired: boolean, userId?: string) {
|
||||
async logout(expired: boolean, userId?: UserId) {
|
||||
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
await this.eventUploadService.uploadEvents(userId);
|
||||
|
||||
await Promise.all([
|
||||
@@ -1058,6 +1068,7 @@ export default class MainBackground {
|
||||
this.vaultTimeoutSettingsService.clear(userId),
|
||||
this.keyConnectorService.clear(),
|
||||
this.vaultFilterService.clear(),
|
||||
this.biometricStateService.logout(userId),
|
||||
// We intentionally do not clear the autofillSettingsService
|
||||
]);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
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 { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { SetPinComponent } from "../../auth/popup/components/set-pin.component";
|
||||
@@ -101,6 +102,7 @@ export class SettingsComponent implements OnInit {
|
||||
private userVerificationService: UserVerificationService,
|
||||
private dialogService: DialogService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
}
|
||||
@@ -176,7 +178,9 @@ export class SettingsComponent implements OnInit {
|
||||
),
|
||||
pin: pinStatus !== "DISABLED",
|
||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||
enableAutoBiometricsPrompt: !(await this.stateService.getDisableAutoBiometricsPrompt()),
|
||||
enableAutoBiometricsPrompt: await firstValueFrom(
|
||||
this.biometricStateService.promptAutomatically$,
|
||||
),
|
||||
};
|
||||
this.form.patchValue(initialValues); // Emit event to initialize `pairwise` operator
|
||||
|
||||
@@ -416,8 +420,8 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async updateAutoBiometricsPrompt() {
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(
|
||||
!this.form.value.enableAutoBiometricsPrompt,
|
||||
await this.biometricStateService.setPromptAutomatically(
|
||||
this.form.value.enableAutoBiometricsPrompt,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||
@@ -205,6 +206,7 @@ export class Main {
|
||||
derivedStateProvider: DerivedStateProvider;
|
||||
stateProvider: StateProvider;
|
||||
loginStrategyService: LoginStrategyServiceAbstraction;
|
||||
biometricStateService: BiometricStateService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -627,6 +629,7 @@ export class Main {
|
||||
this.collectionService.clear(userId as UserId),
|
||||
this.policyService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.biometricStateService.logout(userId as UserId),
|
||||
]);
|
||||
await this.stateService.clean();
|
||||
process.env.BW_SESSION = null;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { ThemeType, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -23,7 +24,6 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { SetPinComponent } from "../../auth/components/set-pin.component";
|
||||
import { flagEnabled } from "../../platform/flags";
|
||||
import { ElectronStateService } from "../../platform/services/electron-state.service.abstraction";
|
||||
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
@@ -110,7 +110,7 @@ export class SettingsComponent implements OnInit {
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
private stateService: ElectronStateService,
|
||||
private stateService: StateService,
|
||||
private messagingService: MessagingService,
|
||||
private cryptoService: CryptoService,
|
||||
private modalService: ModalService,
|
||||
@@ -242,7 +242,7 @@ export class SettingsComponent implements OnInit {
|
||||
),
|
||||
pin: this.userHasPinSet,
|
||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||
autoPromptBiometrics: !(await this.stateService.getDisableAutoBiometricsPrompt()),
|
||||
autoPromptBiometrics: await firstValueFrom(this.biometricStateService.promptAutomatically$),
|
||||
requirePasswordOnStart: await firstValueFrom(
|
||||
this.biometricStateService.requirePasswordOnStart$,
|
||||
),
|
||||
@@ -453,9 +453,9 @@ export class SettingsComponent implements OnInit {
|
||||
// Recommended settings for Windows Hello
|
||||
this.form.controls.requirePasswordOnStart.setValue(true);
|
||||
this.form.controls.autoPromptBiometrics.setValue(false);
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(true);
|
||||
await this.biometricStateService.setPromptAutomatically(false);
|
||||
await this.biometricStateService.setRequirePasswordOnStart(true);
|
||||
await this.stateService.setDismissedBiometricRequirePasswordOnStart();
|
||||
await this.biometricStateService.setDismissedRequirePasswordOnStartCallout();
|
||||
}
|
||||
await this.cryptoService.refreshAdditionalKeys();
|
||||
|
||||
@@ -475,10 +475,9 @@ export class SettingsComponent implements OnInit {
|
||||
// require password on start must be disabled if auto prompt biometrics is enabled
|
||||
this.form.controls.requirePasswordOnStart.setValue(false);
|
||||
await this.updateRequirePasswordOnStart();
|
||||
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(null);
|
||||
await this.biometricStateService.setPromptAutomatically(true);
|
||||
} else {
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(true);
|
||||
await this.biometricStateService.setPromptAutomatically(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +491,7 @@ export class SettingsComponent implements OnInit {
|
||||
} else {
|
||||
await this.biometricStateService.setRequirePasswordOnStart(false);
|
||||
}
|
||||
await this.stateService.setDismissedBiometricRequirePasswordOnStart();
|
||||
await this.biometricStateService.setDismissedRequirePasswordOnStartCallout();
|
||||
await this.cryptoService.refreshAdditionalKeys();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -147,6 +148,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private userVerificationService: UserVerificationService,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -576,6 +578,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
await this.vaultTimeoutSettingsService.clear(userBeingLoggedOut);
|
||||
await this.policyService.clear(userBeingLoggedOut);
|
||||
await this.keyConnectorService.clear();
|
||||
await this.biometricStateService.logout(userBeingLoggedOut as UserId);
|
||||
|
||||
preLogoutActiveUserId = this.activeUserId;
|
||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||
|
||||
@@ -57,7 +57,6 @@ import { ElectronRendererMessagingService } from "../../platform/services/electr
|
||||
import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service";
|
||||
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
|
||||
import { ElectronStateService } from "../../platform/services/electron-state.service";
|
||||
import { ElectronStateService as ElectronStateServiceAbstraction } from "../../platform/services/electron-state.service.abstraction";
|
||||
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
|
||||
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
|
||||
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
|
||||
@@ -140,10 +139,6 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
||||
STATE_SERVICE_USE_CACHE,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: ElectronStateServiceAbstraction,
|
||||
useExisting: StateServiceAbstraction,
|
||||
},
|
||||
{
|
||||
provide: FileDownloadService,
|
||||
useClass: DesktopFileDownloadService,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
@@ -44,7 +45,7 @@ export class Main {
|
||||
memoryStorageService: MemoryStorageService;
|
||||
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
|
||||
messagingService: ElectronMainMessagingService;
|
||||
stateService: ElectronStateService;
|
||||
stateService: StateService;
|
||||
environmentService: EnvironmentService;
|
||||
desktopCredentialStorageListener: DesktopCredentialStorageListener;
|
||||
migrationRunner: MigrationRunner;
|
||||
@@ -146,8 +147,11 @@ export class Main {
|
||||
false, // Do not use disk caching because this will get out of sync with the renderer service
|
||||
);
|
||||
|
||||
const biometricStateService = new DefaultBiometricStateService(stateProvider);
|
||||
|
||||
this.windowMain = new WindowMain(
|
||||
this.stateService,
|
||||
biometricStateService,
|
||||
this.logService,
|
||||
this.storageService,
|
||||
(arg) => this.processDeepLink(arg),
|
||||
@@ -169,8 +173,6 @@ export class Main {
|
||||
this.updaterMain,
|
||||
);
|
||||
|
||||
const biometricStateService = new DefaultBiometricStateService(stateProvider);
|
||||
|
||||
this.biometricsService = new BiometricsService(
|
||||
this.i18nService,
|
||||
this.windowMain,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { WindowState } from "@bitwarden/common/models/domain/window-state";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
|
||||
import {
|
||||
cleanUserAgent,
|
||||
@@ -36,6 +37,7 @@ export class WindowMain {
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private logService: LogService,
|
||||
private storageService: AbstractStorageService,
|
||||
private argvCallback: (argv: string[]) => void = null,
|
||||
@@ -90,11 +92,9 @@ export class WindowMain {
|
||||
|
||||
// This method will be called when Electron is shutting
|
||||
// down the application.
|
||||
app.on("before-quit", () => {
|
||||
app.on("before-quit", async () => {
|
||||
// Allow biometric to auto-prompt on reload
|
||||
// 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
|
||||
this.stateService.setBiometricPromptCancelled(false);
|
||||
await this.biometricStateService.resetPromptCancelled();
|
||||
this.isQuitting = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { WindowMain } from "../../../main/window.main";
|
||||
import { ElectronStateService } from "../../services/electron-state.service.abstraction";
|
||||
|
||||
import BiometricDarwinMain from "./biometric.darwin.main";
|
||||
import BiometricWindowsMain from "./biometric.windows.main";
|
||||
@@ -24,7 +24,7 @@ jest.mock("@bitwarden/desktop-native", () => {
|
||||
describe("biometrics tests", function () {
|
||||
const i18nService = mock<I18nService>();
|
||||
const windowMain = mock<WindowMain>();
|
||||
const stateService = mock<ElectronStateService>();
|
||||
const stateService = mock<StateService>();
|
||||
const logService = mock<LogService>();
|
||||
const messagingService = mock<MessagingService>();
|
||||
const biometricStateService = mock<BiometricStateService>();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { WindowMain } from "../../../main/window.main";
|
||||
import { ElectronStateService } from "../../services/electron-state.service.abstraction";
|
||||
|
||||
import { BiometricsServiceAbstraction, OsBiometricService } from "./biometrics.service.abstraction";
|
||||
|
||||
@@ -16,7 +16,7 @@ export class BiometricsService implements BiometricsServiceAbstraction {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private windowMain: WindowMain,
|
||||
private stateService: ElectronStateService,
|
||||
private stateService: StateService,
|
||||
private logService: LogService,
|
||||
private messagingService: MessagingService,
|
||||
private platform: NodeJS.Platform,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { makeEncString } from "@bitwarden/common/spec";
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
} from "../../../../../libs/common/spec/fake-account-service";
|
||||
|
||||
import { ElectronCryptoService } from "./electron-crypto.service";
|
||||
import { ElectronStateService } from "./electron-state.service.abstraction";
|
||||
|
||||
describe("electronCryptoService", () => {
|
||||
let sut: ElectronCryptoService;
|
||||
@@ -29,7 +29,7 @@ describe("electronCryptoService", () => {
|
||||
const encryptService = mock<EncryptService>();
|
||||
const platformUtilService = mock<PlatformUtilsService>();
|
||||
const logService = mock<LogService>();
|
||||
const stateService = mock<ElectronStateService>();
|
||||
const stateService = mock<StateService>();
|
||||
let accountService: FakeAccountService;
|
||||
let stateProvider: FakeStateProvider;
|
||||
const biometricStateService = mock<BiometricStateService>();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -15,8 +16,6 @@ import { CsprngString } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { ElectronStateService } from "./electron-state.service.abstraction";
|
||||
|
||||
export class ElectronCryptoService extends CryptoService {
|
||||
constructor(
|
||||
keyGenerationService: KeyGenerationService,
|
||||
@@ -24,7 +23,7 @@ export class ElectronCryptoService extends CryptoService {
|
||||
encryptService: EncryptService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
protected override stateService: ElectronStateService,
|
||||
stateService: StateService,
|
||||
accountService: AccountService,
|
||||
stateProvider: StateProvider,
|
||||
private biometricStateService: BiometricStateService,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
|
||||
import { Account } from "../../models/account";
|
||||
|
||||
export abstract class ElectronStateService extends StateService<Account> {
|
||||
getDismissedBiometricRequirePasswordOnStart: (options?: StorageOptions) => Promise<boolean>;
|
||||
setDismissedBiometricRequirePasswordOnStart: (options?: StorageOptions) => Promise<void>;
|
||||
}
|
||||
@@ -7,12 +7,7 @@ import { DeviceKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { Account } from "../../models/account";
|
||||
|
||||
import { ElectronStateService as ElectronStateServiceAbstraction } from "./electron-state.service.abstraction";
|
||||
|
||||
export class ElectronStateService
|
||||
extends BaseStateService<GlobalState, Account>
|
||||
implements ElectronStateServiceAbstraction
|
||||
{
|
||||
export class ElectronStateService extends BaseStateService<GlobalState, Account> {
|
||||
private partialKeys = {
|
||||
deviceKey: "_deviceKey",
|
||||
};
|
||||
@@ -23,24 +18,6 @@ export class ElectronStateService
|
||||
await super.addAccount(account);
|
||||
}
|
||||
|
||||
async getDismissedBiometricRequirePasswordOnStart(options?: StorageOptions): Promise<boolean> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
return account?.settings?.dismissedBiometricRequirePasswordOnStartCallout;
|
||||
}
|
||||
|
||||
async setDismissedBiometricRequirePasswordOnStart(options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
account.settings.dismissedBiometricRequirePasswordOnStartCallout = true;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
override async getDeviceKey(options?: StorageOptions): Promise<DeviceKey | null> {
|
||||
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||
if (options?.userId == null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -24,11 +25,10 @@ import { GenerateResponse } from "../models/native-messaging/encrypted-message-r
|
||||
import { MessageResponseData } from "../models/native-messaging/encrypted-message-responses/message-response-data";
|
||||
import { SuccessStatusResponse } from "../models/native-messaging/encrypted-message-responses/success-status-response";
|
||||
import { UserStatusErrorResponse } from "../models/native-messaging/encrypted-message-responses/user-status-error-response";
|
||||
import { ElectronStateService } from "../platform/services/electron-state.service";
|
||||
|
||||
export class EncryptedMessageHandlerService {
|
||||
constructor(
|
||||
private stateService: ElectronStateService,
|
||||
private stateService: StateService,
|
||||
private authService: AuthService,
|
||||
private cipherService: CipherService,
|
||||
private policyService: PolicyService,
|
||||
|
||||
@@ -20,7 +20,9 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
@@ -82,6 +84,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -265,6 +268,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.policyService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.keyConnectorService.clear(),
|
||||
this.biometricStateService.logout(userId as UserId),
|
||||
]);
|
||||
|
||||
this.searchService.clearIndex();
|
||||
|
||||
@@ -17,6 +17,7 @@ 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";
|
||||
|
||||
@@ -45,6 +46,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
biometricStateService: BiometricStateService,
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
@@ -66,6 +68,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
deviceTrustCryptoService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
biometricStateService,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user