diff --git a/locales/en.yml b/locales/en.yml index b0741ac..c332656 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -198,7 +198,6 @@ sync_downloading: "Downloading and decrypting configuration..." sync_upload_complete: "Configuration uploaded" sync_download_complete: "Configuration downloaded" sync_failed: "Synchronization failed" -keyboard_interactive: "Keyboard Interactive" settings_proxy: "Proxy" enable_proxy: "Enable Proxy" read_env_proxy: "Read environment variables on startup" @@ -210,4 +209,3 @@ global_proxy_port: "Proxy Port" global_proxy_user: "Proxy Username" global_proxy_password: "Proxy Password" save_proxy: "Save Proxy" - diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 6840769..bd742cd 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -200,7 +200,6 @@ sync_downloading: "正在下载并解密配置..." sync_upload_complete: "配置上传完成" sync_download_complete: "配置下载完成" sync_failed: "同步失败" -keyboard_interactive: "键盘交互" settings_proxy: "代理设置" enable_proxy: "启用代理功能" read_env_proxy: "启动时读取环境变量" @@ -212,4 +211,3 @@ global_proxy_port: "代理端口" global_proxy_user: "代理用户名" global_proxy_password: "代理密码" save_proxy: "保存代理设置" - diff --git a/src/app/dialogs.rs b/src/app/dialogs.rs index d2cc5b8..4b8889d 100644 --- a/src/app/dialogs.rs +++ b/src/app/dialogs.rs @@ -1,5 +1,5 @@ use gpui::{ - Anchor, AppContext as _, Context, Focusable as _, FontWeight, InteractiveElement as _, MouseButton, + Anchor, Context, Focusable as _, FontWeight, InteractiveElement as _, MouseButton, ParentElement as _, SharedString, StatefulInteractiveElement as _, Styled as _, Window, div, prelude::FluentBuilder as _, px, rems, }; @@ -8,7 +8,7 @@ use gpui_component::{ button::{Button, ButtonVariants as _}, dialog::Dialog, h_flex, - input::{Input, InputState}, + input::Input, menu::{DropdownMenu as _, PopupMenuItem}, progress::Progress, scroll::{Scrollbar, ScrollbarShow}, @@ -70,10 +70,7 @@ impl Ashell { let proxy_user_input = proxy_user_input.clone(); let proxy_password_input = proxy_password_input.clone(); move |content, window, cx| { - let method = view.read(cx).ssh_auth_method; - let is_password = method == AuthMethod::Password; - let is_key = method == AuthMethod::Key; - let is_kb = method == AuthMethod::KeyboardInteractive; + let is_password = view.read(cx).ssh_auth_method == AuthMethod::Password; let is_editing = view.read(cx).editing_session_id.is_some(); let proxy_type = view.read(cx).ssh_proxy_type.clone(); let show_proxy_fields = proxy_type != "none"; @@ -102,13 +99,13 @@ impl Ashell { AuthMethod::Password, cx, ) - }, - )), + }, + )), ) .child( Button::new("ssh-auth-key") .label(t!("key").to_string()) - .when(is_key, |button| button.primary()) + .when(!is_password, |button| button.primary()) .on_click(window.listener_for( &view, |this, _, _, cx| { @@ -116,22 +113,8 @@ impl Ashell { AuthMethod::Key, cx, ) - }, - )), - ) - .child( - Button::new("ssh-auth-kb") - .label(t!("keyboard_interactive").to_string()) - .when(is_kb, |button| button.primary()) - .on_click(window.listener_for( - &view, - |this, _, _, cx| { - this.set_ssh_auth_method( - AuthMethod::KeyboardInteractive, - cx, - ) - }, - )), + }, + )), ), ) .when(is_password, |this| { @@ -139,7 +122,7 @@ impl Ashell { Input::new(&password_input).mask_toggle().tab_index(4), ) }) - .when(is_key, |this| { + .when(!is_password, |this| { this.child( h_flex() .gap_2() @@ -2026,7 +2009,7 @@ impl Ashell { ) .child( div() - .text_size(rems(0.9)) + .text_size(rems(0.9)) .text_color(cx.theme().muted_foreground) .child(t!("about_feedback_hint")), ) @@ -2047,101 +2030,4 @@ impl Ashell { }) }); } - - pub(crate) fn show_interactive_prompt_dialog( - &mut self, - tab_id: String, - prompt_type: crate::terminal::PromptType, - instruction: String, - prompts: Vec, - window: &mut Window, - cx: &mut Context, - ) { - let view = cx.entity(); - - // Dynamically instantiate InputState for each prompt - let mut input_states = Vec::new(); - for p in &prompts { - let is_masked = !p.echo; - let input_state = cx.new(|cx| { - let mut state = InputState::new(window, cx).placeholder(p.prompt.clone()); - if is_masked { - state = state.masked(true); - } - state - }); - input_states.push(input_state); - } - - let tab_id_clone = tab_id.clone(); - let input_states_clone = input_states.clone(); - - self.active_dialog = Some(crate::app::DialogKind::PromptRequest); - - let tab_id_for_close = tab_id.clone(); - - window.open_dialog(cx, move |dialog: Dialog, _window, _| { - let title = match prompt_type { - crate::terminal::PromptType::KeyboardInteractive => "Keyboard Interactive Authentication", - crate::terminal::PromptType::Passphrase => "Enter Private Key Passphrase", - }; - - let tab_id_for_ok = tab_id_clone.clone(); - let input_states_for_ok = input_states_clone.clone(); - let view_for_ok = view.clone(); - - let view_for_close = view.clone(); - let tab_id_for_close = tab_id_for_close.clone(); - - let instruction_for_content = instruction.clone(); - let input_states_for_content = input_states_clone.clone(); - - dialog - .title(title) - .w(px(500.)) - .overlay_closable(false) - .on_close(move |_, _, cx| { - view_for_close.update(cx, |this, cx| { - this.active_dialog = None; - // Send Close command to abort connection if they close the dialog without OK - if let Some(tab) = this.tabs.iter().find(|t| t.id == tab_id_for_close) { - tab.send_backend(crate::terminal::BackendCommand::Close); - } - cx.notify(); - }); - }) - .on_ok(move |_, window, cx| { - view_for_ok.update(cx, |this, cx| { - this.active_dialog = None; - let mut responses = Vec::new(); - for state in &input_states_for_ok { - responses.push(state.read(cx).text().to_string()); - } - if let Some(tab) = this.tabs.iter().find(|t| t.id == tab_id_for_ok) { - tab.send_backend(crate::terminal::BackendCommand::PromptResponse(responses)); - } - cx.notify(); - }); - window.close_dialog(cx); - true - }) - .content(move |content, _window, _cx| { - let mut container = v_flex().gap_3(); - if !instruction_for_content.is_empty() { - container = container.child( - div() - .text_sm() - .text_color(gpui::rgba(0x808080ff)) - .child(instruction_for_content.clone()) - ); - } - for input_state in &input_states_for_content { - container = container.child( - Input::new(input_state).w_full() - ); - } - content.child(container) - }) - }); - } } diff --git a/src/app/mod.rs b/src/app/mod.rs index a518748..0be7d2b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -197,7 +197,6 @@ pub(crate) enum DialogKind { SessionSelector, Transfers, NewSsh, - PromptRequest, } pub(crate) struct Ashell { @@ -687,7 +686,7 @@ impl Ashell { this.apply_theme_preferences(window, cx); // this.open_local(cx); - this.start_event_pump(window, cx); + this.start_event_pump(cx); this } @@ -749,20 +748,18 @@ impl Ashell { cx.notify(); } - pub(crate) fn start_event_pump(&self, window: &mut Window, cx: &mut Context) { - cx.spawn_in(window, async move |this, mut cx| { + pub(crate) fn start_event_pump(&self, cx: &mut Context) { + cx.spawn(async move |this, cx| { let mut idle_frames = 0u32; let mut last_blink_time = std::time::Instant::now(); loop { cx.background_executor() .timer(Duration::from_millis(16)) .await; - let mut changed = false; - let mut system_sampled = false; - let update_res = gpui::AsyncWindowContext::update(&mut cx, |window, cx| { - let _ = this.update(cx, |this, cx| { - changed = this.drain_backend_events(window, cx); - system_sampled = this.sample_system_if_due(); + if this + .update(cx, |this, cx| { + let changed = this.drain_backend_events(); + let system_sampled = this.sample_system_if_due(); this.sync_theme_if_due(cx); let is_blinking = matches!( this.cursor_style, @@ -784,9 +781,9 @@ impl Ashell { idle_frames = 0; } } - }); - }); - if update_res.is_err() { + }) + .is_err() + { break; } } @@ -794,7 +791,7 @@ impl Ashell { .detach(); } - pub(crate) fn drain_backend_events(&mut self, window: &mut Window, cx: &mut Context) -> bool { + pub(crate) fn drain_backend_events(&mut self) -> bool { let mut changed = false; let mut transfers_changed = false; while let Ok(event) = self.events_rx.try_recv() { @@ -837,21 +834,6 @@ impl Ashell { self.connection_progress = None; } } - BackendEvent::PromptRequest { - tab_id, - prompt_type, - instruction, - prompts, - } => { - self.show_interactive_prompt_dialog( - tab_id, - prompt_type, - instruction, - prompts, - window, - cx, - ); - } BackendEvent::SftpEntries { tab_id, path, diff --git a/src/backend/local.rs b/src/backend/local.rs index 3c4aa9e..32d53b4 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -117,7 +117,6 @@ pub fn spawn_local_terminal( } BackendCommand::Close => break, BackendCommand::SampleMetrics => {} - BackendCommand::PromptResponse(_) => {} }, Err(mpsc::RecvTimeoutError::Timeout) => { if let Ok(Some(status)) = child.try_wait() { diff --git a/src/backend/ssh.rs b/src/backend/ssh.rs index f45b01f..26a98a3 100644 --- a/src/backend/ssh.rs +++ b/src/backend/ssh.rs @@ -16,23 +16,8 @@ use tokio::sync::mpsc; use crate::{ session::config::{AuthMethod, Session}, system::{SystemSnapshot, remote_snapshot_from_kv}, - terminal::{BackendCommand, BackendEvent, BackendTx, PromptType, PromptInfo}, + terminal::{BackendCommand, BackendEvent, BackendTx}, }; -use std::sync::OnceLock; -use std::collections::HashMap; -use tokio::sync::Mutex; - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct CachedCreds { - pub password: Option, - pub passphrase: Option, - pub kb_responses: Option>, -} - -pub static CREDENTIALS_CACHE: OnceLock>> = OnceLock::new(); - -pub static PROMPT_LOCK: OnceLock> = OnceLock::new(); pub fn spawn_ssh_terminal( runtime: &tokio::runtime::Handle, @@ -110,7 +95,7 @@ async fn run_ssh( }); let handle = Arc::new(tokio::sync::Mutex::new( - connect_and_authenticate(&tab_id, &session, &events, &mut commands).await?, + connect_and_authenticate(&tab_id, &session, &events).await?, )); let mut channel = handle @@ -171,9 +156,6 @@ async fn run_ssh( } }); } - Some(BackendCommand::PromptResponse(_)) => { - tracing::warn!("[ssh] received unexpected prompt response after authentication"); - } Some(BackendCommand::Close) | None => { tracing::info!("[ssh] local client closed the session for tab {}", tab_id); let _ = channel.eof().await; @@ -231,27 +213,10 @@ async fn run_ssh( Ok(()) } -async fn load_session_private_key_with_cache(session: &Session) -> Result { - let cached_passphrase = { - let cache_lock = CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())); - let cache = cache_lock.lock().unwrap(); - cache.get(&session.id).and_then(|c| c.passphrase.clone()) - }; - if let Some(p) = cached_passphrase { - let mut temp_session = session.clone(); - temp_session.passphrase = p; - if let Ok(key) = load_session_private_key(&temp_session) { - return Ok(key); - } - } - load_session_private_key(session) -} - async fn connect_and_authenticate( tab_id: &str, session: &Session, events: &std::sync::mpsc::Sender, - commands: &mut mpsc::UnboundedReceiver, ) -> Result> { let config = Arc::new(client::Config { inactivity_timeout: Some(std::time::Duration::from_secs(600)), @@ -313,55 +278,7 @@ async fn connect_and_authenticate( tab_id: tab_id.to_string(), text: format!("connected to {addr}, loading private key from {source}"), }); - let mut keypair = load_session_private_key_with_cache(session).await; - if let Err(e) = &keypair { - let err_str = e.to_string(); - if err_str.contains("encrypted") || err_str.contains("passphrase") || err_str.contains("decrypt") { - let prompt_lock = PROMPT_LOCK.get_or_init(|| Mutex::new(())); - let _guard = prompt_lock.lock().await; - let _ = events.send(BackendEvent::PromptRequest { - tab_id: tab_id.to_string(), - prompt_type: PromptType::Passphrase, - instruction: format!("Enter passphrase for private key (source: {})", source), - prompts: vec![PromptInfo { - prompt: "Passphrase".to_string(), - echo: false, - }], - }); - let mut passphrase_res = None; - while let Some(cmd) = commands.recv().await { - match cmd { - BackendCommand::PromptResponse(responses) => { - if let Some(p) = responses.first().cloned() { - passphrase_res = Some(p); - } - break; - } - BackendCommand::Close => { - return Err(anyhow!("Authentication cancelled by user")); - } - _ => {} - } - } - if let Some(p) = passphrase_res { - { - let cache_lock = CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())); - let mut cache = cache_lock.lock().unwrap(); - cache.entry(session.id.clone()).or_insert_with(|| CachedCreds { - password: None, - passphrase: None, - kb_responses: None, - }).passphrase = Some(p.clone()); - } - let mut temp_session = session.clone(); - temp_session.passphrase = p; - keypair = load_session_private_key(&temp_session); - } else { - return Err(anyhow!("Passphrase prompt cancelled")); - } - } - } - let keypair = keypair.context("failed to load private key")?; + let keypair = load_session_private_key(session)?; let algorithm = format!("{:?}", keypair.algorithm()); let _ = events.send(BackendEvent::Status { tab_id: tab_id.to_string(), @@ -397,83 +314,6 @@ async fn connect_and_authenticate( } success } - AuthMethod::KeyboardInteractive => { - let cached = { - let cache_lock = CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())); - let cache = cache_lock.lock().unwrap(); - cache.get(&session.id).cloned() - }; - let mut response = if cached.as_ref().and_then(|c| c.kb_responses.as_ref()).is_some() { - handle.authenticate_keyboard_interactive_start(&session.user, None).await? - } else { - let prompt_lock = PROMPT_LOCK.get_or_init(|| Mutex::new(())); - let _guard = prompt_lock.lock().await; - handle.authenticate_keyboard_interactive_start(&session.user, None).await? - }; - loop { - match response { - russh::client::KeyboardInteractiveAuthResponse::Success => { - break true; - } - russh::client::KeyboardInteractiveAuthResponse::Failure => { - break false; - } - russh::client::KeyboardInteractiveAuthResponse::InfoRequest { name, instructions, prompts } => { - let mut responses = Vec::new(); - let mut cache_hit = false; - if let Some(c) = cached.as_ref().and_then(|c| c.kb_responses.as_ref()) { - if c.len() == prompts.len() { - responses = c.clone(); - cache_hit = true; - } - } - if !cache_hit { - let prompt_lock = PROMPT_LOCK.get_or_init(|| Mutex::new(())); - let _guard = prompt_lock.lock().await; - let mut prompt_infos = Vec::new(); - for p in &prompts { - prompt_infos.push(PromptInfo { - prompt: p.prompt.clone(), - echo: p.echo, - }); - } - let _ = events.send(BackendEvent::PromptRequest { - tab_id: tab_id.to_string(), - prompt_type: PromptType::KeyboardInteractive, - instruction: format!("{}\n{}", name, instructions), - prompts: prompt_infos, - }); - let mut prompt_res = None; - while let Some(cmd) = commands.recv().await { - match cmd { - BackendCommand::PromptResponse(res) => { - prompt_res = Some(res); - break; - } - BackendCommand::Close => { - return Err(anyhow!("Authentication cancelled")); - } - _ => {} - } - } - if let Some(res) = prompt_res { - responses = res; - let cache_lock = CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())); - let mut cache = cache_lock.lock().unwrap(); - cache.entry(session.id.clone()).or_insert_with(|| CachedCreds { - password: None, - passphrase: None, - kb_responses: None, - }).kb_responses = Some(responses.clone()); - } else { - return Err(anyhow!("Keyboard-interactive cancelled")); - } - } - response = handle.authenticate_keyboard_interactive_respond(responses).await?; - } - } - } - } }; if !authed { @@ -495,10 +335,6 @@ async fn connect_and_authenticate( session.port, key_source_label(session) ), - AuthMethod::KeyboardInteractive => format!( - "authentication failed: server rejected keyboard-interactive authentication for {}@{}:{}", - session.user, session.host, session.port - ), } )); } diff --git a/src/session/config.rs b/src/session/config.rs index 504bc3f..cdf3d17 100644 --- a/src/session/config.rs +++ b/src/session/config.rs @@ -6,11 +6,10 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "lowercase")] pub enum AuthMethod { Password, Key, - KeyboardInteractive, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -94,32 +93,6 @@ impl Session { proxy_password: String::new(), } } - - pub fn keyboard_interactive( - host: String, - port: u16, - user: String, - ) -> Self { - let name = format!("{user}@{host}"); - Self { - id: Uuid::new_v4().to_string(), - name, - host, - port, - user, - auth: AuthMethod::KeyboardInteractive, - password: String::new(), - private_key_path: String::new(), - private_key_inline: String::new(), - passphrase: String::new(), - last_used: None, - proxy_type: "none".to_string(), - proxy_host: String::new(), - proxy_port: None, - proxy_user: String::new(), - proxy_password: String::new(), - } - } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/session/mod.rs b/src/session/mod.rs index 26a0998..0ecfddb 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -111,7 +111,6 @@ impl Ashell { } Session::key(host, port, user, key_path, key_inline, passphrase) } - AuthMethod::KeyboardInteractive => Session::keyboard_interactive(host, port, user), }; session.name = name; if let Some(id) = existing_id { diff --git a/src/sftp/mod.rs b/src/sftp/mod.rs index 3d0c039..828e1b8 100644 --- a/src/sftp/mod.rs +++ b/src/sftp/mod.rs @@ -875,34 +875,6 @@ async fn connect_and_authenticate( } success } - AuthMethod::KeyboardInteractive => { - let cached_kb_responses = { - let cache_lock = crate::backend::ssh::CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())); - let cache = cache_lock.lock().unwrap(); - cache.get(&session.id).and_then(|c| c.kb_responses.clone()) - }; - let responses = cached_kb_responses.unwrap_or_else(|| vec![session.password.clone()]); - let mut response = handle.authenticate_keyboard_interactive_start(&session.user, None).await?; - loop { - match response { - russh::client::KeyboardInteractiveAuthResponse::Success => { - break true; - } - russh::client::KeyboardInteractiveAuthResponse::Failure => { - break false; - } - russh::client::KeyboardInteractiveAuthResponse::InfoRequest { prompts, .. } => { - let mut resp = responses.clone(); - if resp.len() < prompts.len() { - resp.resize(prompts.len(), String::new()); - } else if resp.len() > prompts.len() { - resp.truncate(prompts.len()); - } - response = handle.authenticate_keyboard_interactive_respond(resp).await?; - } - } - } - } }; if !authed { @@ -914,7 +886,6 @@ async fn connect_and_authenticate( match session.auth { AuthMethod::Password => "password", AuthMethod::Key => "public key", - AuthMethod::KeyboardInteractive => "keyboard interactive", }, session.user, session.host, @@ -928,17 +899,8 @@ async fn connect_and_authenticate( fn load_session_private_key(session: &Session) -> Result { let inline_key = normalize_inline_private_key(&session.private_key_inline); let key_path = expand_key_path(session.private_key_path.trim()); - let cached_passphrase = { - let cache_lock = crate::backend::ssh::CREDENTIALS_CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())); - let cache = cache_lock.lock().unwrap(); - cache.get(&session.id).and_then(|c| c.passphrase.clone()) - }; - let passphrase = if let Some(ref p) = cached_passphrase { - Some(p.as_str()) - } else { - let p = session.passphrase.trim(); - (!p.is_empty()).then_some(p) - }; + let passphrase = session.passphrase.trim(); + let passphrase = (!passphrase.is_empty()).then_some(passphrase); let has_inline = !inline_key.is_empty(); let has_path = key_path.is_some(); diff --git a/src/terminal/mod.rs b/src/terminal/mod.rs index ad3644a..3ef083d 100644 --- a/src/terminal/mod.rs +++ b/src/terminal/mod.rs @@ -25,35 +25,16 @@ pub enum TabKind { Ssh, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PromptType { - KeyboardInteractive, - Passphrase, -} - -#[derive(Debug, Clone)] -pub struct PromptInfo { - pub prompt: String, - pub echo: bool, -} - #[derive(Debug)] pub enum BackendCommand { Input(Vec), Resize { cols: u16, rows: u16 }, SampleMetrics, Close, - PromptResponse(Vec), } #[derive(Debug, Clone)] pub enum BackendEvent { - PromptRequest { - tab_id: String, - prompt_type: PromptType, - instruction: String, - prompts: Vec, - }, Output { tab_id: String, bytes: Vec,