1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-01 17:23:37 +00:00

Remove some more files

This commit is contained in:
Isaiah Inuwa
2025-11-08 21:38:09 -06:00
parent 1f18d92f7a
commit add2aabf70
10 changed files with 17 additions and 1070 deletions

View File

@@ -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<SyncedCredential>
}
export interface PasskeyErrorResponse {
message: string
}
export function register(): void
export function onRequest(callback: (error: null | Error, event: PasskeyRequestEvent) => Promise<string>): Promise<string>
export function syncCredentialsToWindows(credentials: Array<SyncedCredential>): void
export function getCredentialsFromWindows(): Array<SyncedCredential>
}
export declare namespace logging {
export const enum LogLevel {

View File

@@ -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<SyncedCredential>,
}
#[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<string>"
)]
callback: ThreadsafeFunction<PasskeyRequestEvent, CalleeHandled>,
) -> napi::Result<String> {
crate::passkey_authenticator_internal::on_request(callback).await
}
#[napi]
pub fn sync_credentials_to_windows(credentials: Vec<SyncedCredential>) -> napi::Result<()> {
crate::passkey_authenticator_internal::sync_credentials_to_windows(credentials)
}
#[napi]
pub fn get_credentials_from_windows() -> napi::Result<Vec<SyncedCredential>> {
crate::passkey_authenticator_internal::get_credentials_from_windows()
}
}
#[napi]

View File

@@ -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<PasskeyRequestEvent, CalleeHandled>,
) -> napi::Result<String> {
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<Promise<String>, 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<windows_plugin_authenticator::SyncedCredential> 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<SyncedCredential> 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<SyncedCredential>) -> 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<windows_plugin_authenticator::SyncedCredential> =
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<Vec<SyncedCredential>> {
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<SyncedCredential> = 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)
}

View File

@@ -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");

View File

@@ -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<Option<mpsc::UnboundedSender<RequestEvent>>> = Mutex::new(None);
/// Sets the channel sender for request notifications
pub fn set_request_sender(sender: mpsc::UnboundedSender<RequestEvent>) {
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<PasskeyResponse> {
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
}
}

View File

@@ -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() {}

View File

@@ -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<PasskeyResponse> {
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<SyncedCredential>,
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<Vec<SyncedCredential>, 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)
}
}
}

View File

@@ -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<String> 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<u8>,
pub user_verification: UserVerificationRequirement,
pub allowed_credentials: Vec<Vec<u8>>,
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<u8>,
pub client_data_hash: Vec<u8>,
pub user_verification: UserVerificationRequirement,
pub supported_algorithms: Vec<i32>,
pub window_xy: Position,
pub excluded_credentials: Vec<Vec<u8>>,
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<u8>,
signature: Vec<u8>,
client_data_hash: Vec<u8>,
authenticator_data: Vec<u8>,
credential_id: Vec<u8>,
},
#[serde(rename = "registration_response", rename_all = "camelCase")]
RegistrationResponse {
rp_id: String,
client_data_hash: Vec<u8>,
credential_id: Vec<u8>,
attestation_object: Vec<u8>,
},
#[serde(rename = "sync_response", rename_all = "camelCase")]
SyncResponse { credentials: Vec<SyncedCredential> },
#[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<u8>,
pub rp_id: String,
pub user_name: String,
pub user_handle: Vec<u8>,
}
/// 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<PasskeyResponse>,
}

View File

@@ -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(

View File

@@ -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<string, (response: any) => 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<string> {
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.PasskeyAssertionResponse>(
"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<string> {
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.PasskeyRegistrationResponse>(
"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<string> {
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<passkey_authenticator.PasskeySyncResponse>(
"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<T = any>(
channel: string,
data: any,
options?: { waitForResponse?: boolean; timeout?: number },
): Promise<T | void> {
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<T>((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",
<C extends CommandDefinition>(
_event: any,
params: RunCommandParams<C>,
): Promise<RunCommandResult<C>> => {
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<C extends CommandDefinition>(
command: RunCommandParams<C>,
): Promise<RunCommandResult<C>> {
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<C>;
}
if (command.command !== "sync") {
this.logService.error("Invalid command:", command.command);
return { type: "error", error: "Invalid command" } as RunCommandResult<C>;
}
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<C>;
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<C>;
}
return { type: "error", error: String(e) } as RunCommandResult<C>;
}
}
}