mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 10:13:31 +00:00
[BEEEP] Add native rust module (#1379)
This commit is contained in:
@@ -37,7 +37,7 @@ import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { MenuUpdateRequest } from "src/main/menu.updater";
|
||||
import { MenuUpdateRequest } from "../main/menu/menu.updater";
|
||||
|
||||
import { PremiumComponent } from "./accounts/premium.component";
|
||||
import { SettingsComponent } from "./accounts/settings.component";
|
||||
|
||||
19
src/main.ts
19
src/main.ts
@@ -2,11 +2,9 @@ import * as path from "path";
|
||||
|
||||
import { app } from "electron";
|
||||
|
||||
import { BiometricMain } from "jslib-common/abstractions/biometric.main";
|
||||
import { StateFactory } from "jslib-common/factories/stateFactory";
|
||||
import { GlobalState } from "jslib-common/models/domain/globalState";
|
||||
import { StateService } from "jslib-common/services/state.service";
|
||||
import { KeytarStorageListener } from "jslib-electron/keytarStorageListener";
|
||||
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
|
||||
import { ElectronMainMessagingService } from "jslib-electron/services/electronMainMessaging.service";
|
||||
import { ElectronStorageService } from "jslib-electron/services/electronStorage.service";
|
||||
@@ -14,7 +12,9 @@ import { TrayMain } from "jslib-electron/tray.main";
|
||||
import { UpdaterMain } from "jslib-electron/updater.main";
|
||||
import { WindowMain } from "jslib-electron/window.main";
|
||||
|
||||
import { MenuMain } from "./main/menu.main";
|
||||
import { BiometricMain } from "./main/biometric/biometric.main";
|
||||
import { DesktopCredentialStorageListener } from "./main/desktopCredentialStorageListener";
|
||||
import { MenuMain } from "./main/menu/menu.main";
|
||||
import { MessagingMain } from "./main/messaging.main";
|
||||
import { NativeMessagingMain } from "./main/nativeMessaging.main";
|
||||
import { PowerMonitorMain } from "./main/powerMonitor.main";
|
||||
@@ -27,7 +27,7 @@ export class Main {
|
||||
storageService: ElectronStorageService;
|
||||
messagingService: ElectronMainMessagingService;
|
||||
stateService: StateService;
|
||||
keytarStorageListener: KeytarStorageListener;
|
||||
desktopCredentialStorageListener: DesktopCredentialStorageListener;
|
||||
|
||||
windowMain: WindowMain;
|
||||
messagingMain: MessagingMain;
|
||||
@@ -116,7 +116,7 @@ export class Main {
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// eslint-disable-next-line
|
||||
const BiometricWindowsMain = require("jslib-electron/biometric.windows.main").default;
|
||||
const BiometricWindowsMain = require("./main/biometric/biometric.windows.main").default;
|
||||
this.biometricMain = new BiometricWindowsMain(
|
||||
this.i18nService,
|
||||
this.windowMain,
|
||||
@@ -125,11 +125,14 @@ export class Main {
|
||||
);
|
||||
} else if (process.platform === "darwin") {
|
||||
// eslint-disable-next-line
|
||||
const BiometricDarwinMain = require("jslib-electron/biometric.darwin.main").default;
|
||||
const BiometricDarwinMain = require("./main/biometric/biometric.darwin.main").default;
|
||||
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
|
||||
}
|
||||
|
||||
this.keytarStorageListener = new KeytarStorageListener("Bitwarden", this.biometricMain);
|
||||
this.desktopCredentialStorageListener = new DesktopCredentialStorageListener(
|
||||
"Bitwarden",
|
||||
this.biometricMain
|
||||
);
|
||||
|
||||
this.nativeMessagingMain = new NativeMessagingMain(
|
||||
this.logService,
|
||||
@@ -140,7 +143,7 @@ export class Main {
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
this.keytarStorageListener.init();
|
||||
this.desktopCredentialStorageListener.init();
|
||||
this.windowMain.init().then(
|
||||
async () => {
|
||||
const locale = await this.stateService.getLocale();
|
||||
|
||||
36
src/main/biometric/biometric.darwin.main.ts
Normal file
36
src/main/biometric/biometric.darwin.main.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { ipcMain, systemPreferences } from "electron";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { BiometricMain } from "../biometric/biometric.main";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/main/biometric/biometric.main.ts
Normal file
6
src/main/biometric/biometric.main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export abstract class BiometricMain {
|
||||
isError: boolean;
|
||||
init: () => Promise<void>;
|
||||
supportsBiometric: () => Promise<boolean>;
|
||||
authenticateBiometric: () => Promise<boolean>;
|
||||
}
|
||||
146
src/main/biometric/biometric.windows.main.ts
Normal file
146
src/main/biometric/biometric.windows.main.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { ipcMain } from "electron";
|
||||
import forceFocus from "forcefocus";
|
||||
|
||||
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 "jslib-electron/window.main";
|
||||
|
||||
import { BiometricMain } from "src/main/biometric/biometric.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;
|
||||
}
|
||||
}
|
||||
63
src/main/desktopCredentialStorageListener.ts
Normal file
63
src/main/desktopCredentialStorageListener.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { passwords } from "@bitwarden/desktop-native";
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
import { BiometricMain } from "./biometric/biometric.main";
|
||||
|
||||
const AuthRequiredSuffix = "_biometric";
|
||||
const AuthenticatedActions = ["getPassword"];
|
||||
|
||||
export class DesktopCredentialStorageListener {
|
||||
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 this.getPassword(serviceName, message.key);
|
||||
} else if (message.action === "hasPassword") {
|
||||
const result = await this.getPassword(serviceName, message.key);
|
||||
val = result != null;
|
||||
} else if (message.action === "setPassword" && message.value) {
|
||||
await passwords.setPassword(serviceName, message.key, message.value);
|
||||
} else if (message.action === "deletePassword") {
|
||||
await passwords.deletePassword(serviceName, message.key);
|
||||
}
|
||||
}
|
||||
event.returnValue = val;
|
||||
} catch {
|
||||
event.returnValue = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gracefully handle old keytar values, and if detected updated the entry to the proper format
|
||||
private async getPassword(serviceName: string, key: string) {
|
||||
let val = await passwords.getPassword(serviceName, key);
|
||||
try {
|
||||
JSON.parse(val);
|
||||
} catch (e) {
|
||||
val = await passwords.getPasswordKeytar(serviceName, key);
|
||||
await passwords.setPassword(serviceName, key, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private async authenticateBiometric(): Promise<boolean> {
|
||||
if (this.biometricService) {
|
||||
return await this.biometricService.authenticateBiometric();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { app, Menu } from "electron";
|
||||
|
||||
import { BaseMenu } from "jslib-electron/baseMenu";
|
||||
|
||||
import { Main } from "../main";
|
||||
import { Main } from "../../main";
|
||||
|
||||
import { MenuUpdateRequest } from "./menu.updater";
|
||||
import { Menubar } from "./menubar";
|
||||
@@ -7,7 +7,7 @@ import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { Main } from "../main";
|
||||
|
||||
import { MenuUpdateRequest } from "./menu.updater";
|
||||
import { MenuUpdateRequest } from "./menu/menu.updater";
|
||||
|
||||
const SyncInterval = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"url": "https://github.com/bitwarden/desktop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bitwarden/desktop-native": "file:../desktop_native",
|
||||
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
|
||||
"forcefocus": "^1.1.0",
|
||||
"keytar": "7.7.0"
|
||||
"forcefocus": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user