diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index b973c19d3b1..5558e641da8 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -3,10 +3,6 @@ /* auto-generated by NAPI-RS */ -export interface PasskeyRequestEvent { - requestType: string - requestJson: string -} export declare namespace passwords { /** Fetch the stored password from the keychain. */ export function getPassword(service: string, account: string): Promise @@ -190,6 +186,10 @@ export declare namespace crypto { export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise } export declare namespace passkey_authenticator { + export interface PasskeyRequestEvent { + requestType: string + requestJson: string + } export interface SyncedCredential { credentialId: string rpId: string diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 95ec030fbc3..bb404fa211c 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -810,13 +810,14 @@ pub mod crypto { #[napi] pub mod passkey_authenticator { - use napi::threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, - }; - use serde_json; + use napi::threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}; - // Re-export the platform-specific types - pub use crate::passkey_authenticator_internal::PasskeyRequestEvent; + #[napi(object)] + #[derive(Debug)] + pub struct PasskeyRequestEvent { + pub request_type: String, + pub request_json: String, + } #[napi(object)] #[derive(serde::Serialize, serde::Deserialize)] @@ -827,34 +828,6 @@ pub mod passkey_authenticator { pub user_id: String, // base64url encoded } - 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_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_id), - } - } - } - - 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_id: base64::engine::general_purpose::URL_SAFE_NO_PAD - .decode(&cred.user_id) - .unwrap_or_default(), - } - } - } - #[napi(object)] #[derive(serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] @@ -923,7 +896,6 @@ pub mod passkey_authenticator { pub message: String, } - #[napi] pub fn register() -> napi::Result<()> { crate::passkey_authenticator_internal::register().map_err(|e| { @@ -933,7 +905,9 @@ pub mod passkey_authenticator { #[napi] pub async fn on_request( - #[napi(ts_arg_type = "(error: null | Error, event: PasskeyRequestEvent) => Promise")] + #[napi( + ts_arg_type = "(error: null | Error, event: PasskeyRequestEvent) => Promise" + )] callback: ThreadsafeFunction, ) -> napi::Result { crate::passkey_authenticator_internal::on_request(callback).await @@ -941,81 +915,12 @@ pub mod passkey_authenticator { #[napi] 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_id.len() > 16 { - format!("{}...", &cred.user_id[..16]) - } else { - cred.user_id.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))) + crate::passkey_authenticator_internal::sync_credentials_to_windows(credentials) } #[napi] 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 using From trait - 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) + crate::passkey_authenticator_internal::get_credentials_from_windows() } } diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs index 9cd748ba185..96908105f12 100644 --- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs +++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs @@ -1,15 +1,8 @@ use anyhow::{bail, Result}; -use napi::threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, -}; +use napi::threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}; -#[napi(object)] -#[derive(Debug)] -pub struct PasskeyRequestEvent { - pub operation: String, - pub rpid: String, - pub transaction_id: String, -} +// Use the PasskeyRequestEvent from the parent module +pub use crate::passkey_authenticator::{PasskeyRequestEvent, SyncedCredential}; pub fn register() -> Result<()> { bail!("Not implemented") @@ -18,5 +11,19 @@ pub fn register() -> Result<()> { pub async fn on_request( _callback: ThreadsafeFunction, ) -> napi::Result { - Err(napi::Error::from_reason("Passkey authenticator is not supported on this platform".to_string())) + Err(napi::Error::from_reason( + "Passkey authenticator is not supported on this platform", + )) +} + +pub fn sync_credentials_to_windows(_credentials: Vec) -> napi::Result<()> { + Err(napi::Error::from_reason( + "Windows credential sync not supported on this platform", + )) +} + +pub fn get_credentials_from_windows() -> napi::Result> { + Err(napi::Error::from_reason( + "Windows credential retrieval not supported on this platform", + )) } 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 13bc8f71b32..85a77fef04b 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,18 +1,17 @@ use anyhow::{anyhow, Result}; -use napi::{bindgen_prelude::Promise, threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, -}, JsObject}; -use tokio::sync::mpsc; +use napi::{ + bindgen_prelude::Promise, + threadsafe_function::{ + ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, + }, + JsObject, +}; use serde_json; +use tokio::sync::mpsc; use windows_plugin_authenticator::util; -// Simple wrapper for passing JSON strings to TypeScript -#[napi(object)] -#[derive(Debug)] -pub struct PasskeyRequestEvent { - pub request_type: String, - pub request_json: String, -} +// 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))?; @@ -24,59 +23,203 @@ 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::Registration => { + "registration".to_string() + } windows_plugin_authenticator::RequestType::Sync => "sync".to_string(), }; - - let napi_event = PasskeyRequestEvent { request_type, request_json }; - + + 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; + 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) => { - util::message(&format!("CALLBACK COMPLETED WITH RESPONSE: {}", 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: {}", e), - } - }; - 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), - }); - - } - } + util::message(&format!("CALLBACK COMPLETED WITH RESPONSE: {}", 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: {}", e), + }, + }; + 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), - }); + 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_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_id), + } + } +} + +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_id: base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(&cred.user_id) + .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_id.len() > 16 { + format!("{}...", &cred.user_id[..16]) + } else { + cred.user_id.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) +}