From add2aabf70c7217f043e02b09bdd27803c3253bb Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Sat, 8 Nov 2025 21:38:09 -0600 Subject: [PATCH] Remove some more files --- apps/desktop/desktop_native/napi/index.d.ts | 24 -- apps/desktop/desktop_native/napi/src/lib.rs | 63 --- .../passkey_authenticator_internal/windows.rs | 213 ---------- .../src/assert.rs | 2 +- .../windows_plugin_authenticator/src/ipc.rs | 69 ---- .../windows_plugin_authenticator/src/lib.rs | 22 +- .../windows_plugin_authenticator/src/sync.rs | 193 --------- .../windows_plugin_authenticator/src/types.rs | 107 ----- .../main/autofill/native-autofill.main.ts | 20 +- .../autofill/native-autofill.windows.main.ts | 374 ------------------ 10 files changed, 17 insertions(+), 1070 deletions(-) delete mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs delete mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs delete mode 100644 apps/desktop/src/platform/main/autofill/native-autofill.windows.main.ts diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 4a9aba70759..927fc8c9d0b 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -213,31 +213,7 @@ export declare namespace autofill { } } export declare namespace passkey_authenticator { - export interface PasskeyRequestEvent { - requestType: string - requestJson: string - } - export interface SyncedCredential { - /** base64url-encoded credential ID. */ - credentialId: string - rpId: string - userName: string - /** base64url-encoded user ID. */ - userHandle: string - } - export interface PasskeySyncRequest { - rpId: string - } - export interface PasskeySyncResponse { - credentials: Array - } - export interface PasskeyErrorResponse { - message: string - } export function register(): void - export function onRequest(callback: (error: null | Error, event: PasskeyRequestEvent) => Promise): Promise - export function syncCredentialsToWindows(credentials: Array): void - export function getCredentialsFromWindows(): Array } export declare namespace logging { export const enum LogLevel { diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 8f4efa808ca..870a59f4abb 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -962,75 +962,12 @@ pub mod autofill { #[napi] pub mod passkey_authenticator { - use napi::threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}; - - #[napi(object)] - #[derive(Debug)] - pub struct PasskeyRequestEvent { - pub request_type: String, - pub request_json: String, - } - - #[napi(object)] - #[derive(serde::Serialize, serde::Deserialize)] - pub struct SyncedCredential { - /// base64url-encoded credential ID. - pub credential_id: String, // base64url encoded - pub rp_id: String, - pub user_name: String, - /// base64url-encoded user ID. - pub user_handle: String, // base64url encoded - } - - #[napi(object)] - #[derive(serde::Serialize, serde::Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeySyncRequest { - pub rp_id: String, - } - - #[napi(object)] - #[derive(serde::Serialize, serde::Deserialize)] - #[serde(rename_all = "camelCase")] - - pub struct PasskeySyncResponse { - pub credentials: Vec, - } - - #[napi(object)] - #[derive(serde::Serialize, serde::Deserialize)] - #[serde(rename_all = "camelCase")] - - pub struct PasskeyErrorResponse { - pub message: String, - } - #[napi] pub fn register() -> napi::Result<()> { crate::passkey_authenticator_internal::register().map_err(|e| { napi::Error::from_reason(format!("Passkey registration failed - Error: {e} - {e:?}")) }) } - - #[napi] - pub async fn on_request( - #[napi( - ts_arg_type = "(error: null | Error, event: PasskeyRequestEvent) => Promise" - )] - callback: ThreadsafeFunction, - ) -> napi::Result { - crate::passkey_authenticator_internal::on_request(callback).await - } - - #[napi] - pub fn sync_credentials_to_windows(credentials: Vec) -> napi::Result<()> { - crate::passkey_authenticator_internal::sync_credentials_to_windows(credentials) - } - - #[napi] - pub fn get_credentials_from_windows() -> napi::Result> { - crate::passkey_authenticator_internal::get_credentials_from_windows() - } } #[napi] diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs index 7aa72174b97..4ff51f5bce4 100644 --- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs +++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs @@ -1,220 +1,7 @@ use anyhow::{anyhow, Result}; -use napi::{ - bindgen_prelude::Promise, - threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}, -}; -use serde_json; -use tokio::sync::mpsc; - -// Use the PasskeyRequestEvent from the parent module -pub use crate::passkey_authenticator::{PasskeyRequestEvent, SyncedCredential}; pub fn register() -> Result<()> { windows_plugin_authenticator::register().map_err(|e| anyhow!(e))?; Ok(()) } - -pub async fn on_request( - callback: ThreadsafeFunction, -) -> napi::Result { - let (tx, mut rx) = mpsc::unbounded_channel(); - - // Set the sender in the Windows plugin authenticator - windows_plugin_authenticator::set_request_sender(tx); - - // Spawn task to handle incoming events - tokio::spawn(async move { - while let Some(event) = rx.recv().await { - // The request is already serialized as JSON in the event - let request_json = event.request_json; - - // Get the request type as a string - let request_type = match event.request_type { - windows_plugin_authenticator::RequestType::Assertion => "assertion".to_string(), - windows_plugin_authenticator::RequestType::Registration => { - "registration".to_string() - } - windows_plugin_authenticator::RequestType::Sync => "sync".to_string(), - }; - - let napi_event = PasskeyRequestEvent { - request_type, - request_json, - }; - - // Call the callback asynchronously and capture the return value - let promise_result: Result, napi::Error> = - callback.call_async(Ok(napi_event)).await; - // awai promse - - match promise_result { - Ok(promise_result) => match promise_result.await { - Ok(result) => { - // Parse the JSON response directly back to Rust enum - let response: windows_plugin_authenticator::PasskeyResponse = - match serde_json::from_str(&result) { - Ok(resp) => resp, - Err(e) => windows_plugin_authenticator::PasskeyResponse::Error { - message: format!("JSON parse error: {}\nJSON: {}", e, &result), - }, - }; - let _ = event.response_sender.send(response); - } - Err(e) => { - eprintln!("Error calling passkey callback inner: {}", e); - let _ = event.response_sender.send( - windows_plugin_authenticator::PasskeyResponse::Error { - message: format!("Inner Callback error: {}", e), - }, - ); - } - }, - Err(e) => { - eprintln!("Error calling passkey callback: {}", e); - let _ = event.response_sender.send( - windows_plugin_authenticator::PasskeyResponse::Error { - message: format!("Callback error: {}", e), - }, - ); - } - } - } - }); - - Ok("Event listener registered successfully".to_string()) -} - -impl From for SyncedCredential { - fn from(cred: windows_plugin_authenticator::SyncedCredential) -> Self { - use base64::Engine; - Self { - credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD - .encode(&cred.credential_id), - rp_id: cred.rp_id, - user_name: cred.user_name, - user_handle: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_handle), - } - } -} - -impl From for windows_plugin_authenticator::SyncedCredential { - fn from(cred: SyncedCredential) -> Self { - use base64::Engine; - Self { - credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(&cred.credential_id) - .unwrap_or_default(), - rp_id: cred.rp_id, - user_name: cred.user_name, - user_handle: base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(&cred.user_handle) - .unwrap_or_default(), - } - } -} - -pub fn sync_credentials_to_windows(credentials: Vec) -> napi::Result<()> { - const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; - - log::info!( - "[NAPI] sync_credentials_to_windows called with {} credentials", - credentials.len() - ); - - // Log each credential being synced (with truncated IDs for security) - for (i, cred) in credentials.iter().enumerate() { - let truncated_cred_id = if cred.credential_id.len() > 16 { - format!("{}...", &cred.credential_id[..16]) - } else { - cred.credential_id.clone() - }; - let truncated_user_id = if cred.user_handle.len() > 16 { - format!("{}...", &cred.user_handle[..16]) - } else { - cred.user_handle.clone() - }; - log::info!( - "[NAPI] Credential {}: RP={}, User={}, CredID={}, UserID={}", - i + 1, - cred.rp_id, - cred.user_name, - truncated_cred_id, - truncated_user_id - ); - } - - // Convert NAPI types to internal types using From trait - let internal_credentials: Vec = - credentials.into_iter().map(|cred| cred.into()).collect(); - - log::info!( - "[NAPI] Calling Windows Plugin Authenticator sync with CLSID: {}", - PLUGIN_CLSID - ); - let result = windows_plugin_authenticator::sync_credentials_to_windows( - internal_credentials, - PLUGIN_CLSID, - ); - - match &result { - Ok(()) => log::info!("[NAPI] sync_credentials_to_windows completed successfully"), - Err(e) => log::error!("[NAPI] sync_credentials_to_windows failed: {}", e), - } - - result.map_err(|e| napi::Error::from_reason(format!("Sync credentials failed: {}", e))) -} - -pub fn get_credentials_from_windows() -> napi::Result> { - const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; - - log::info!( - "[NAPI] get_credentials_from_windows called with CLSID: {}", - PLUGIN_CLSID - ); - - let result = windows_plugin_authenticator::get_credentials_from_windows(PLUGIN_CLSID); - - let internal_credentials = match &result { - Ok(creds) => { - log::info!("[NAPI] Retrieved {} credentials from Windows", creds.len()); - result - .map_err(|e| napi::Error::from_reason(format!("Get credentials failed: {}", e)))? - } - Err(e) => { - log::error!("[NAPI] get_credentials_from_windows failed: {}", e); - return Err(napi::Error::from_reason(format!( - "Get credentials failed: {}", - e - ))); - } - }; - - // Convert internal types to NAPI types - let napi_credentials: Vec = internal_credentials - .into_iter() - .enumerate() - .map(|(i, cred)| { - let result_cred: SyncedCredential = cred.into(); - let truncated_cred_id = if result_cred.credential_id.len() > 16 { - format!("{}...", &result_cred.credential_id[..16]) - } else { - result_cred.credential_id.clone() - }; - log::info!( - "[NAPI] Retrieved credential {}: RP={}, User={}, CredID={}", - i + 1, - result_cred.rp_id, - result_cred.user_name, - truncated_cred_id - ); - result_cred - }) - .collect(); - - log::info!( - "[NAPI] get_credentials_from_windows completed successfully, returning {} credentials", - napi_credentials.len() - ); - Ok(napi_credentials) -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs index 610a319380d..dd0825fa4c3 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -385,7 +385,7 @@ pub unsafe fn plugin_get_assertion( passkey_response.user_handle, ) .map_err(|err| { - format!("Failed to create WebAuthn assertion response: {err}"); + tracing::error!("Failed to create WebAuthn assertion response: {err}"); HRESULT(-1) })?; tracing::debug!("Successfully created WebAuthn assertion response"); diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs deleted file mode 100644 index 3b1bd7788ba..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Mutex; -use tokio::sync::{mpsc, oneshot}; - -use crate::types::*; -use crate::util::debug_log; - -/// Global channel sender for request notifications -static REQUEST_SENDER: Mutex>> = Mutex::new(None); - -/// Sets the channel sender for request notifications -pub fn set_request_sender(sender: mpsc::UnboundedSender) { - match REQUEST_SENDER.lock() { - Ok(mut tx) => { - *tx = Some(sender); - debug_log("Passkey request callback registered"); - } - Err(e) => { - debug_log(&format!("Failed to register passkey callback: {:?}", e)); - } - } -} - -/// Sends a passkey request and waits for response -pub fn send_passkey_request( - request_type: RequestType, - request_json: String, - rpid: &str, -) -> Option { - let request_desc = match &request_type { - RequestType::Assertion => format!("assertion request for {}", rpid), - RequestType::Registration => format!("registration request for {}", rpid), - RequestType::Sync => format!("sync request for {}", rpid), - }; - - debug_log(&format!("Passkey {}", request_desc)); - - if let Ok(tx_guard) = REQUEST_SENDER.lock() { - if let Some(sender) = tx_guard.as_ref() { - let (response_tx, response_rx) = oneshot::channel(); - let event = RequestEvent { - request_type, - request_json, - response_sender: response_tx, - }; - - if let Ok(()) = sender.send(event) { - // Wait for response from TypeScript callback - match response_rx.blocking_recv() { - Ok(response) => { - debug_log(&format!("Received callback response {:?}", response)); - Some(response) - } - Err(_) => { - debug_log("No response from callback"); - None - } - } - } else { - debug_log("Failed to send event to callback"); - None - } - } else { - debug_log("No callback registered for passkey requests"); - None - } - } else { - None - } -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index a5a69ea384e..fe217379d45 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -7,10 +7,8 @@ mod assert; mod com_buffer; mod com_provider; mod com_registration; -mod ipc; mod ipc2; mod make_credential; -mod sync; mod types; mod util; mod webauthn; @@ -18,33 +16,23 @@ mod webauthn; // Re-export main functionality pub use assert::WindowsAssertionRequest; pub use com_registration::{add_authenticator, initialize_com_library, register_com_library}; -pub use ipc::{send_passkey_request, set_request_sender}; pub use make_credential::WindowsRegistrationRequest; -pub use sync::{get_credentials_from_windows, send_sync_request, sync_credentials_to_windows}; -pub use types::{ - PasskeyRequest, PasskeyResponse, RequestEvent, RequestType, SyncedCredential, - UserVerificationRequirement, -}; - -use crate::util::debug_log; +pub use types::UserVerificationRequirement; /// Handles initialization and registration for the Bitwarden desktop app as a /// For now, also adds the authenticator pub fn register() -> std::result::Result<(), String> { // TODO: Can we spawn a new named thread for debugging? - debug_log("register() called..."); + tracing::debug!("register() called..."); let r = com_registration::initialize_com_library(); - debug_log(&format!("Initialized the com library: {:?}", r)); + tracing::debug!("Initialized the com library: {:?}", r); let r = com_registration::register_com_library(); - debug_log(&format!("Registered the com library: {:?}", r)); + tracing::debug!("Registered the com library: {:?}", r); let r = com_registration::add_authenticator(); - debug_log(&format!("Added the authenticator: {:?}", r)); + tracing::debug!("Added the authenticator: {:?}", r); Ok(()) } - -/// This sets up IPC so the plugin can request credentials from Electron. -fn setup_ipc() {} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs deleted file mode 100644 index 6aa75231bb6..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/sync.rs +++ /dev/null @@ -1,193 +0,0 @@ -use hex; -use serde_json; - -use crate::com_registration::parse_clsid_to_guid_str; -use crate::ipc::send_passkey_request; -use crate::types::*; -use crate::util::debug_log; -use crate::webauthn::*; - -/// Helper for sync requests - requests credentials from Electron for a specific RP ID -pub fn send_sync_request(rpid: &str) -> Option { - debug_log(&format!( - "[SYNC] send_sync_request called for RP ID: {}", - rpid - )); - - let request = PasskeySyncRequest { - rp_id: rpid.to_string(), - }; - - debug_log(&format!("[SYNC] Created sync request for RP ID: {}", rpid)); - - match serde_json::to_string(&request) { - Ok(request_json) => { - debug_log(&format!( - "[SYNC] Serialized sync request to JSON: {}", - request_json - )); - debug_log(&format!("[SYNC] Sending sync request to Electron via IPC")); - let response = send_passkey_request(RequestType::Sync, request_json, rpid); - match &response { - Some(resp) => debug_log(&format!( - "[SYNC] Received response from Electron: {:?}", - resp - )), - None => debug_log("[SYNC] No response received from Electron"), - } - response - } - Err(e) => { - debug_log(&format!( - "[SYNC] ERROR: Failed to serialize sync request: {}", - e - )); - None - } - } -} - -/// Initiates credential sync from Electron to Windows - called when Electron wants to push credentials to Windows -pub fn sync_credentials_to_windows( - credentials: Vec, - plugin_clsid: &str, -) -> Result<(), String> { - debug_log(&format!( - "[SYNC_TO_WIN] sync_credentials_to_windows called with {} credentials for plugin CLSID: {}", - credentials.len(), - plugin_clsid - )); - - // Parse CLSID string to GUID - let clsid_guid = parse_clsid_to_guid_str(plugin_clsid) - .map_err(|e| format!("Failed to parse CLSID: {}", e))?; - - if credentials.is_empty() { - debug_log("[SYNC_TO_WIN] No credentials to sync, proceeding with empty sync"); - } - - // Convert Bitwarden credentials to Windows credential details - let mut win_credentials = Vec::new(); - - for (i, cred) in credentials.iter().enumerate() { - let truncated_cred_id = if cred.credential_id.len() > 16 { - format!("{}...", hex::encode(&cred.credential_id[..16])) - } else { - hex::encode(&cred.credential_id) - }; - let truncated_user_id = if cred.user_handle.len() > 16 { - format!("{}...", hex::encode(&cred.user_handle[..16])) - } else { - hex::encode(&cred.user_handle) - }; - - debug_log(&format!("[SYNC_TO_WIN] Converting credential {}: RP ID: {}, User: {}, Credential ID: {} ({} bytes), User ID: {} ({} bytes)", - i + 1, cred.rp_id, cred.user_name, truncated_cred_id, cred.credential_id.len(), truncated_user_id, cred.user_handle.len())); - - let win_cred = WebAuthnPluginCredentialDetails::create_from_bytes( - cred.credential_id.clone(), // Pass raw bytes - cred.rp_id.clone(), - cred.rp_id.clone(), // Use RP ID as friendly name for now - cred.user_handle.clone(), // Pass raw bytes - cred.user_name.clone(), - cred.user_name.clone(), // Use user name as display name for now - ); - - win_credentials.push(win_cred); - debug_log(&format!( - "[SYNC_TO_WIN] Converted credential {} to Windows format", - i + 1 - )); - } - - // First try to remove all existing credentials for this plugin - debug_log("Attempting to remove all existing credentials before sync..."); - match remove_all_credentials(clsid_guid) { - Ok(()) => { - debug_log("Successfully removed existing credentials"); - } - Err(e) if e.contains("can't be loaded") => { - debug_log("RemoveAllCredentials function not available - this is expected for some Windows versions"); - // This is fine, the function might not exist in all versions - } - Err(e) => { - debug_log(&format!( - "Warning: Failed to remove existing credentials: {}", - e - )); - // Continue anyway, as this might be the first sync or an older Windows version - } - } - - // Add the new credentials (only if we have any) - if credentials.is_empty() { - debug_log("No credentials to add to Windows - sync completed successfully"); - Ok(()) - } else { - debug_log("Adding new credentials to Windows..."); - match add_credentials(clsid_guid, win_credentials) { - Ok(()) => { - debug_log("Successfully synced credentials to Windows"); - Ok(()) - } - Err(e) => { - debug_log(&format!( - "ERROR: Failed to add credentials to Windows: {}", - e - )); - Err(e) - } - } - } -} - -/// Gets all credentials from Windows for a specific plugin - used when Electron requests current state -pub fn get_credentials_from_windows(plugin_clsid: &str) -> Result, String> { - debug_log(&format!( - "Getting all credentials from Windows for plugin CLSID: {}", - plugin_clsid - )); - - // Parse CLSID string to GUID - let clsid_guid = parse_clsid_to_guid_str(plugin_clsid) - .map_err(|e| format!("Failed to parse CLSID: {}", e))?; - - match get_all_credentials(clsid_guid) { - Ok(credentials) => { - debug_log(&format!( - "Retrieved {} credentials from Windows", - credentials.len() - )); - - let mut bitwarden_credentials = Vec::new(); - - // Convert Windows credentials to Bitwarden format - for cred in credentials { - let synced_cred = SyncedCredential { - credential_id: cred.credential_id, - rp_id: cred.rpid, - user_name: cred.user_name, - user_handle: cred.user_id, - }; - - debug_log(&format!( - "Converted Windows credential: RP ID: {}, User: {}, Credential ID: {} bytes", - synced_cred.rp_id, - synced_cred.user_name, - synced_cred.credential_id.len() - )); - - bitwarden_credentials.push(synced_cred); - } - - Ok(bitwarden_credentials) - } - Err(e) => { - debug_log(&format!( - "ERROR: Failed to get credentials from Windows: {}", - e - )); - Err(e) - } - } -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs index 4de3eb44900..f64a73478f8 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs @@ -1,6 +1,3 @@ -use serde::{Deserialize, Serialize}; -use tokio::sync::oneshot; - /// User verification requirement as defined by WebAuthn spec #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "lowercase")] @@ -36,107 +33,3 @@ impl Into for UserVerificationRequirement { } } } - -/// IDENTICAL to napi/lib.rs/PasskeyAssertionRequest -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PasskeyAssertionRequest { - pub rp_id: String, - pub client_data_hash: Vec, - pub user_verification: UserVerificationRequirement, - pub allowed_credentials: Vec>, - pub window_xy: Position, - - pub transaction_id: String, -} - -// Identical to napi/lib.rs/Position -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Position { - pub x: i32, - pub y: i32, -} - -/// IDENTICAL to napi/lib.rs/PasskeyRegistrationRequest -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PasskeyRegistrationRequest { - pub rp_id: String, - pub user_name: String, - pub user_handle: Vec, - pub client_data_hash: Vec, - pub user_verification: UserVerificationRequirement, - pub supported_algorithms: Vec, - pub window_xy: Position, - pub excluded_credentials: Vec>, - - pub transaction_id: String, -} - -/// Sync request structure -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PasskeySyncRequest { - pub rp_id: String, -} - -/// Union type for different request types -#[derive(Debug, Clone)] -pub enum PasskeyRequest { - AssertionRequest(PasskeyAssertionRequest), - RegistrationRequest(PasskeyRegistrationRequest), - SyncRequest(PasskeySyncRequest), -} - -/// Response types for different operations - kept as tagged enum for JSON compatibility -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum PasskeyResponse { - #[serde(rename = "assertion_response", rename_all = "camelCase")] - AssertionResponse { - rp_id: String, - user_handle: Vec, - signature: Vec, - client_data_hash: Vec, - authenticator_data: Vec, - credential_id: Vec, - }, - #[serde(rename = "registration_response", rename_all = "camelCase")] - RegistrationResponse { - rp_id: String, - client_data_hash: Vec, - credential_id: Vec, - attestation_object: Vec, - }, - #[serde(rename = "sync_response", rename_all = "camelCase")] - SyncResponse { credentials: Vec }, - #[serde(rename = "error", rename_all = "camelCase")] - Error { message: String }, -} - -/// Credential data for sync operations -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SyncedCredential { - pub credential_id: Vec, - pub rp_id: String, - pub user_name: String, - pub user_handle: Vec, -} - -/// Request type enumeration for type discrimination -#[derive(Debug, Clone)] -pub enum RequestType { - Assertion, - Registration, - Sync, -} - -/// Internal request event with response channel and serializable request data -#[derive(Debug)] -pub struct RequestEvent { - pub request_type: RequestType, - pub request_json: String, - pub response_sender: oneshot::Sender, -} diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts index 67ebadd6dd5..703b15fb1b8 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -1,12 +1,11 @@ import { ipcMain } from "electron"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { autofill } from "@bitwarden/desktop-napi"; +import { autofill, passkey_authenticator } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../../main/window.main"; import { CommandDefinition } from "./command"; -import { NativeAutofillWindowsMain } from "./native-autofill.windows.main"; type BufferedMessage = { channel: string; @@ -26,15 +25,10 @@ export class NativeAutofillMain { private messageBuffer: BufferedMessage[] = []; private listenerReady = false; - private windowsIpc: NativeAutofillWindowsMain | null - constructor( private logService: LogService, private windowMain: WindowMain, ) { - if (process.platform === "win32") { - this.windowsIpc = new NativeAutofillWindowsMain(this.logService, this.windowMain); - } } /** @@ -68,8 +62,16 @@ export class NativeAutofillMain { async init() { if (process.platform === "win32") { - this.windowsIpc.initWindows(); - // this.windowsIpc.setupWindowsRendererIPCHandlers(); + try { + passkey_authenticator.register(); + } + catch (err) { + this.logService.error("Failed to register windows passkey plugin:", err) + return JSON.stringify({ + "type": "error", + "message": "Failed to register windows passkey plugin" + }) + } } ipcMain.handle( diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.windows.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.windows.main.ts deleted file mode 100644 index 97071740901..00000000000 --- a/apps/desktop/src/platform/main/autofill/native-autofill.windows.main.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { ipcMain } from "electron"; - -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { autofill, passkey_authenticator } from "@bitwarden/desktop-napi"; - -import { WindowMain } from "../../../main/window.main"; - -import { CommandDefinition } from "./command"; -import type { RunCommandParams, RunCommandResult } from "./native-autofill.main"; -import { NativeAutofillFido2Credential, NativeAutofillSyncParams } from "./sync.command"; - -export class NativeAutofillWindowsMain { - private pendingPasskeyRequests = new Map void>(); - - constructor( - private logService: LogService, - private windowMain: WindowMain, - ) {} - - initWindows() { - try { - passkey_authenticator.register(); - } - catch (err) { - this.logService.error("Failed to register windows passkey plugin:", err) - return JSON.stringify({ - "type": "error", - "message": "Failed to register windows passkey plugin" - }) - } - /* - void passkey_authenticator.onRequest(async (error, event) => { - this.logService.info("Passkey request received:", { error, event }); - - try { - const request = JSON.parse(event.requestJson); - this.logService.info("Parsed passkey request:", { type: event.requestType, request }); - - // Handle different request types based on the requestType field - switch (event.requestType) { - case "assertion": - return await this.handleAssertionRequest(request); - case "registration": - return await this.handleRegistrationRequest(request); - case "sync": - return await this.handleSyncRequest(request); - default: - this.logService.error("Unknown passkey request type:", event.requestType); - return JSON.stringify({ - type: "error", - message: `Unknown request type: ${event.requestType}`, - }); - } - } catch (parseError) { - this.logService.error("Failed to parse passkey request:", parseError); - return JSON.stringify({ - type: "error", - message: "Failed to parse request JSON", - }); - } - }); - */ - } - - private async handleAssertionRequest(request: autofill.PasskeyAssertionRequest): Promise { - this.logService.info("Handling assertion request for rpId:", request.rpId); - - try { - // Generate unique identifiers for tracking this request - const clientId = Date.now(); - const sequenceNumber = Math.floor(Math.random() * 1000000); - - // Send request and wait for response - const response = await this.sendAndOptionallyWait( - "autofill.passkeyAssertion", - { - clientId, - sequenceNumber, - request: request, - }, - { waitForResponse: true, timeout: 60000 }, - ); - - if (response) { - // Convert the response to the format expected by the NAPI bridge - return JSON.stringify({ - type: "assertion_response", - ...response, - }); - } else { - return JSON.stringify({ - type: "error", - message: "No response received from renderer", - }); - } - } catch (error) { - this.logService.error("Error in assertion request:", error); - return JSON.stringify({ - type: "error", - message: `Assertion request failed: ${error.message}`, - }); - } - } - - private async handleRegistrationRequest( - request: autofill.PasskeyRegistrationRequest, - ): Promise { - this.logService.info("Handling registration request for rpId:", request.rpId); - - try { - // Generate unique identifiers for tracking this request - const clientId = Date.now(); - const sequenceNumber = Math.floor(Math.random() * 1000000); - - // Send request and wait for response - const response = await this.sendAndOptionallyWait( - "autofill.passkeyRegistration", - { - clientId, - sequenceNumber, - request: request, - }, - { waitForResponse: true, timeout: 60000 }, - ); - - this.logService.info("Received response for registration request:", response); - - if (response) { - // Convert the response to the format expected by the NAPI bridge - return JSON.stringify({ - type: "registration_response", - ...response, - }); - } else { - return JSON.stringify({ - type: "error", - message: "No response received from renderer", - }); - } - } catch (error) { - this.logService.error("Error in registration request:", error); - return JSON.stringify({ - type: "error", - message: `Registration request failed: ${error.message}`, - }); - } - } - - private async handleSyncRequest( - request: passkey_authenticator.PasskeySyncRequest, - ): Promise { - this.logService.info("Handling sync request for rpId:", request.rpId); - - try { - // Generate unique identifiers for tracking this request - const clientId = Date.now(); - const sequenceNumber = Math.floor(Math.random() * 1000000); - - // Send sync request and wait for response - const response = await this.sendAndOptionallyWait( - "autofill.passkeySync", - { - clientId, - sequenceNumber, - request: { rpId: request.rpId }, - }, - { waitForResponse: true, timeout: 60000 }, - ); - - this.logService.info("Received response for sync request:", response); - - if (response && response.credentials) { - // Convert the response to the format expected by the NAPI bridge - return JSON.stringify({ - type: "sync_response", - credentials: response.credentials, - }); - } else { - return JSON.stringify({ - type: "error", - message: "No credentials received from renderer", - }); - } - } catch (error) { - this.logService.error("Error in sync request:", error); - return JSON.stringify({ - type: "error", - message: `Sync request failed: ${error.message}`, - }); - } - } - - /** - * Wrapper for webContents.send that optionally waits for a response - * @param channel The IPC channel to send to - * @param data The data to send - * @param options Optional configuration - * @returns Promise that resolves with the response if waitForResponse is true - */ - private async sendAndOptionallyWait( - channel: string, - data: any, - options?: { waitForResponse?: boolean; timeout?: number }, - ): Promise { - if (!options?.waitForResponse) { - // Just send without waiting for response (existing behavior) - this.logService.info(`Sending fire-and-forget message to ${channel}`); - this.windowMain.win.webContents.send(channel, data); - return; - } - - // Use clientId and sequenceNumber as the tracking key - const trackingKey = `${data.clientId}_${data.sequenceNumber}`; - const timeout = options.timeout || 30000; // 30 second default timeout - - this.logService.info(`Sending awaitable request ${trackingKey} to ${channel}`, { data }); - - return new Promise((resolve, reject) => { - // Set up timeout - const timeoutId = setTimeout(() => { - this.logService.warning(`Request ${trackingKey} timed out after ${timeout}ms`); - this.pendingPasskeyRequests.delete(trackingKey); - reject(new Error(`Request timeout after ${timeout}ms`)); - }, timeout); - - // Store the resolver - this.pendingPasskeyRequests.set(trackingKey, (response: T) => { - this.logService.info(`Request ${trackingKey} resolved with response:`, response); - clearTimeout(timeoutId); - this.pendingPasskeyRequests.delete(trackingKey); - resolve(response); - }); - - this.logService.info( - `Stored resolver for request ${trackingKey}, total pending: ${this.pendingPasskeyRequests.size}`, - ); - - // Send the request - this.windowMain.win.webContents.send(channel, data); - }); - } - - /** - * These Handlers react to requests coming from the electron RENDERER process. - */ - setupWindowsRendererIPCHandlers() { - // This will run a command in windows and return the result. - // Only the "sync" command is supported for now. - ipcMain.handle( - "autofill.runCommand", - ( - _event: any, - params: RunCommandParams, - ): Promise> => { - this.logService.debug("Received event (windows):", "autofill.runCommand", params) - return this.runCommand(params); - }, - ); - - ipcMain.on("autofill.completePasskeySync", (event, data) => { - this.logService.warning("autofill.completePasskeySync", data); - const { clientId, sequenceNumber, response } = data; - - // Handle awaitable passkey requests using clientId and sequenceNumber - if (clientId !== undefined && sequenceNumber !== undefined) { - const trackingKey = `${clientId}_${sequenceNumber}`; - this.handlePasskeyResponse(trackingKey, response); - } - }); - - ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { - this.logService.warning("autofill.completePasskeyRegistration", data); - const { clientId, sequenceNumber, response } = data; - - // Handle awaitable passkey requests using clientId and sequenceNumber - if (clientId !== undefined && sequenceNumber !== undefined) { - const trackingKey = `${clientId}_${sequenceNumber}`; - this.handlePasskeyResponse(trackingKey, response); - } - }); - - ipcMain.on("autofill.completePasskeyAssertion", (event, data) => { - this.logService.warning("autofill.completePasskeyAssertion", data); - const { clientId, sequenceNumber, response } = data; - - // Handle awaitable passkey requests using clientId and sequenceNumber - if (clientId !== undefined && sequenceNumber !== undefined) { - const trackingKey = `${clientId}_${sequenceNumber}`; - this.handlePasskeyResponse(trackingKey, response); - } - }); - - ipcMain.on("autofill.completeError", (event, data) => { - this.logService.warning("autofill.completeError", data); - const { clientId, sequenceNumber, error } = data; - - // Handle awaitable passkey requests using clientId and sequenceNumber - if (clientId !== undefined && sequenceNumber !== undefined) { - const trackingKey = `${clientId}_${sequenceNumber}`; - this.handlePasskeyResponse(trackingKey, { error: String(error) }); - } - }); - } - - private handlePasskeyResponse(trackingKey: string, response: any): void { - this.logService.info("Received passkey response for tracking key:", trackingKey, response); - - if (!trackingKey) { - this.logService.error("Response missing tracking key:", response); - return; - } - - this.logService.info(`Looking for pending request with tracking key: ${trackingKey}`); - this.logService.info( - `Current pending requests: ${Array.from(this.pendingPasskeyRequests.keys())}`, - ); - - const resolver = this.pendingPasskeyRequests.get(trackingKey); - if (resolver) { - this.logService.info("Found resolver, calling with response data:", response); - resolver(response); - } else { - this.logService.warning("No pending request found for tracking key:", trackingKey); - } - } - - private async runCommand( - command: RunCommandParams, - ): Promise> { - try { - this.logService.info("Windows runCommand (sync) is called with command:", command); - - if (command.namespace !== "autofill") { - this.logService.error("Invalid command namespace:", command.namespace); - return { type: "error", error: "Invalid command namespace" } as RunCommandResult; - } - - if (command.command !== "sync") { - this.logService.error("Invalid command:", command.command); - return { type: "error", error: "Invalid command" } as RunCommandResult; - } - - const syncParams = command.params as NativeAutofillSyncParams; - // Only sync FIDO2 credentials - const fido2Credentials = syncParams.credentials.filter((c) => c.type === "fido2"); - - const mappedCredentials = fido2Credentials.map((cred: NativeAutofillFido2Credential) => { - const credential: passkey_authenticator.SyncedCredential = { - credentialId: cred.credentialId, - rpId: cred.rpId, - userName: cred.userName, - userHandle: cred.userHandle, - }; - this.logService.info("Mapped credential:", credential); - return credential; - }); - - this.logService.info("Syncing passkeys to Windows:", mappedCredentials); - - passkey_authenticator.syncCredentialsToWindows(mappedCredentials); - - const res = { value: { added: mappedCredentials.length } } as RunCommandResult; - return res; - } catch (e) { - this.logService.error(`Error running autofill command '${command.command}':`, e); - - if (e instanceof Error) { - return { type: "error", error: e.stack ?? String(e) } as RunCommandResult; - } - - return { type: "error", error: String(e) } as RunCommandResult; - } - } -}