From 44774a1b9504383b156a9a575831b8e4e4d6bf28 Mon Sep 17 00:00:00 2001 From: "engine-labs-app[bot]" <140088366+engine-labs-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 20:30:20 +0000 Subject: [PATCH 1/2] feat(auth): implement retry logic for authentication failures Adds support for retrying authentication up to a maximum number of attempts (default 3) when the initial action fails. Improves user experience by informing the user of failed attempts and allowing retries before closing the authentication window. - Introduce retry_count and max_retries to track attempts per session - Only close window after max retries exceeded or success - Show error messages and remaining attempts in UI - Use message-passing to synchronize thread callbacks with main app This is a user-visible improvement that helps prevent lockouts and assists with failed password entries, ensuring a smoother authentication flow. --- src/main.rs | 95 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index c7b75cc..6f69c9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,29 +19,55 @@ use polkit_agent_rs::polkit::UnixSession; use polkit_agent_rs::traits::ListenerExt; use std::sync::Arc; use std::sync::Mutex; +use futures::channel::mpsc::Sender; mod mypolkit; use mypolkit::MyPolkit; const OBJECT_PATH: &str = "/org/waycrate/PolicyKit1/AuthenticationAgent"; +const MAX_RETRIES: u32 = 3; -fn start_session(session: &AgentSession, password: String, task: gio::Task) { +fn start_session( + session: &AgentSession, + password: String, + task: gio::Task, + window_id: Id, + sender: Arc>>, +) { let sub_loop = glib::MainLoop::new(None, true); let sub_loop_2 = sub_loop.clone(); + let sender_clone = sender.clone(); - session.connect_completed(move |session, _success| { + session.connect_completed(move |session, success| { unsafe { - task.clone().return_result(Ok("success".to_string())); + if success { + task.clone().return_result(Ok("success".to_string())); + let _ = sender_clone.lock().unwrap().try_send(Message::AuthenticationSuccess(window_id)); + } else { + task.clone().return_result(Err(glib::Error::new( + glib::FileError::Failed, + "Authentication failed", + ))); + let _ = sender_clone.lock().unwrap().try_send(Message::AuthenticationFailed( + window_id, + "Authentication failed".to_string(), + )); + } } session.cancel(); sub_loop_2.quit(); }); - session.connect_show_info(|_session, info| { + + let sender_clone = sender.clone(); + session.connect_show_info(move |_session, info| { println!("info: {info}"); }); - session.connect_show_error(|_session, error| { + + let sender_clone = sender.clone(); + session.connect_show_error(move |_session, error| { eprintln!("error: {error}"); }); + session.connect_request(move |session, request, _echo_on| { println!("{}", request); if !request.starts_with("Password:") { @@ -83,11 +109,14 @@ struct AuthSession { error: Option, task: gio::Task, message: String, + retry_count: u32, + max_retries: u32, } -#[derive(Debug, Default)] +#[derive(Debug)] struct PolkitApp { sessions: BTreeMap, + sender: Option>>>, } #[to_layer_message(multi)] @@ -103,6 +132,7 @@ pub enum Message { AuthenticationSuccess(Id), AuthenticationFailed(Id, String), IcedEvent(Event), + SetSender(Arc>>), } impl PolkitApp { @@ -116,6 +146,7 @@ impl PolkitApp { ( Self { sessions: BTreeMap::new(), + sender: None, }, Command::none(), ) @@ -130,6 +161,7 @@ impl PolkitApp { iced::Subscription::run(|| { iced::stream::channel(100, |sender| { let sender = Arc::new(Mutex::new(sender)); + let sender_clone = sender.clone(); std::thread::spawn(move || { let main_loop = glib::MainLoop::new(None, true); @@ -154,7 +186,10 @@ impl PolkitApp { main_loop.run(); }); - futures::future::ready(()) + async move { + let _ = sender_clone.lock().unwrap().try_send(Message::SetSender(sender_clone.clone())); + futures::future::pending::<()>().await; + } }) }), iced::window::close_events().map(Message::WindowClosed), @@ -190,6 +225,8 @@ impl PolkitApp { error: None, task, message: messg, + retry_count: 0, + max_retries: MAX_RETRIES, }, ); @@ -220,15 +257,21 @@ impl PolkitApp { } Message::Authenticate(id) => { - if let Some(session) = self.sessions.get(&id) { + if let Some(session) = self.sessions.get_mut(&id) { let user: UnixUser = UnixUser::new_for_name(&session.selected_user).unwrap(); let ass = AgentSession::new(&user, &session.cookie); - - start_session(&ass, session.password.clone(), session.task.clone()); - } else { - return Command::none(); + let password = session.password.clone(); + let task = session.task.clone(); + + session.error = None; + + if let Some(sender) = self.sender.clone() { + std::thread::spawn(move || { + start_session(&ass, password, task, id, sender); + }); + } } - task::effect(Action::Window(WindowAction::Close(id))) + Command::none() } Message::AuthenticationSuccess(id) => { @@ -237,7 +280,20 @@ impl PolkitApp { Message::AuthenticationFailed(id, error) => { if let Some(session) = self.sessions.get_mut(&id) { - session.error = Some(error); + session.retry_count += 1; + + if session.retry_count >= session.max_retries { + return task::effect(Action::Window(WindowAction::Close(id))); + } + + let remaining = session.max_retries - session.retry_count; + session.error = Some(format!( + "{}. {} attempt{} remaining.", + error, + remaining, + if remaining == 1 { "" } else { "s" } + )); + session.password.clear(); } Command::none() } @@ -246,6 +302,11 @@ impl PolkitApp { task::effect(Action::Window(WindowAction::Close(id))) } + Message::SetSender(sender) => { + self.sender = Some(sender); + Command::none() + } + _ => Command::none(), } } @@ -289,9 +350,9 @@ impl PolkitApp { .spacing(20) .padding(25) ]; - if let Some(_error) = &session.error { - // content = content - // .push(text(error).style(|theme| iced::theme::Text::Color(theme.palette().danger))); + if let Some(error) = &session.error { + content = content + .push(text(error).size(14).style(|theme| iced::theme::Text::Color(theme.palette().danger))); } content = content.push(Space::with_height(Fill)).push( From b8f08ef1326c5108393ed10952acfa1ba3f18d1a Mon Sep 17 00:00:00 2001 From: "engine-labs-app[bot]" <140088366+engine-labs-app[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:22:47 +0000 Subject: [PATCH 2/2] feat(auth): add retry mechanism for authentication failures Enable users to retry authentication up to three times before the dialog closes. Displays error messages with remaining attempts and clears the password input after each failure. Ensures authentication is handled in background threads with safe message passing back to the UI. --- Readme.md | 2 +- src/main.rs | 57 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Readme.md b/Readme.md index 33a2922..25628f3 100644 --- a/Readme.md +++ b/Readme.md @@ -9,10 +9,10 @@ A Polkit authentication agent for Wayland desktops, built with `iced_layershell` * Lightweight and fast * Built with Rust and the Iced toolkit * Wayland native +* Retry mechanism (up to 3 attempts) if authentication fails ### Planned Features -* [ ] Retry if authentication fails * [ ] Use system theme for a more integrated look and feel * [ ] CSS-based styling for customization diff --git a/src/main.rs b/src/main.rs index 6f69c9b..8778ff5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use iced_layershell::to_layer_message; use polkit_agent_rs::polkit::UnixUser; use std::collections::BTreeMap; +use futures::channel::mpsc::Sender; use iced::widget::{Space, button, column, pick_list, row, text, text_input}; use iced::{Bottom, Center, Fill}; use polkit_agent_rs::RegisterFlags; @@ -19,7 +20,6 @@ use polkit_agent_rs::polkit::UnixSession; use polkit_agent_rs::traits::ListenerExt; use std::sync::Arc; use std::sync::Mutex; -use futures::channel::mpsc::Sender; mod mypolkit; use mypolkit::MyPolkit; @@ -27,12 +27,15 @@ const OBJECT_PATH: &str = "/org/waycrate/PolicyKit1/AuthenticationAgent"; const MAX_RETRIES: u32 = 3; fn start_session( - session: &AgentSession, + username: String, + cookie: String, password: String, task: gio::Task, window_id: Id, sender: Arc>>, ) { + let user: UnixUser = UnixUser::new_for_name(&username).unwrap(); + let session = AgentSession::new(&user, &cookie); let sub_loop = glib::MainLoop::new(None, true); let sub_loop_2 = sub_loop.clone(); @@ -42,32 +45,36 @@ fn start_session( unsafe { if success { task.clone().return_result(Ok("success".to_string())); - let _ = sender_clone.lock().unwrap().try_send(Message::AuthenticationSuccess(window_id)); + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::AuthenticationSuccess(window_id)); } else { task.clone().return_result(Err(glib::Error::new( glib::FileError::Failed, "Authentication failed", ))); - let _ = sender_clone.lock().unwrap().try_send(Message::AuthenticationFailed( - window_id, - "Authentication failed".to_string(), - )); + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::AuthenticationFailed( + window_id, + "Authentication failed".to_string(), + )); } } session.cancel(); sub_loop_2.quit(); }); - - let sender_clone = sender.clone(); + session.connect_show_info(move |_session, info| { println!("info: {info}"); }); - - let sender_clone = sender.clone(); + session.connect_show_error(move |_session, error| { eprintln!("error: {error}"); }); - + session.connect_request(move |session, request, _echo_on| { println!("{}", request); if !request.starts_with("Password:") { @@ -187,7 +194,10 @@ impl PolkitApp { }); async move { - let _ = sender_clone.lock().unwrap().try_send(Message::SetSender(sender_clone.clone())); + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::SetSender(sender_clone.clone())); futures::future::pending::<()>().await; } }) @@ -258,16 +268,16 @@ impl PolkitApp { Message::Authenticate(id) => { if let Some(session) = self.sessions.get_mut(&id) { - let user: UnixUser = UnixUser::new_for_name(&session.selected_user).unwrap(); - let ass = AgentSession::new(&user, &session.cookie); + let username = session.selected_user.clone(); + let cookie = session.cookie.clone(); let password = session.password.clone(); let task = session.task.clone(); - + session.error = None; - + if let Some(sender) = self.sender.clone() { std::thread::spawn(move || { - start_session(&ass, password, task, id, sender); + start_session(username, cookie, password, task, id, sender); }); } } @@ -281,11 +291,11 @@ impl PolkitApp { Message::AuthenticationFailed(id, error) => { if let Some(session) = self.sessions.get_mut(&id) { session.retry_count += 1; - + if session.retry_count >= session.max_retries { return task::effect(Action::Window(WindowAction::Close(id))); } - + let remaining = session.max_retries - session.retry_count; session.error = Some(format!( "{}. {} attempt{} remaining.", @@ -351,8 +361,11 @@ impl PolkitApp { .padding(25) ]; if let Some(error) = &session.error { - content = content - .push(text(error).size(14).style(|theme| iced::theme::Text::Color(theme.palette().danger))); + content = content.push( + text(error) + .size(14) + .color(iced::Color::from_rgb(0.8, 0.0, 0.0)), + ); } content = content.push(Space::with_height(Fill)).push(