From 0bf73848fda6fc4a9df04a233f61d86d90e140f1 Mon Sep 17 00:00:00 2001 From: Ricky Date: Wed, 27 May 2026 02:28:52 +0800 Subject: [PATCH] Restore key import for extensionless private keys OpenSSH private keys commonly use names like id_ed25519 without an extension. The native picker was restricted to extension filters, which hid those files and made the public .pub file the visible default path. The picker is now unfiltered and scoped by title so private keys with no suffix or uncommon suffixes can be selected, while the existing import path still reads the selected file content. A focused test covers reading an extensionless private-key file. Constraint: Keep the change limited to the SSH key picker/import path. Rejected: Adding more extensions or relying on a wildcard filter, because rfd filters are extension-based and backend behavior can still hide extensionless names. Confidence: High. Scope-risk: Low; the picker is less restrictive but credential handling is unchanged. Reversibility: Revert src-tauri/src/commands/dialog.rs. Directive: Address the extensionless private key Browse/import bug noted in GitHub issue #3. Tested: npm run build; cargo check -p vibeshell; cargo test -p vibeshell dialog::tests -- --nocapture; rustfmt --edition 2021 --check src-tauri/src/commands/dialog.rs. Not-tested: Manual native dialog click-through on macOS. Related: https://github.com/veithly/vibeshell/issues/3 --- src-tauri/src/commands/dialog.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/commands/dialog.rs b/src-tauri/src/commands/dialog.rs index ceb812f..5e8edce 100644 --- a/src-tauri/src/commands/dialog.rs +++ b/src-tauri/src/commands/dialog.rs @@ -4,7 +4,7 @@ //! specifically for picking SSH key files. use rfd::FileDialog; -use std::fs; +use std::{fs, path::Path}; /// Opens a file dialog to pick an SSH private key file. /// @@ -18,9 +18,9 @@ use std::fs; /// or an error if the dialog failed to open. #[tauri::command] pub async fn pick_ssh_key_file() -> Result, String> { + // OpenSSH private keys commonly use names like `id_ed25519` with no extension, + // while rfd filters are extension-based and can hide them. let file = FileDialog::new() - .add_filter("SSH Keys", &["pem", "key", "pub", "ppk"]) - .add_filter("All Files", &["*"]) .set_title("Select SSH Private Key") .pick_file(); @@ -85,5 +85,29 @@ pub async fn pick_download_directory() -> Result, String> { /// Returns the file contents as a string, or an error if the file cannot be read. #[tauri::command] pub async fn read_ssh_key_file(path: String) -> Result { - fs::read_to_string(&path).map_err(|e| format!("Failed to read SSH key file: {}", e)) + read_ssh_key_file_content(path) +} + +fn read_ssh_key_file_content(path: impl AsRef) -> Result { + fs::read_to_string(path.as_ref()).map_err(|e| format!("Failed to read SSH key file: {}", e)) +} + +#[cfg(test)] +mod tests { + use super::read_ssh_key_file_content; + + #[test] + fn reads_extensionless_private_key_files() { + let temp_dir = tempfile::tempdir().expect("temp dir should be created"); + let key_path = temp_dir.path().join("id_ed25519"); + let key_content = + "-----BEGIN OPENSSH PRIVATE KEY-----\nkey\n-----END OPENSSH PRIVATE KEY-----\n"; + + std::fs::write(&key_path, key_content).expect("extensionless key should be written"); + + assert_eq!( + read_ssh_key_file_content(&key_path).expect("extensionless key should be read"), + key_content + ); + } }