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;
};