diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index d570e668ccf..6f1ef4a1095 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -7,7 +7,7 @@ import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { StateService } from "@bitwarden/common/services/state.service"; -import { BiometricMain } from "./main/biometric/biometric.main"; +import { BiometricsService, BiometricsServiceAbstraction } from "./main/biometric/index"; import { DesktopCredentialStorageListener } from "./main/desktop-credential-storage-listener"; import { MenuMain } from "./main/menu/menu.main"; import { MessagingMain } from "./main/messaging.main"; @@ -37,7 +37,7 @@ export class Main { menuMain: MenuMain; powerMonitorMain: PowerMonitorMain; trayMain: TrayMain; - biometricMain: BiometricMain; + biometricsService: BiometricsServiceAbstraction; nativeMessagingMain: NativeMessagingMain; constructor() { @@ -117,24 +117,16 @@ export class Main { this.updaterMain ); - if (process.platform === "win32") { - // eslint-disable-next-line - const BiometricWindowsMain = require("./main/biometric/biometric.windows.main").default; - this.biometricMain = new BiometricWindowsMain( - this.i18nService, - this.windowMain, - this.stateService, - this.logService - ); - } else if (process.platform === "darwin") { - // eslint-disable-next-line - const BiometricDarwinMain = require("./main/biometric/biometric.darwin.main").default; - this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService); - } + this.biometricsService = new BiometricsService( + this.i18nService, + this.windowMain, + this.stateService, + this.logService + ); this.desktopCredentialStorageListener = new DesktopCredentialStorageListener( "Bitwarden", - this.biometricMain + this.biometricsService ); this.nativeMessagingMain = new NativeMessagingMain( @@ -166,8 +158,8 @@ export class Main { } this.powerMonitorMain.init(); await this.updaterMain.init(); - if (this.biometricMain != null) { - await this.biometricMain.init(); + if (this.biometricsService != null) { + await this.biometricsService.init(); } if ( diff --git a/apps/desktop/src/main/biometric/biometric.darwin.main.ts b/apps/desktop/src/main/biometric/biometric.darwin.main.ts index 49638425b7b..2e360eb3674 100644 --- a/apps/desktop/src/main/biometric/biometric.darwin.main.ts +++ b/apps/desktop/src/main/biometric/biometric.darwin.main.ts @@ -3,9 +3,9 @@ import { ipcMain, systemPreferences } from "electron"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { BiometricMain } from "../biometric/biometric.main"; +import { BiometricsServiceAbstraction } from "./biometrics.service.abstraction"; -export default class BiometricDarwinMain implements BiometricMain { +export default class BiometricDarwinMain implements BiometricsServiceAbstraction { constructor(private i18nservice: I18nService, private stateService: StateService) {} async init() { diff --git a/apps/desktop/src/main/biometric/biometric.windows.main.ts b/apps/desktop/src/main/biometric/biometric.windows.main.ts index 207e6c38b56..c2df381811e 100644 --- a/apps/desktop/src/main/biometric/biometric.windows.main.ts +++ b/apps/desktop/src/main/biometric/biometric.windows.main.ts @@ -7,9 +7,9 @@ import { biometrics } from "@bitwarden/desktop-native"; import { WindowMain } from "../window.main"; -import { BiometricMain } from "./biometric.main"; +import { BiometricsServiceAbstraction } from "./biometrics.service.abstraction"; -export default class BiometricWindowsMain implements BiometricMain { +export default class BiometricWindowsMain implements BiometricsServiceAbstraction { constructor( private i18nservice: I18nService, private windowMain: WindowMain, diff --git a/apps/desktop/src/main/biometric/biometric.main.ts b/apps/desktop/src/main/biometric/biometrics.service.abstraction.ts similarity index 70% rename from apps/desktop/src/main/biometric/biometric.main.ts rename to apps/desktop/src/main/biometric/biometrics.service.abstraction.ts index a2e3da4df89..88411859122 100644 --- a/apps/desktop/src/main/biometric/biometric.main.ts +++ b/apps/desktop/src/main/biometric/biometrics.service.abstraction.ts @@ -1,4 +1,4 @@ -export abstract class BiometricMain { +export abstract class BiometricsServiceAbstraction { init: () => Promise; supportsBiometric: () => Promise; authenticateBiometric: () => Promise; diff --git a/apps/desktop/src/main/biometric/biometrics.service.spec.ts b/apps/desktop/src/main/biometric/biometrics.service.spec.ts new file mode 100644 index 00000000000..2e5b9a9505b --- /dev/null +++ b/apps/desktop/src/main/biometric/biometrics.service.spec.ts @@ -0,0 +1,83 @@ +import { mock } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; + +import { WindowMain } from "../window.main"; + +import BiometricDarwinMain from "./biometric.darwin.main"; +import BiometricWindowsMain from "./biometric.windows.main"; +import { BiometricsService } from "./biometrics.service"; +import { BiometricsServiceAbstraction } from "./biometrics.service.abstraction"; + +describe("biometrics tests", function () { + const i18nService = mock(); + const windowMain = mock(); + const stateService = mock(); + const logService = mock(); + + it("Should call the platformspecific methods", () => { + const sut = new BiometricsService(i18nService, windowMain, stateService, logService); + + const mockService = mock(); + (sut as any).platformSpecificService = mockService; + sut.init(); + expect(mockService.init).toBeCalled(); + + sut.supportsBiometric(); + expect(mockService.supportsBiometric).toBeCalled(); + + sut.authenticateBiometric(); + expect(mockService.authenticateBiometric).toBeCalled(); + }); + + describe("win32 process platform", function () { + let originalPlatform: NodeJS.Platform = null; + + beforeAll(function () { + originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + value: "win32", + }); + }); + + const sut = new BiometricsService(i18nService, windowMain, stateService, logService); + + it("Should create a biometrics service specific for Windows", () => { + const internalService = (sut as any).platformSpecificService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(BiometricWindowsMain); + }); + + afterAll(function () { + Object.defineProperty(process, "platform", { + value: originalPlatform, + }); + }); + }); + + describe("darwin process platform", function () { + let originalPlatform: NodeJS.Platform = null; + + beforeAll(function () { + originalPlatform = process.platform; + Object.defineProperty(process, "platform", { + value: "darwin", + }); + }); + + it("Should create a biometrics service specific for MacOs", () => { + const sut = new BiometricsService(i18nService, windowMain, stateService, logService); + const internalService = (sut as any).platformSpecificService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(BiometricDarwinMain); + }); + + afterAll(function () { + Object.defineProperty(process, "platform", { + value: originalPlatform, + }); + }); + }); +}); diff --git a/apps/desktop/src/main/biometric/biometrics.service.ts b/apps/desktop/src/main/biometric/biometrics.service.ts new file mode 100644 index 00000000000..b117b0f41f9 --- /dev/null +++ b/apps/desktop/src/main/biometric/biometrics.service.ts @@ -0,0 +1,57 @@ +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; + +import { WindowMain } from "../window.main"; + +import { BiometricsServiceAbstraction } from "./biometrics.service.abstraction"; + +export class BiometricsService implements BiometricsServiceAbstraction { + private platformSpecificService: BiometricsServiceAbstraction; + + constructor( + private i18nService: I18nService, + private windowMain: WindowMain, + private stateService: StateService, + private logService: LogService + ) { + this.loadPlatformSpecificService(); + } + + private loadPlatformSpecificService() { + if (process.platform === "win32") { + this.loadWindowsHelloService(); + } else if (process.platform === "darwin") { + this.loadMacOSService(); + } + } + + private loadWindowsHelloService() { + // eslint-disable-next-line + const BiometricWindowsMain = require("./biometric.windows.main").default; + this.platformSpecificService = new BiometricWindowsMain( + this.i18nService, + this.windowMain, + this.stateService, + this.logService + ); + } + + private loadMacOSService() { + // eslint-disable-next-line + const BiometricDarwinMain = require("./biometric.darwin.main").default; + this.platformSpecificService = new BiometricDarwinMain(this.i18nService, this.stateService); + } + + async init() { + return await this.platformSpecificService.init(); + } + + async supportsBiometric(): Promise { + return await this.platformSpecificService.supportsBiometric(); + } + + async authenticateBiometric(): Promise { + return await this.platformSpecificService.authenticateBiometric(); + } +} diff --git a/apps/desktop/src/main/biometric/index.ts b/apps/desktop/src/main/biometric/index.ts new file mode 100644 index 00000000000..f5a594d966f --- /dev/null +++ b/apps/desktop/src/main/biometric/index.ts @@ -0,0 +1,2 @@ +export * from "./biometrics.service.abstraction"; +export * from "./biometrics.service"; diff --git a/apps/desktop/src/main/desktop-credential-storage-listener.ts b/apps/desktop/src/main/desktop-credential-storage-listener.ts index d31333f8125..c3d923d58f5 100644 --- a/apps/desktop/src/main/desktop-credential-storage-listener.ts +++ b/apps/desktop/src/main/desktop-credential-storage-listener.ts @@ -2,13 +2,16 @@ import { ipcMain } from "electron"; import { passwords } from "@bitwarden/desktop-native"; -import { BiometricMain } from "./biometric/biometric.main"; +import { BiometricsServiceAbstraction } from "./biometric/index"; const AuthRequiredSuffix = "_biometric"; const AuthenticatedActions = ["getPassword"]; export class DesktopCredentialStorageListener { - constructor(private serviceName: string, private biometricService: BiometricMain) {} + constructor( + private serviceName: string, + private biometricService: BiometricsServiceAbstraction + ) {} init() { ipcMain.handle("keytar", async (event: any, message: any) => {