diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts new file mode 100644 index 00000000000..fd2431d83ae --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts @@ -0,0 +1,82 @@ +import { mock } from "jest-mock-extended"; + +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; + +import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; + +jest.mock("@bitwarden/desktop-napi", () => ({ + biometrics: { + available: jest.fn(), + setBiometricSecret: jest.fn(), + getBiometricSecret: jest.fn(), + deriveKeyMaterial: jest.fn(), + prompt: jest.fn(), + }, + passwords: { + getPassword: jest.fn(), + deletePassword: jest.fn(), + }, +})); + +describe("OsBiometricsServiceWindows", () => { + let service: OsBiometricsServiceWindows; + let biometricStateService: BiometricStateService; + + beforeEach(() => { + const i18nService = mock(); + const logService = mock(); + biometricStateService = mock(); + const encryptionService = mock(); + const cryptoFunctionService = mock(); + service = new OsBiometricsServiceWindows( + i18nService, + null, + logService, + biometricStateService, + encryptionService, + cryptoFunctionService, + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("getBiometricsFirstUnlockStatusForUser", () => { + const userId = "test-user-id" as UserId; + it("should return Available when requirePasswordOnRestart is false", async () => { + biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(false); + const result = await service.getBiometricsFirstUnlockStatusForUser(userId); + expect(result).toBe(BiometricsStatus.Available); + }); + it("should return Available when requirePasswordOnRestart is true and client key half is set", async () => { + biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true); + (service as any).clientKeyHalves = new Map(); + (service as any).clientKeyHalves.set(userId, new Uint8Array([1, 2, 3, 4])); + const result = await service.getBiometricsFirstUnlockStatusForUser(userId); + expect(result).toBe(BiometricsStatus.Available); + }); + it("should return UnlockNeeded when requirePasswordOnRestart is true and client key half is not set", async () => { + biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(true); + (service as any).clientKeyHalves = new Map(); + const result = await service.getBiometricsFirstUnlockStatusForUser(userId); + expect(result).toBe(BiometricsStatus.UnlockNeeded); + }); + }); + + describe("getOrCreateBiometricEncryptionClientKeyHalf", () => { + const userId = "test-user-id" as UserId; + const key = new SymmetricCryptoKey(new Uint8Array(64)); + it("should return null if getRequirePasswordOnRestart is false", async () => { + biometricStateService.getRequirePasswordOnStart = jest.fn().mockResolvedValue(false); + const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key); + expect(result).toBeNull(); + }); + }); +}); diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts index 4ebb1d45b22..6d3613e4623 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts @@ -274,7 +274,7 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { async runSetup(): Promise {} - private async getOrCreateBiometricEncryptionClientKeyHalf( + async getOrCreateBiometricEncryptionClientKeyHalf( userId: UserId, key: SymmetricCryptoKey, ): Promise { @@ -308,11 +308,14 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise { const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); - const clientKeyHalfB64 = this.clientKeyHalves.get(userId); - const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; - if (!clientKeyHalfSatisfied) { + if (!requireClientKeyHalf) { + return BiometricsStatus.Available; + } + + if (this.clientKeyHalves.has(userId)) { + return BiometricsStatus.Available; + } else { return BiometricsStatus.UnlockNeeded; } - return BiometricsStatus.Available; } }