9 changed files with 29033 additions and 17571 deletions
@ -1,6 +0,0 @@ |
|||||||
export abstract class BiometricMain { |
|
||||||
isError: boolean; |
|
||||||
init: () => Promise<void>; |
|
||||||
supportsBiometric: () => Promise<boolean>; |
|
||||||
authenticateBiometric: () => Promise<boolean>; |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@ |
|||||||
import { ipcMain, systemPreferences } from "electron"; |
|
||||||
|
|
||||||
import { BiometricMain } from "jslib-common/abstractions/biometric.main"; |
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service"; |
|
||||||
import { StateService } from "jslib-common/abstractions/state.service"; |
|
||||||
|
|
||||||
export default class BiometricDarwinMain implements BiometricMain { |
|
||||||
isError = false; |
|
||||||
|
|
||||||
constructor(private i18nservice: I18nService, private stateService: StateService) {} |
|
||||||
|
|
||||||
async init() { |
|
||||||
await this.stateService.setEnableBiometric(await this.supportsBiometric()); |
|
||||||
await this.stateService.setBiometricText("unlockWithTouchId"); |
|
||||||
await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptTouchId"); |
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
ipcMain.on("biometric", async (event: any, message: any) => { |
|
||||||
event.returnValue = await this.authenticateBiometric(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
supportsBiometric(): Promise<boolean> { |
|
||||||
return Promise.resolve(systemPreferences.canPromptTouchID()); |
|
||||||
} |
|
||||||
|
|
||||||
async authenticateBiometric(): Promise<boolean> { |
|
||||||
try { |
|
||||||
await systemPreferences.promptTouchID(this.i18nservice.t("touchIdConsentMessage")); |
|
||||||
return true; |
|
||||||
} catch { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,146 +0,0 @@ |
|||||||
import { ipcMain } from "electron"; |
|
||||||
import forceFocus from "forcefocus"; |
|
||||||
|
|
||||||
import { BiometricMain } from "jslib-common/abstractions/biometric.main"; |
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service"; |
|
||||||
import { LogService } from "jslib-common/abstractions/log.service"; |
|
||||||
import { StateService } from "jslib-common/abstractions/state.service"; |
|
||||||
|
|
||||||
import { WindowMain } from "./window.main"; |
|
||||||
|
|
||||||
export default class BiometricWindowsMain implements BiometricMain { |
|
||||||
isError = false; |
|
||||||
|
|
||||||
private windowsSecurityCredentialsUiModule: any; |
|
||||||
|
|
||||||
constructor( |
|
||||||
private i18nservice: I18nService, |
|
||||||
private windowMain: WindowMain, |
|
||||||
private stateService: StateService, |
|
||||||
private logService: LogService |
|
||||||
) {} |
|
||||||
|
|
||||||
async init() { |
|
||||||
this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule(); |
|
||||||
let supportsBiometric = false; |
|
||||||
try { |
|
||||||
supportsBiometric = await this.supportsBiometric(); |
|
||||||
} catch { |
|
||||||
// store error state so we can let the user know on the settings page
|
|
||||||
this.isError = true; |
|
||||||
} |
|
||||||
await this.stateService.setEnableBiometric(supportsBiometric); |
|
||||||
await this.stateService.setBiometricText("unlockWithWindowsHello"); |
|
||||||
await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptWindowsHello"); |
|
||||||
|
|
||||||
ipcMain.on("biometric", async (event: any, message: any) => { |
|
||||||
event.returnValue = await this.authenticateBiometric(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async supportsBiometric(): Promise<boolean> { |
|
||||||
const availability = await this.checkAvailabilityAsync(); |
|
||||||
|
|
||||||
return this.getAllowedAvailabilities().includes(availability); |
|
||||||
} |
|
||||||
|
|
||||||
async authenticateBiometric(): Promise<boolean> { |
|
||||||
const module = this.getWindowsSecurityCredentialsUiModule(); |
|
||||||
if (module == null) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
const verification = await this.requestVerificationAsync( |
|
||||||
this.i18nservice.t("windowsHelloConsentMessage") |
|
||||||
); |
|
||||||
|
|
||||||
return verification === module.UserConsentVerificationResult.verified; |
|
||||||
} |
|
||||||
|
|
||||||
getWindowsSecurityCredentialsUiModule(): any { |
|
||||||
try { |
|
||||||
if (this.windowsSecurityCredentialsUiModule == null && this.getWindowsMajorVersion() >= 10) { |
|
||||||
this.windowsSecurityCredentialsUiModule = require("@nodert-win10-rs4/windows.security.credentials.ui"); |
|
||||||
} |
|
||||||
return this.windowsSecurityCredentialsUiModule; |
|
||||||
} catch { |
|
||||||
this.isError = true; |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
async checkAvailabilityAsync(): Promise<any> { |
|
||||||
const module = this.getWindowsSecurityCredentialsUiModule(); |
|
||||||
if (module != null) { |
|
||||||
// eslint-disable-next-line
|
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
try { |
|
||||||
module.UserConsentVerifier.checkAvailabilityAsync((error: Error, result: any) => { |
|
||||||
if (error) { |
|
||||||
return resolve(null); |
|
||||||
} |
|
||||||
return resolve(result); |
|
||||||
}); |
|
||||||
} catch { |
|
||||||
this.isError = true; |
|
||||||
return resolve(null); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
return Promise.resolve(null); |
|
||||||
} |
|
||||||
|
|
||||||
async requestVerificationAsync(message: string): Promise<any> { |
|
||||||
const module = this.getWindowsSecurityCredentialsUiModule(); |
|
||||||
if (module != null) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
try { |
|
||||||
module.UserConsentVerifier.requestVerificationAsync( |
|
||||||
message, |
|
||||||
(error: Error, result: any) => { |
|
||||||
if (error) { |
|
||||||
return resolve(null); |
|
||||||
} |
|
||||||
return resolve(result); |
|
||||||
} |
|
||||||
); |
|
||||||
|
|
||||||
forceFocus.focusWindow(this.windowMain.win); |
|
||||||
} catch (error) { |
|
||||||
this.isError = true; |
|
||||||
return reject(error); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
return Promise.resolve(null); |
|
||||||
} |
|
||||||
|
|
||||||
getAllowedAvailabilities(): any[] { |
|
||||||
try { |
|
||||||
const module = this.getWindowsSecurityCredentialsUiModule(); |
|
||||||
if (module != null) { |
|
||||||
return [ |
|
||||||
module.UserConsentVerifierAvailability.available, |
|
||||||
module.UserConsentVerifierAvailability.deviceBusy, |
|
||||||
]; |
|
||||||
} |
|
||||||
} catch { |
|
||||||
/*Ignore error*/ |
|
||||||
} |
|
||||||
return []; |
|
||||||
} |
|
||||||
|
|
||||||
getWindowsMajorVersion(): number { |
|
||||||
if (process.platform !== "win32") { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
try { |
|
||||||
// eslint-disable-next-line
|
|
||||||
const version = require("os").release(); |
|
||||||
return Number.parseInt(version.split(".")[0], 10); |
|
||||||
} catch { |
|
||||||
this.logService.error("Unable to resolve windows major version number"); |
|
||||||
} |
|
||||||
return -1; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,51 +0,0 @@ |
|||||||
import { ipcMain } from "electron"; |
|
||||||
import { deletePassword, getPassword, setPassword } from "keytar"; |
|
||||||
|
|
||||||
import { BiometricMain } from "jslib-common/abstractions/biometric.main"; |
|
||||||
|
|
||||||
const AuthRequiredSuffix = "_biometric"; |
|
||||||
const AuthenticatedActions = ["getPassword"]; |
|
||||||
|
|
||||||
export class KeytarStorageListener { |
|
||||||
constructor(private serviceName: string, private biometricService: BiometricMain) {} |
|
||||||
|
|
||||||
init() { |
|
||||||
ipcMain.on("keytar", async (event: any, message: any) => { |
|
||||||
try { |
|
||||||
let serviceName = this.serviceName; |
|
||||||
message.keySuffix = "_" + (message.keySuffix ?? ""); |
|
||||||
if (message.keySuffix !== "_") { |
|
||||||
serviceName += message.keySuffix; |
|
||||||
} |
|
||||||
|
|
||||||
const authenticationRequired = |
|
||||||
AuthenticatedActions.includes(message.action) && AuthRequiredSuffix === message.keySuffix; |
|
||||||
const authenticated = !authenticationRequired || (await this.authenticateBiometric()); |
|
||||||
|
|
||||||
let val: string | boolean = null; |
|
||||||
if (authenticated && message.action && message.key) { |
|
||||||
if (message.action === "getPassword") { |
|
||||||
val = await getPassword(serviceName, message.key); |
|
||||||
} else if (message.action === "hasPassword") { |
|
||||||
const result = await getPassword(serviceName, message.key); |
|
||||||
val = result != null; |
|
||||||
} else if (message.action === "setPassword" && message.value) { |
|
||||||
await setPassword(serviceName, message.key, message.value); |
|
||||||
} else if (message.action === "deletePassword") { |
|
||||||
await deletePassword(serviceName, message.key); |
|
||||||
} |
|
||||||
} |
|
||||||
event.returnValue = val; |
|
||||||
} catch { |
|
||||||
event.returnValue = null; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
private async authenticateBiometric(): Promise<boolean> { |
|
||||||
if (this.biometricService) { |
|
||||||
return await this.biometricService.authenticateBiometric(); |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue