mirror of
https://github.com/bitwarden/directory-connector
synced 2026-02-25 00:52:53 +00:00
Refactor secure storage implementation to utilize the 'dc-native' module directly in the renderer process, removing the need for the credential storage listener. Update TypeScript definitions and enhance error handling in password management functions. Adjust Cargo dependencies and versions for improved compatibility.
This commit is contained in:
@@ -1,43 +1,53 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { passwords } from "dc-native";
|
||||
|
||||
const APPLICATION_NAME = "Bitwarden Directory Connector";
|
||||
|
||||
export class ElectronRendererSecureStorageService implements StorageService {
|
||||
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
const val = ipcRenderer.sendSync("nativeSecureStorage", {
|
||||
action: "getPassword",
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? "",
|
||||
});
|
||||
return Promise.resolve(val != null ? (JSON.parse(val) as T) : null);
|
||||
return passwords
|
||||
.getPassword(this.buildServiceName(options), key)
|
||||
.then((val: string) => JSON.parse(val) as T)
|
||||
.catch((e: Error): T => {
|
||||
if (e.message === passwords.PASSWORD_NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async has(key: string, options?: StorageOptions): Promise<boolean> {
|
||||
const val = ipcRenderer.sendSync("nativeSecureStorage", {
|
||||
action: "hasPassword",
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? "",
|
||||
});
|
||||
return Promise.resolve(!!val);
|
||||
return (await this.get(key, options)) != null;
|
||||
}
|
||||
|
||||
async save(key: string, obj: any, options?: StorageOptions): Promise<any> {
|
||||
ipcRenderer.sendSync("nativeSecureStorage", {
|
||||
action: "setPassword",
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? "",
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
return Promise.resolve();
|
||||
if (!obj) {
|
||||
return this.remove(key, options);
|
||||
}
|
||||
return passwords.setPassword(
|
||||
this.buildServiceName(options),
|
||||
key,
|
||||
JSON.stringify(obj),
|
||||
);
|
||||
}
|
||||
|
||||
async remove(key: string, options?: StorageOptions): Promise<any> {
|
||||
ipcRenderer.sendSync("nativeSecureStorage", {
|
||||
action: "deletePassword",
|
||||
key: key,
|
||||
keySuffix: options?.keySuffix ?? "",
|
||||
return passwords.deletePassword(this.buildServiceName(options), key).catch((e: Error) => {
|
||||
if (e.message === passwords.PASSWORD_NOT_FOUND) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private buildServiceName(options?: StorageOptions): string {
|
||||
const suffix = options?.keySuffix;
|
||||
if (suffix) {
|
||||
return `${APPLICATION_NAME}_${suffix}`;
|
||||
}
|
||||
return APPLICATION_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
116
native/Cargo.lock
generated
116
native/Cargo.lock
generated
@@ -38,6 +38,15 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
@@ -430,9 +439,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -493,20 +502,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.5.0"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"ctor-proc-macro",
|
||||
"dtor",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-proc-macro"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2"
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -694,21 +697,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301"
|
||||
dependencies = [
|
||||
"dtor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor-proc-macro"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
@@ -1287,9 +1275,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.9.0"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
@@ -1397,33 +1385,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "3.3.0"
|
||||
version = "2.16.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1b74e3dce5230795bb4d2821b941706dee733c7308752507254b0497f39cad7"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
"nohash-hasher",
|
||||
"rustc-hash",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.2.3"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14"
|
||||
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "3.2.5"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7552d5a579b834614bbd496db5109f1b9f1c758f08224b0dee1e408333adf0d0"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"ctor",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1432,22 +1419,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "2.2.0"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6a81ac7486b70f2532a289603340862c06eea5a1e650c1ffeda2ce1238516a"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "3.2.1"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
@@ -1465,12 +1454,6 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
@@ -2090,6 +2073,35 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
@@ -2131,12 +2143,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
|
||||
@@ -12,8 +12,8 @@ name = "dc_native"
|
||||
[dependencies]
|
||||
anyhow = "=1.0.100"
|
||||
desktop_core = { git = "https://github.com/bitwarden/clients", rev = "00cf24972d944638bbd1adc00a0ae3eeabb6eb9a", package = "desktop_core" }
|
||||
napi = { version = "=3.3.0", features = ["async"] }
|
||||
napi-derive = "=3.2.5"
|
||||
napi = { version = "2", features = ["async"] }
|
||||
napi-derive = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
scopeguard = "=1.2.0"
|
||||
@@ -24,4 +24,4 @@ windows = { version = "=0.61.1", features = [
|
||||
] }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "=2.2.3"
|
||||
napi-build = "1"
|
||||
|
||||
@@ -66,4 +66,17 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load dc-native binding`);
|
||||
}
|
||||
|
||||
module.exports = nativeBinding;
|
||||
// Re-export flat native symbols as the `passwords` namespace so all
|
||||
// TypeScript callers (and the existing index.d.ts declarations) work unchanged.
|
||||
module.exports = {
|
||||
passwords: {
|
||||
getPassword: (service, account) => nativeBinding.getPassword(service, account),
|
||||
setPassword: (service, account, password) =>
|
||||
nativeBinding.setPassword(service, account, password),
|
||||
deletePassword: (service, account) => nativeBinding.deletePassword(service, account),
|
||||
isAvailable: () => nativeBinding.isAvailable(),
|
||||
migrateKeytarPassword: (service, account) =>
|
||||
nativeBinding.migrateKeytarPassword(service, account),
|
||||
PASSWORD_NOT_FOUND: "Password not found.",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,68 +1,62 @@
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
|
||||
/// Fetch the stored password from the keychain.
|
||||
/// Throws an Error with message PASSWORD_NOT_FOUND if the password does not exist.
|
||||
#[napi]
|
||||
pub mod passwords {
|
||||
/// The error message returned when a password is not found during retrieval or deletion.
|
||||
#[napi]
|
||||
pub const PASSWORD_NOT_FOUND: &str = desktop_core::password::PASSWORD_NOT_FOUND;
|
||||
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
|
||||
desktop_core::password::get_password(&service, &account)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Fetch the stored password from the keychain.
|
||||
/// Throws an Error with message PASSWORD_NOT_FOUND if the password does not exist.
|
||||
#[napi]
|
||||
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
|
||||
desktop_core::password::get_password(&service, &account)
|
||||
/// Save the password to the keychain. Adds an entry if none exists, otherwise updates it.
|
||||
#[napi]
|
||||
pub async fn set_password(
|
||||
service: String,
|
||||
account: String,
|
||||
password: String,
|
||||
) -> napi::Result<()> {
|
||||
desktop_core::password::set_password(&service, &account, &password)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Delete the stored password from the keychain.
|
||||
/// Throws an Error with message PASSWORD_NOT_FOUND if the password does not exist.
|
||||
#[napi]
|
||||
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
|
||||
desktop_core::password::delete_password(&service, &account)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Check if OS secure storage is available.
|
||||
#[napi]
|
||||
pub async fn is_available() -> napi::Result<bool> {
|
||||
desktop_core::password::is_available()
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Migrate a credential that was stored by keytar (UTF-8 blob) to the new UTF-16 format
|
||||
/// used by desktop_core on Windows. No-ops on non-Windows platforms.
|
||||
///
|
||||
/// Returns true if a migration was performed, false if the credential was already in the
|
||||
/// correct format or does not exist.
|
||||
#[napi]
|
||||
pub async fn migrate_keytar_password(service: String, account: String) -> napi::Result<bool> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
migration::migrate_keytar_password(&service, &account)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Save the password to the keychain. Adds an entry if none exists, otherwise updates it.
|
||||
#[napi]
|
||||
pub async fn set_password(
|
||||
service: String,
|
||||
account: String,
|
||||
password: String,
|
||||
) -> napi::Result<()> {
|
||||
desktop_core::password::set_password(&service, &account, &password)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Delete the stored password from the keychain.
|
||||
/// Throws an Error with message PASSWORD_NOT_FOUND if the password does not exist.
|
||||
#[napi]
|
||||
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
|
||||
desktop_core::password::delete_password(&service, &account)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Check if OS secure storage is available.
|
||||
#[napi]
|
||||
pub async fn is_available() -> napi::Result<bool> {
|
||||
desktop_core::password::is_available()
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Migrate a credential that was stored by keytar (UTF-8 blob) to the new UTF-16 format
|
||||
/// used by desktop_core on Windows. No-ops on non-Windows platforms.
|
||||
///
|
||||
/// Returns true if a migration was performed, false if the credential was already in the
|
||||
/// correct format or does not exist.
|
||||
#[napi]
|
||||
pub async fn migrate_keytar_password(service: String, account: String) -> napi::Result<bool> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
crate::migration::migrate_keytar_password(&service, &account)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _ = (service, account);
|
||||
Ok(false)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _ = (service, account);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,11 @@
|
||||
import { passwords } from "dc-native";
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
// Secure storage is handled directly in the renderer process via dc-native,
|
||||
// so this listener is no longer needed. Kept as a stub to avoid refactoring
|
||||
// the main.ts bootstrap sequence.
|
||||
export class DCCredentialStorageListener {
|
||||
|
||||
constructor(private serviceName: string) {}
|
||||
|
||||
init() {
|
||||
ipcMain.on("nativeSecureStorage", async (event: any, message: any) => {
|
||||
try {
|
||||
let serviceName = this.serviceName;
|
||||
message.keySuffix = "_" + (message.keySuffix ?? "");
|
||||
if (message.keySuffix !== "_") {
|
||||
serviceName += message.keySuffix;
|
||||
}
|
||||
|
||||
let val: string | boolean = null;
|
||||
if (message.action && message.key) {
|
||||
if (message.action === "getPassword") {
|
||||
val = await passwords.getPassword(serviceName, message.key);
|
||||
} else if (message.action === "hasPassword") {
|
||||
const result = await passwords.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;
|
||||
}
|
||||
});
|
||||
// no-op: renderer calls dc-native directly
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,15 @@ export class NativeSecureStorageService implements StorageService {
|
||||
constructor(private serviceName: string) {}
|
||||
|
||||
get<T>(key: string): Promise<T> {
|
||||
return passwords.getPassword(this.serviceName, key).then((val: string) => {
|
||||
return JSON.parse(val) as T;
|
||||
});
|
||||
return passwords
|
||||
.getPassword(this.serviceName, key)
|
||||
.then((val: string) => JSON.parse(val) as T)
|
||||
.catch((e: Error): T => {
|
||||
if (e.message === passwords.PASSWORD_NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
@@ -24,6 +30,11 @@ export class NativeSecureStorageService implements StorageService {
|
||||
}
|
||||
|
||||
remove(key: string): Promise<any> {
|
||||
return passwords.deletePassword(this.serviceName, key);
|
||||
return passwords.deletePassword(this.serviceName, key).catch((e: Error) => {
|
||||
if (e.message === passwords.PASSWORD_NOT_FOUND) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ const main = {
|
||||
],
|
||||
externals: {
|
||||
"electron-reload": "commonjs2 electron-reload",
|
||||
"dc-native": "commonjs2 dc-native",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user