From 2ea35579d5e27b8a3d4007bf55a1bf7cf6562c07 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 28 Aug 2025 15:14:02 +0200 Subject: [PATCH] Re-add biometric unlock on app start to Windows Hello --- .../src/app/accounts/settings.component.html | 13 +++++++++++ .../src/app/accounts/settings.component.ts | 23 +++++++++++++++++++ .../biometrics/desktop.biometrics.service.ts | 2 ++ .../main-biometrics-ipc.listener.ts | 7 ++++++ .../biometrics/main-biometrics.service.ts | 8 +++++++ .../biometrics/renderer-biometrics.service.ts | 8 +++++++ apps/desktop/src/key-management/preload.ts | 11 +++++++++ apps/desktop/src/locales/en/messages.json | 3 +++ apps/desktop/src/types/biometric-message.ts | 13 ++++++++++- 9 files changed, 87 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index a9fdcd2f088..d2b8bd055a4 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -80,6 +80,19 @@ {{ "additionalTouchIdSettings" | i18n }} + +
+
+ +
+
{ + const userKey = await firstValueFrom(this.keyService.userKey$(activeAccount.id)); + if (enabled) { + if (!(await this.biometricsService.hasPersistentKey(activeAccount.id))) { + await this.biometricsService.enrollPersistent(activeAccount.id, userKey); + } + } else { + await this.biometricsService.deleteBiometricUnlockKeyForUser(activeAccount.id); + await this.biometricsService.setBiometricProtectedUnlockKeyForUser( + activeAccount.id, + userKey, + ); + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); this.form.controls.enableBrowserIntegration.valueChanges .pipe(takeUntil(this.destroy$)) diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index 97e1d322a0e..e3be5b289d0 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -13,4 +13,6 @@ export abstract class DesktopBiometricsService extends BiometricsService { ): Promise; abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise; abstract setupBiometrics(): Promise; + abstract enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise; + abstract hasPersistentKey(userId: UserId): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts index d4ce01f53f4..91f61c2e7c1 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts @@ -51,6 +51,13 @@ export class MainBiometricsIPCListener { return await this.biometricService.setShouldAutopromptNow(message.data as boolean); case BiometricAction.GetShouldAutoprompt: return await this.biometricService.getShouldAutopromptNow(); + case BiometricAction.HasPersistentKey: + return await this.biometricService.hasPersistentKey(message.userId as UserId); + case BiometricAction.EnrollPersistent: + return await this.biometricService.enrollPersistent( + message.userId as UserId, + SymmetricCryptoKey.fromString(message.key as string), + ); default: return; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts index f6747d9ea5e..4c820fd67da 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -128,4 +128,12 @@ export class MainBiometricsService extends DesktopBiometricsService { async canEnableBiometricUnlock(): Promise { return true; } + + async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise { + return await this.osBiometricsService.enrollPersistent(userId, key); + } + + async hasPersistentKey(userId: UserId): Promise { + return await this.osBiometricsService.hasPersistentKey(userId); + } } diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts index c7ed88d390f..0edd7308b45 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -68,4 +68,12 @@ export class RendererBiometricsService extends DesktopBiometricsService { BiometricsStatus.ManualSetupNeeded, ].includes(biometricStatus); } + + async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise { + return await ipc.keyManagement.biometric.enrollPersistent(userId, key.toBase64()); + } + + async hasPersistentKey(userId: UserId): Promise { + return await ipc.keyManagement.biometric.hasPersistentKey(userId); + } } diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index 7f8576b8472..255c2c26270 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -50,6 +50,17 @@ const biometric = { action: BiometricAction.SetShouldAutoprompt, data: should, } satisfies BiometricMessage), + enrollPersistent: (userId: string, keyB64: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.EnrollPersistent, + userId: userId, + key: keyB64, + } satisfies BiometricMessage), + hasPersistentKey: (userId: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.HasPersistentKey, + userId: userId, + } satisfies BiometricMessage), }; export default { diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index eaa5f7f0f1e..e1b2f5e309f 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1849,6 +1849,9 @@ "lockWithMasterPassOnRestart1": { "message": "Lock with master password on restart" }, + "allowBiometricUnlockOnAppRestart": { + "message": "Allow biometric unlock on app restart" + }, "deleteAccount": { "message": "Delete account" }, diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 9711b49496d..52224a17736 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -13,6 +13,9 @@ export enum BiometricAction { GetShouldAutoprompt = "getShouldAutoprompt", SetShouldAutoprompt = "setShouldAutoprompt", + + EnrollPersistent = "enrollPersistent", + HasPersistentKey = "hasPersistentKey", } export type BiometricMessage = @@ -22,7 +25,15 @@ export type BiometricMessage = key: string; } | { - action: Exclude; + action: BiometricAction.EnrollPersistent; + userId: string; + key: string; + } + | { + action: Exclude< + BiometricAction, + BiometricAction.SetKeyForUser | BiometricAction.EnrollPersistent + >; userId?: string; data?: any; };