1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-22 11:45:59 +01:00

[PM-5537] Remove Unecessary Biometric State (#7762)

* Create state for biometric client key halves

* Move enc string util to central utils

* Provide biometric state through service

* Use biometric state to track client key half

* Create migration for client key half

* Ensure client key half is removed on logout

* Remove account data for client key half

* Remove unnecessary key definition likes

* Remove moved state from account

* Fix null-conditional operator failure

* Simplify migration

* Remove lame test

* Fix test type

* Add migrator

* Remove state that is never read.

* Remove unnecessary biometric state

We don't need to determine platform in desktop background, it can be done in the UI at any time.

* Fix merge

* Use platform utils to identify OS desktop type
This commit is contained in:
Matt Gibson 2024-02-15 15:29:29 -05:00 committed by GitHub
parent fae12cd0e6
commit c8c1ed42ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 36 additions and 109 deletions

View File

@ -296,8 +296,6 @@ export class NativeMessagingBackground {
switch (message.command) {
case "biometricUnlock": {
await this.stateService.setBiometricAwaitingAcceptance(null);
if (message.response === "not enabled") {
this.messagingService.send("showDialog", {
title: { key: "biometricsNotEnabledTitle" },

View File

@ -369,14 +369,12 @@ export class SettingsComponent implements OnInit {
const awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService);
const awaitDesktopDialogClosed = firstValueFrom(awaitDesktopDialogRef.closed);
await this.stateService.setBiometricAwaitingAcceptance(true);
await this.cryptoService.refreshAdditionalKeys();
await Promise.race([
awaitDesktopDialogClosed.then(async (result) => {
if (result !== true) {
this.form.controls.biometric.setValue(false);
await this.stateService.setBiometricAwaitingAcceptance(null);
}
}),
this.platformUtilsService

View File

@ -24,6 +24,7 @@ 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",
templateUrl: "settings.component.html",
@ -39,9 +40,7 @@ export class SettingsComponent implements OnInit {
themeOptions: any[];
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometricText: string;
additionalBiometricSettingsText: string;
autoPromptBiometricsText: string;
showAlwaysShowDock = false;
requireEnableTray = false;
showDuckDuckGoIntegrationOption = false;
@ -275,12 +274,10 @@ export class SettingsComponent implements OnInit {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricText = await this.stateService.getBiometricText();
this.additionalBiometricSettingsText =
this.biometricText === "unlockWithTouchId"
? "additionalTouchIdSettings"
: "additionalWindowsHelloSettings";
this.autoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.previousVaultTimeout = this.form.value.vaultTimeout;
this.refreshTimeoutSettings$
@ -667,4 +664,26 @@ export class SettingsComponent implements OnInit {
this.destroy$.next();
this.destroy$.complete();
}
get biometricText() {
switch (this.platformUtilsService.getDevice()) {
case DeviceType.MacOsDesktop:
return "unlockWithTouchId";
case DeviceType.WindowsDesktop:
return "unlockWithWindowsHello";
default:
throw new Error("Unsupported platform");
}
}
get autoPromptBiometricsText() {
switch (this.platformUtilsService.getDevice()) {
case DeviceType.MacOsDesktop:
return "autoPromptTouchId";
case DeviceType.WindowsDesktop:
return "autoPromptWindowsHello";
default:
throw new Error("Unsupported platform");
}
}
}

View File

@ -185,4 +185,15 @@ export class LockComponent extends BaseLockComponent {
await this.stateService.setDismissedBiometricRequirePasswordOnStart();
}
}
get biometricText() {
switch (this.platformUtilsService.getDevice()) {
case DeviceType.MacOsDesktop:
return "unlockWithTouchId";
case DeviceType.WindowsDesktop:
return "unlockWithWindowsHello";
default:
throw new Error("Unsupported platform");
}
}
}

View File

@ -225,9 +225,6 @@ export class Main {
}
this.powerMonitorMain.init();
await this.updaterMain.init();
if (this.biometricsService != null) {
await this.biometricsService.init();
}
if (
(await this.stateService.getEnableBrowserIntegration()) ||

View File

@ -1,21 +1,12 @@
import { systemPreferences } from "electron";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { passwords } from "@bitwarden/desktop-native";
import { OsBiometricService } from "./biometrics.service.abstraction";
export default class BiometricDarwinMain implements OsBiometricService {
constructor(
private i18nservice: I18nService,
private stateService: StateService,
) {}
async init() {
await this.stateService.setBiometricText("unlockWithTouchId");
await this.stateService.setNoAutoPromptBiometricsText("autoPromptTouchId");
}
constructor(private i18nservice: I18nService) {}
async osSupportsBiometric(): Promise<boolean> {
return systemPreferences.canPromptTouchID();

View File

@ -5,7 +5,6 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
import { biometrics, passwords } from "@bitwarden/desktop-native";
import { WindowMain } from "../../../main/window.main";
import { ElectronStateService } from "../../services/electron-state.service.abstraction";
import { OsBiometricService } from "./biometrics.service.abstraction";
@ -21,15 +20,9 @@ export default class BiometricWindowsMain implements OsBiometricService {
constructor(
private i18nService: I18nService,
private windowMain: WindowMain,
private stateService: ElectronStateService,
private logService: LogService,
) {}
async init() {
await this.stateService.setBiometricText("unlockWithWindowsHello");
await this.stateService.setNoAutoPromptBiometricsText("autoPromptWindowsHello");
}
async osSupportsBiometric(): Promise<boolean> {
return await biometrics.available();
}

View File

@ -1,5 +1,4 @@
export abstract class BiometricsServiceAbstraction {
init: () => Promise<void>;
osSupportsBiometric: () => Promise<boolean>;
canAuthBiometric: ({
service,
@ -26,7 +25,6 @@ export abstract class BiometricsServiceAbstraction {
}
export interface OsBiometricService {
init: () => Promise<void>;
osSupportsBiometric: () => Promise<boolean>;
authenticateBiometric: () => Promise<boolean>;
getBiometricKey: (

View File

@ -43,13 +43,7 @@ describe("biometrics tests", function () {
const mockService = mock<OsBiometricService>();
(sut as any).platformSpecificService = mockService;
// 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
sut.init();
// 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
sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" });
expect(mockService.init).toBeCalled();
await sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" });
await sut.canAuthBiometric({ service: "test", key: "test", userId });
expect(mockService.osSupportsBiometric).toBeCalled();
@ -111,9 +105,6 @@ describe("biometrics tests", function () {
innerService = mock();
(sut as any).platformSpecificService = innerService;
// 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
sut.init();
});
it("should return false if client key half is required and not provided", async () => {
@ -128,7 +119,6 @@ describe("biometrics tests", function () {
// 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
sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" });
expect(innerService.init).toBeCalled();
await sut.canAuthBiometric({ service: "test", key: "test", userId });
expect(innerService.osSupportsBiometric).toBeCalled();

View File

@ -58,10 +58,6 @@ export class BiometricsService implements BiometricsServiceAbstraction {
this.platformSpecificService = new NoopBiometricsService();
}
async init() {
return await this.platformSpecificService.init();
}
async osSupportsBiometric() {
return await this.platformSpecificService.osSupportsBiometric();
}

View File

@ -41,7 +41,6 @@ export class LockComponent implements OnInit, OnDestroy {
formPromise: Promise<MasterPasswordPolicyResponse>;
supportsBiometric: boolean;
biometricLock: boolean;
biometricText: string;
protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
@ -343,7 +342,6 @@ export class LockComponent implements OnInit, OnDestroy {
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
((await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric)) ||
!this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
this.webVaultHostname = await this.environmentService.getHost();

View File

@ -69,12 +69,8 @@ export abstract class StateService<T extends Account = Account> {
setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise<void>;
getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise<boolean>;
setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise<boolean>;
setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>;
setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricText: (options?: StorageOptions) => Promise<string>;
setBiometricText: (value: string, options?: StorageOptions) => Promise<void>;
getBiometricUnlock: (options?: StorageOptions) => Promise<boolean>;
setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise<void>;
getCanAccessPremium: (options?: StorageOptions) => Promise<boolean>;
@ -378,8 +374,6 @@ export abstract class StateService<T extends Account = Account> {
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: unknown }>;
setNeverDomains: (value: { [id: string]: unknown }, options?: StorageOptions) => Promise<void>;
getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise<string>;
setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise<void>;
getOpenAtLogin: (options?: StorageOptions) => Promise<boolean>;
setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise<void>;
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;

View File

@ -11,15 +11,11 @@ export class GlobalState {
window?: WindowState = new WindowState();
twoFactorToken?: string;
disableFavicon?: boolean;
biometricAwaitingAcceptance?: boolean;
biometricFingerprintValidated?: boolean;
vaultTimeout?: number;
vaultTimeoutAction?: string;
loginRedirect?: any;
mainWindowSize?: number;
enableBiometrics?: boolean;
biometricText?: string;
noAutoPromptBiometricsText?: string;
enableTray?: boolean;
enableMinimizeToTray?: boolean;
enableCloseToTray?: boolean;

View File

@ -372,24 +372,6 @@ export class StateService<
);
}
async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise<boolean> {
return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.biometricAwaitingAcceptance ?? false
);
}
async setBiometricAwaitingAcceptance(value: boolean, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
globals.biometricAwaitingAcceptance = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getBiometricFingerprintValidated(options?: StorageOptions): Promise<boolean> {
return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@ -408,23 +390,6 @@ export class StateService<
);
}
async getBiometricText(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.biometricText;
}
async setBiometricText(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
globals.biometricText = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getBiometricUnlock(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
@ -1989,23 +1954,6 @@ export class StateService<
);
}
async getNoAutoPromptBiometricsText(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.noAutoPromptBiometricsText;
}
async setNoAutoPromptBiometricsText(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
globals.noAutoPromptBiometricsText = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getOpenAtLogin(options?: StorageOptions): Promise<boolean> {
return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))