diff --git a/src/main.rs b/src/main.rs index fe897f9..9c9f116 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4206,9 +4206,7 @@ async fn handle_control_command( } ControlAction::UnlockPassword { password } => { let password = zeroize::Zeroizing::new(password); - if !vault_locked.load(std::sync::atomic::Ordering::Relaxed) { - return sshwarden_agent::ControlResponse::ok("Vault is already unlocked"); - } + let was_locked = vault_locked.load(std::sync::atomic::Ordering::Relaxed); // Get email from vault file or config let email = { @@ -4251,11 +4249,17 @@ async fn handle_control_command( if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) { tracing::warn!("Failed to sync managed SSH config: {}", e); } - public_key_identity_tuples - .write() - .await - .set(key_tuples.clone()); - cached_key_tuples.write().await.set(key_tuples.clone()); + reconcile_synced_key_state( + &key_tuples, + cached_key_tuples, + public_key_identity_tuples, + local_key_cache_data, + local_cache_key_state, + authorization_memory, + key_material_fingerprints, + notification_state, + ) + .await; // Update key_names { @@ -4306,11 +4310,13 @@ async fn handle_control_command( ) .await; - info!("Vault unlocked via master password, {} keys loaded", count); - sshwarden_agent::ControlResponse::ok(&format!( - "Vault unlocked, {} SSH keys loaded", - count - )) + let message = if was_locked { + format!("Vault unlocked, {} SSH keys loaded", count) + } else { + format!("Bitwarden session refreshed, {} SSH keys loaded", count) + }; + info!("{}", message); + sshwarden_agent::ControlResponse::ok(&message) } Err(e) => sshwarden_agent::ControlResponse::err(&format!( "Sync failed after login: {}", @@ -4728,53 +4734,21 @@ async fn try_restore_api_session_hello( } } -/// Sync SSH keys from the Bitwarden API and reload into the agent. #[allow(clippy::too_many_arguments)] -async fn do_sync( - api_client: &Arc>>, +async fn reconcile_synced_key_state( + key_tuples: &[(String, String, String)], cached_key_tuples: &CachedKeyTuples, public_key_identity_tuples: &CachedKeyTuples, local_key_cache_data: &Arc>>, local_cache_key_state: &LocalCacheKeyHandle, authorization_memory: &AuthorizationMemorySet, key_material_fingerprints: &KeyMaterialFingerprints, - vault_locked: &Arc, - agent: &mut sshwarden_agent::SshWardenAgent, - key_names: &Arc>>, notification_state: &Arc>, -) -> Result { - let client_guard = api_client.read().await; - let client = match *client_guard { - Some(ref c) => c, - None => return Err("Not authenticated. Run 'sshwarden login'.".to_string()), - }; - - let keys = client - .sync_ssh_keys() - .await - .map_err(|e| format!("Sync failed: {}", e))?; - - let key_tuples: Vec<(String, String, String)> = keys - .iter() - .map(|k| { - ( - (*k.private_key_pem).clone(), - k.name.clone(), - k.cipher_id.clone(), - ) - }) - .collect(); - let count = key_tuples.len(); - if let Err(e) = write_key_selector_files(&keys) { - tracing::warn!("Failed to write key selector files: {}", e); - } - if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) { - tracing::warn!("Failed to sync managed SSH config: {}", e); - } +) { let old_fingerprints = key_material_fingerprints.read().await.clone(); let (cleared_memory, new_fingerprints) = clear_authorization_memory_for_changed_keys_async( &old_fingerprints, - &key_tuples, + key_tuples, authorization_memory, ) .await; @@ -4788,17 +4762,18 @@ async fn do_sync( public_key_identity_tuples .write() .await - .set(key_tuples.clone()); - cached_key_tuples.write().await.set(key_tuples.clone()); + .set(key_tuples.to_vec()); + cached_key_tuples.write().await.set(key_tuples.to_vec()); *key_material_fingerprints.write().await = new_fingerprints; if let (Some(existing_cache), Some(local_cache_key)) = ( local_key_cache_data.read().await.as_ref().cloned(), local_cache_key_state.read().await.clone_key(), ) { - match refresh_envelope_local_key_cache(&key_tuples, &existing_cache, &local_cache_key) { + match refresh_envelope_local_key_cache(key_tuples, &existing_cache, &local_cache_key) { Ok(cache) => { *local_key_cache_data.write().await = Some(cache); + local_cache_key_state.write().await.set(local_cache_key); tracing::info!("Local key cache refreshed after sync"); } Err(e) => { @@ -4815,6 +4790,69 @@ async fn do_sync( } } } +} + +/// Sync SSH keys from the Bitwarden API and reload into the agent. +#[allow(clippy::too_many_arguments)] +async fn do_sync( + api_client: &Arc>>, + cached_key_tuples: &CachedKeyTuples, + public_key_identity_tuples: &CachedKeyTuples, + local_key_cache_data: &Arc>>, + local_cache_key_state: &LocalCacheKeyHandle, + authorization_memory: &AuthorizationMemorySet, + key_material_fingerprints: &KeyMaterialFingerprints, + vault_locked: &Arc, + agent: &mut sshwarden_agent::SshWardenAgent, + key_names: &Arc>>, + notification_state: &Arc>, +) -> Result { + let client_guard = api_client.read().await; + let client = match *client_guard { + Some(ref c) => c, + None => return Err("Not authenticated. Run 'sshwarden login'.".to_string()), + }; + + if !client.has_user_key() { + return Err( + "Bitwarden session cannot decrypt vault data. Run `sshwarden login` to re-authenticate with your master password." + .to_string(), + ); + } + + let keys = client + .sync_ssh_keys() + .await + .map_err(|e| format!("Sync failed: {}", e))?; + + let key_tuples: Vec<(String, String, String)> = keys + .iter() + .map(|k| { + ( + (*k.private_key_pem).clone(), + k.name.clone(), + k.cipher_id.clone(), + ) + }) + .collect(); + let count = key_tuples.len(); + if let Err(e) = write_key_selector_files(&keys) { + tracing::warn!("Failed to write key selector files: {}", e); + } + if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) { + tracing::warn!("Failed to sync managed SSH config: {}", e); + } + reconcile_synced_key_state( + &key_tuples, + cached_key_tuples, + public_key_identity_tuples, + local_key_cache_data, + local_cache_key_state, + authorization_memory, + key_material_fingerprints, + notification_state, + ) + .await; // Update key_names {