mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
Ps/pm 5537/move biometric unlock to state providers (#8099)
* Establish biometric unlock enabled in state providers * Use biometric state service for biometric state values * Migrate biometricUnlock * Fixup Dependencies * linter and import fixes * Fix injection * Fix merge * Use boolean constructor as mapper * Conform to documented test naming conventions * Commit documentation suggestion Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Fix merge commit * Fix test names --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
This commit is contained in:
@@ -453,6 +453,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.accountService,
|
||||
this.stateProvider,
|
||||
this.biometricStateService,
|
||||
);
|
||||
this.tokenService = new TokenService(this.stateService);
|
||||
this.appIdService = new AppIdService(this.storageService);
|
||||
@@ -619,6 +620,7 @@ export default class MainBackground {
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.biometricStateService,
|
||||
);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
@@ -833,6 +835,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.logService,
|
||||
this.authService,
|
||||
this.biometricStateService,
|
||||
);
|
||||
this.commandsBackground = new CommandsBackground(
|
||||
this,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@@ -8,6 +10,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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
@@ -79,6 +82,7 @@ export class NativeMessagingBackground {
|
||||
private stateService: StateService,
|
||||
private logService: LogService,
|
||||
private authService: AuthService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {
|
||||
// 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
|
||||
@@ -321,10 +325,10 @@ export class NativeMessagingBackground {
|
||||
}
|
||||
|
||||
// Check for initial setup of biometric unlock
|
||||
const enabled = await this.stateService.getBiometricUnlock();
|
||||
const enabled = await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$);
|
||||
if (enabled === null || enabled === false) {
|
||||
if (message.response === "unlocked") {
|
||||
await this.stateService.setBiometricUnlock(true);
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
tokenServiceFactory,
|
||||
TokenServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/token-service.factory";
|
||||
import {
|
||||
biometricStateServiceFactory,
|
||||
BiometricStateServiceInitOptions,
|
||||
} from "../../platform/background/service-factories/biometric-state-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
@@ -29,7 +33,8 @@ export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsService
|
||||
CryptoServiceInitOptions &
|
||||
TokenServiceInitOptions &
|
||||
PolicyServiceInitOptions &
|
||||
StateServiceInitOptions;
|
||||
StateServiceInitOptions &
|
||||
BiometricStateServiceInitOptions;
|
||||
|
||||
export function vaultTimeoutSettingsServiceFactory(
|
||||
cache: { vaultTimeoutSettingsService?: AbstractVaultTimeoutSettingsService } & CachedServices,
|
||||
@@ -45,6 +50,7 @@ export function vaultTimeoutSettingsServiceFactory(
|
||||
await tokenServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await biometricStateServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
BiometricStateService,
|
||||
DefaultBiometricStateService,
|
||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
|
||||
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
||||
import { StateProviderInitOptions, stateProviderFactory } from "./state-provider.factory";
|
||||
|
||||
type BiometricStateServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type BiometricStateServiceInitOptions = BiometricStateServiceFactoryOptions &
|
||||
StateProviderInitOptions;
|
||||
|
||||
export function biometricStateServiceFactory(
|
||||
cache: { biometricStateService?: BiometricStateService } & CachedServices,
|
||||
opts: BiometricStateServiceInitOptions,
|
||||
): Promise<BiometricStateService> {
|
||||
return factory(
|
||||
cache,
|
||||
"biometricStateService",
|
||||
opts,
|
||||
async () => new DefaultBiometricStateService(await stateProviderFactory(cache, opts)),
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "../../background/service-factories/log-service.factory";
|
||||
import { BrowserCryptoService } from "../../services/browser-crypto.service";
|
||||
|
||||
import { biometricStateServiceFactory } from "./biometric-state-service.factory";
|
||||
import {
|
||||
cryptoFunctionServiceFactory,
|
||||
CryptoFunctionServiceInitOptions,
|
||||
@@ -60,6 +61,7 @@ export function cryptoServiceFactory(
|
||||
await stateServiceFactory(cache, opts),
|
||||
await accountServiceFactory(cache, opts),
|
||||
await stateProviderFactory(cache, opts),
|
||||
await biometricStateServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,50 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
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 { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||
import { USER_KEY } from "@bitwarden/common/platform/services/key-state/user-key.state";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
export class BrowserCryptoService extends CryptoService {
|
||||
constructor(
|
||||
keyGenerationService: KeyGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
encryptService: EncryptService,
|
||||
platformUtilService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
accountService: AccountService,
|
||||
stateProvider: StateProvider,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {
|
||||
super(
|
||||
keyGenerationService,
|
||||
cryptoFunctionService,
|
||||
encryptService,
|
||||
platformUtilService,
|
||||
logService,
|
||||
stateService,
|
||||
accountService,
|
||||
stateProvider,
|
||||
);
|
||||
}
|
||||
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
|
||||
if (keySuffix === KeySuffixOptions.Biometric) {
|
||||
return await this.stateService.getBiometricUnlock({ userId: userId });
|
||||
const biometricUnlockPromise =
|
||||
userId == null
|
||||
? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)
|
||||
: this.biometricStateService.getBiometricUnlockEnabled(userId);
|
||||
return await biometricUnlockPromise;
|
||||
}
|
||||
return super.hasUserKeyStored(keySuffix, userId);
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ export class SettingsComponent implements OnInit {
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
await this.stateService.setBiometricUnlock(null);
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
||||
await this.stateService.setBiometricFingerprintValidated(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ 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,
|
||||
DefaultBiometricStateService,
|
||||
} 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";
|
||||
@@ -204,6 +208,7 @@ export class Main {
|
||||
derivedStateProvider: DerivedStateProvider;
|
||||
stateProvider: StateProvider;
|
||||
loginStrategyService: LoginStrategyServiceAbstraction;
|
||||
biometricStateService: BiometricStateService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -490,11 +495,14 @@ export class Main {
|
||||
const lockedCallback = async (userId?: string) =>
|
||||
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
|
||||
|
||||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
this.cryptoService,
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.biometricStateService,
|
||||
);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
|
||||
@@ -445,12 +445,12 @@ export class SettingsComponent implements OnInit {
|
||||
try {
|
||||
if (!enabled || !this.supportsBiometric) {
|
||||
this.form.controls.biometric.setValue(false, { emitEvent: false });
|
||||
await this.stateService.setBiometricUnlock(null);
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
||||
await this.cryptoService.refreshAdditionalKeys();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stateService.setBiometricUnlock(true);
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(true);
|
||||
if (this.isWindows) {
|
||||
// Recommended settings for Windows Hello
|
||||
this.form.controls.requirePasswordOnStart.setValue(true);
|
||||
@@ -465,7 +465,7 @@ export class SettingsComponent implements OnInit {
|
||||
const biometricSet = await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric);
|
||||
this.form.controls.biometric.setValue(biometricSet, { emitEvent: false });
|
||||
if (!biometricSet) {
|
||||
await this.stateService.setBiometricUnlock(null);
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
||||
}
|
||||
} finally {
|
||||
this.messagingService.send("redrawMenu");
|
||||
|
||||
@@ -172,7 +172,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.stateService.getBiometricUnlock()) {
|
||||
if (await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)) {
|
||||
const response = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "windowsBiometricUpdateWarningTitle" },
|
||||
content: { key: "windowsBiometricUpdateWarning" },
|
||||
|
||||
@@ -73,8 +73,8 @@ describe("electronCryptoService", () => {
|
||||
encClientKeyHalf.decrypt = jest.fn().mockResolvedValue(decClientKeyHalf);
|
||||
});
|
||||
|
||||
it("sets an Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => {
|
||||
stateService.getBiometricUnlock.mockResolvedValue(true);
|
||||
it("sets a Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => {
|
||||
biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true);
|
||||
platformUtilService.supportsSecureStorage.mockReturnValue(true);
|
||||
biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true);
|
||||
biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(encClientKeyHalf);
|
||||
@@ -90,7 +90,7 @@ describe("electronCryptoService", () => {
|
||||
});
|
||||
|
||||
it("clears the Biometric key if getBiometricUnlock is false or the platform does not support secure storage", async () => {
|
||||
stateService.getBiometricUnlock.mockResolvedValue(true);
|
||||
biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true);
|
||||
platformUtilService.supportsSecureStorage.mockReturnValue(false);
|
||||
|
||||
await sut.setUserKey(mockUserKey, mockUserId);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
@@ -97,7 +99,11 @@ export class ElectronCryptoService extends CryptoService {
|
||||
|
||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
|
||||
if (keySuffix === KeySuffixOptions.Biometric) {
|
||||
const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId });
|
||||
const biometricUnlockPromise =
|
||||
userId == null
|
||||
? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)
|
||||
: this.biometricStateService.getBiometricUnlockEnabled(userId);
|
||||
const biometricUnlock = await biometricUnlockPromise;
|
||||
return biometricUnlock && this.platformUtilService.supportsSecureStorage();
|
||||
}
|
||||
return await super.shouldStoreKey(keySuffix, userId);
|
||||
|
||||
@@ -8,10 +8,12 @@ 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 { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component";
|
||||
@@ -36,6 +38,7 @@ export class NativeMessagingService {
|
||||
private i18nService: I18nService,
|
||||
private messagingService: MessagingService,
|
||||
private stateService: StateService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private nativeMessageHandler: NativeMessageHandlerService,
|
||||
private dialogService: DialogService,
|
||||
private ngZone: NgZone,
|
||||
@@ -136,7 +139,11 @@ export class NativeMessagingService {
|
||||
return this.send({ command: "biometricUnlock", response: "not supported" }, appId);
|
||||
}
|
||||
|
||||
if (!(await this.stateService.getBiometricUnlock({ userId: message.userId }))) {
|
||||
const biometricUnlockPromise =
|
||||
message.userId == null
|
||||
? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)
|
||||
: this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId);
|
||||
if (!(await biometricUnlockPromise)) {
|
||||
// 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.send({ command: "biometricUnlock", response: "not enabled" }, appId);
|
||||
|
||||
Reference in New Issue
Block a user