mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[BEEEP] [EC-141] Rust - Windows hello (#2635)
This commit is contained in:
8
apps/desktop/desktop_native/.cargo/config
Normal file
8
apps/desktop/desktop_native/.cargo/config
Normal file
@@ -0,0 +1,8 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||
@@ -25,9 +25,11 @@ widestring = "0.5.1"
|
||||
windows = {version = "0.32.0", features = [
|
||||
"alloc",
|
||||
"Foundation",
|
||||
"Security_Credentials_UI",
|
||||
"Storage_Streams",
|
||||
"Win32_Foundation",
|
||||
"Win32_Security_Credentials",
|
||||
"Win32_System_WinRT",
|
||||
]}
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies]
|
||||
|
||||
@@ -13,7 +13,7 @@ switch (process.platform) {
|
||||
break;
|
||||
|
||||
default:
|
||||
targets = ['x86_64-unknown-linux-gnu'];
|
||||
targets = ['x86_64-unknown-linux-musl'];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
4
apps/desktop/desktop_native/index.d.ts
vendored
4
apps/desktop/desktop_native/index.d.ts
vendored
@@ -13,3 +13,7 @@ export namespace passwords {
|
||||
/** Delete the stored password from the keychain. */
|
||||
export function deletePassword(service: string, account: string): Promise<void>
|
||||
}
|
||||
export namespace biometrics {
|
||||
export function prompt(hwnd: Buffer, message: string): Promise<boolean>
|
||||
export function available(): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-android-arm64')
|
||||
nativeBinding = require('@bitwarden/desktop-native-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -42,7 +42,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-android-arm-eabi')
|
||||
nativeBinding = require('@bitwarden/desktop-native-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -62,7 +62,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-win32-x64-msvc')
|
||||
nativeBinding = require('@bitwarden/desktop-native-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -76,7 +76,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-win32-ia32-msvc')
|
||||
nativeBinding = require('@bitwarden/desktop-native-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -90,7 +90,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-win32-arm64-msvc')
|
||||
nativeBinding = require('@bitwarden/desktop-native-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -108,7 +108,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-darwin-x64')
|
||||
nativeBinding = require('@bitwarden/desktop-native-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -122,7 +122,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-darwin-arm64')
|
||||
nativeBinding = require('@bitwarden/desktop-native-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -141,7 +141,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-freebsd-x64')
|
||||
nativeBinding = require('@bitwarden/desktop-native-freebsd-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -150,61 +150,31 @@ switch (platform) {
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop-native-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'desktop_native.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop-native-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
@@ -215,7 +185,7 @@ switch (platform) {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./desktop_native.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('@bitwarden/desktop_native-linux-arm-gnueabihf')
|
||||
nativeBinding = require('@bitwarden/desktop-native-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
@@ -236,6 +206,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { passwords } = nativeBinding
|
||||
const { passwords, biometrics } = nativeBinding
|
||||
|
||||
module.exports.passwords = passwords
|
||||
module.exports.biometrics = biometrics
|
||||
|
||||
41
apps/desktop/desktop_native/package-lock.json
generated
41
apps/desktop/desktop_native/package-lock.json
generated
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "@bitwarden/desktop_native",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@bitwarden/desktop_native",
|
||||
"version": "0.1.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/cli": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
|
||||
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"napi": "scripts/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@napi-rs/cli": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
|
||||
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "@bitwarden/desktop_native",
|
||||
"name": "@bitwarden/desktop-native",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.node",
|
||||
"scripts": {
|
||||
"build": "napi build --release --platform",
|
||||
"build:debug": "napi build --platform",
|
||||
"build": "napi build --release --platform --js false",
|
||||
"build:debug": "napi build --platform --js false",
|
||||
"build:cross-platform": "node build.js",
|
||||
"test": "cargo test"
|
||||
},
|
||||
|
||||
9
apps/desktop/desktop_native/src/biometric/macos.rs
Normal file
9
apps/desktop/desktop_native/src/biometric/macos.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
pub fn prompt(_hwnd: Vec<u8>, _message: String) -> Result<bool> {
|
||||
bail!("platform not supported");
|
||||
}
|
||||
|
||||
pub fn available() -> Result<bool> {
|
||||
bail!("platform not supported");
|
||||
}
|
||||
5
apps/desktop/desktop_native/src/biometric/mod.rs
Normal file
5
apps/desktop/desktop_native/src/biometric/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[cfg_attr(target_os = "linux", path = "unix.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(target_os = "macos", path = "macos.rs")]
|
||||
mod biometric;
|
||||
pub use biometric::*;
|
||||
9
apps/desktop/desktop_native/src/biometric/unix.rs
Normal file
9
apps/desktop/desktop_native/src/biometric/unix.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
pub fn prompt(_hwnd: Vec<u8>, _message: String) -> Result<bool> {
|
||||
bail!("platform not supported");
|
||||
}
|
||||
|
||||
pub fn available() -> Result<bool> {
|
||||
bail!("platform not supported");
|
||||
}
|
||||
51
apps/desktop/desktop_native/src/biometric/windows.rs
Normal file
51
apps/desktop/desktop_native/src/biometric/windows.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use anyhow::Result;
|
||||
use windows::{
|
||||
core::factory, Foundation::IAsyncOperation, Security::Credentials::UI::*,
|
||||
Win32::Foundation::HWND, Win32::System::WinRT::IUserConsentVerifierInterop,
|
||||
};
|
||||
|
||||
pub fn prompt(hwnd: Vec<u8>, message: String) -> Result<bool> {
|
||||
let interop = factory::<UserConsentVerifier, IUserConsentVerifierInterop>()?;
|
||||
|
||||
let h = isize::from_le_bytes(hwnd.try_into().unwrap());
|
||||
let window = HWND(h);
|
||||
|
||||
let operation: IAsyncOperation<UserConsentVerificationResult> =
|
||||
unsafe { interop.RequestVerificationForWindowAsync(window, message)? };
|
||||
|
||||
let result: UserConsentVerificationResult = operation.get()?;
|
||||
|
||||
match result {
|
||||
UserConsentVerificationResult::Verified => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available() -> Result<bool> {
|
||||
let ucv_available = UserConsentVerifier::CheckAvailabilityAsync()?.get()?;
|
||||
|
||||
match ucv_available {
|
||||
UserConsentVerifierAvailability::Available => Ok(true),
|
||||
UserConsentVerifierAvailability::DeviceBusy => Ok(true), // TODO: Look into removing this and making the check more ad-hoc
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_prompt() {
|
||||
prompt(
|
||||
vec![0, 0, 0, 0, 0, 0, 0, 0],
|
||||
String::from("Hello from Rust"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_available() {
|
||||
assert!(available().unwrap())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
mod biometric;
|
||||
mod password;
|
||||
|
||||
#[napi]
|
||||
@@ -37,3 +38,21 @@ pub mod passwords {
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub mod biometrics {
|
||||
// Prompt for biometric confirmation
|
||||
#[napi]
|
||||
pub async fn prompt(
|
||||
hwnd: napi::bindgen_prelude::Buffer,
|
||||
message: String,
|
||||
) -> napi::Result<bool> {
|
||||
super::biometric::prompt(hwnd.into(), message)
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn available() -> napi::Result<bool> {
|
||||
super::biometric::available().map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
},
|
||||
"afterSign": "scripts/after-sign.js",
|
||||
"asarUnpack": ["**/*.node"],
|
||||
"files": ["**/*", "!**/node_modules/@bitwarden/desktop-native/**/*"],
|
||||
"files": [
|
||||
"**/*",
|
||||
"!**/node_modules/@bitwarden/desktop-native/**/*",
|
||||
"**/node_modules/@bitwarden/desktop-native/index.js",
|
||||
"**/node_modules/@bitwarden/desktop-native/desktop_native.${platform}-${arch}*.node"
|
||||
],
|
||||
"electronVersion": "19.0.8",
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"publish": {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { StateService } from "@bitwarden/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() {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export abstract class BiometricMain {
|
||||
isError: boolean;
|
||||
init: () => Promise<void>;
|
||||
supportsBiometric: () => Promise<boolean>;
|
||||
authenticateBiometric: () => Promise<boolean>;
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { ipcMain } from "electron";
|
||||
import forceFocus from "forcefocus";
|
||||
|
||||
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 { biometrics } from "@bitwarden/desktop-native";
|
||||
import { WindowMain } from "@bitwarden/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,
|
||||
@@ -21,126 +17,31 @@ export default class BiometricWindowsMain implements BiometricMain {
|
||||
) {}
|
||||
|
||||
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;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
await this.stateService.setEnableBiometric(supportsBiometric);
|
||||
await this.stateService.setBiometricText("unlockWithWindowsHello");
|
||||
await this.stateService.setNoAutoPromptBiometricsText("autoPromptWindowsHello");
|
||||
|
||||
ipcMain.on("biometric", async (event: any, message: any) => {
|
||||
event.returnValue = await this.authenticateBiometric();
|
||||
ipcMain.handle("biometric", async () => {
|
||||
return await this.authenticateBiometric();
|
||||
});
|
||||
}
|
||||
|
||||
async supportsBiometric(): Promise<boolean> {
|
||||
const availability = await this.checkAvailabilityAsync();
|
||||
|
||||
return this.getAllowedAvailabilities().includes(availability);
|
||||
try {
|
||||
return await biometrics.available();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const hwnd = this.windowMain.win.getNativeWindowHandle();
|
||||
return await biometrics.prompt(hwnd, this.i18nservice.t("windowsHelloConsentMessage"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ipcMain } from "electron";
|
||||
import { deletePassword, getPassword, setPassword } from "keytar";
|
||||
|
||||
import { passwords } from "@bitwarden/desktop-native";
|
||||
|
||||
import { BiometricMain } from "./biometric/biometric.main";
|
||||
|
||||
@@ -10,7 +11,7 @@ export class DesktopCredentialStorageListener {
|
||||
constructor(private serviceName: string, private biometricService: BiometricMain) {}
|
||||
|
||||
init() {
|
||||
ipcMain.on("keytar", async (event: any, message: any) => {
|
||||
ipcMain.handle("keytar", async (event: any, message: any) => {
|
||||
try {
|
||||
let serviceName = this.serviceName;
|
||||
message.keySuffix = "_" + (message.keySuffix ?? "");
|
||||
@@ -25,23 +26,35 @@ export class DesktopCredentialStorageListener {
|
||||
let val: string | boolean = null;
|
||||
if (authenticated && message.action && message.key) {
|
||||
if (message.action === "getPassword") {
|
||||
val = await getPassword(serviceName, message.key);
|
||||
val = await this.getPassword(serviceName, message.key);
|
||||
} else if (message.action === "hasPassword") {
|
||||
const result = await getPassword(serviceName, message.key);
|
||||
const result = await this.getPassword(serviceName, message.key);
|
||||
val = result != null;
|
||||
} else if (message.action === "setPassword" && message.value) {
|
||||
await setPassword(serviceName, message.key, message.value);
|
||||
await passwords.setPassword(serviceName, message.key, message.value);
|
||||
} else if (message.action === "deletePassword") {
|
||||
await deletePassword(serviceName, message.key);
|
||||
await passwords.deletePassword(serviceName, message.key);
|
||||
}
|
||||
}
|
||||
event.returnValue = val;
|
||||
return val;
|
||||
} catch {
|
||||
event.returnValue = null;
|
||||
return 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();
|
||||
|
||||
1288
apps/desktop/src/package-lock.json
generated
1288
apps/desktop/src/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,8 +12,6 @@
|
||||
"url": "git+https://github.com/bitwarden/clients.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
|
||||
"forcefocus": "^1.1.0",
|
||||
"keytar": "^7.9.0"
|
||||
"@bitwarden/desktop-native": "file:../desktop_native"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +72,7 @@ const main = {
|
||||
],
|
||||
externals: {
|
||||
"electron-reload": "commonjs2 electron-reload",
|
||||
"@nodert-win10-rs4/windows.security.credentials.ui":
|
||||
"commonjs2 @nodert-win10-rs4/windows.security.credentials.ui",
|
||||
forcefocus: "commonjs2 forcefocus",
|
||||
keytar: "commonjs2 keytar",
|
||||
"@bitwarden/desktop-native": "commonjs2 @bitwarden/desktop-native",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user