From f10d9536a84b7b56fc08a6463b362aea7f4b4e8d Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 12 Jun 2025 10:35:44 +0200 Subject: [PATCH 1/6] Implement credential selection --- .../data/resources/ui/window.ui | 2 +- .../src/credential_service/mod.rs | 55 +++---- .../src/credential_service/usb.rs | 145 ++++++++++++++++-- .../src/dbus.rs | 21 +++ .../src/view_model/gtk/mod.rs | 1 + .../src/view_model/mod.rs | 22 ++- .../src/window.rs | 8 + 7 files changed, 203 insertions(+), 51 deletions(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui b/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui index 77fe2e7c..3a554c77 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui +++ b/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui @@ -173,7 +173,7 @@ vertical - Credentials + Choose credential diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs index 803453e9..70021e0a 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs @@ -17,10 +17,7 @@ use libwebauthn::{ use crate::{ credential_service::{hybrid::HybridEvent, usb::UsbEvent}, - dbus::{ - CredentialRequest, CredentialResponse, GetAssertionResponseInternal, - MakeCredentialResponseInternal, - }, + dbus::{CredentialRequest, CredentialResponse}, view_model::{Device, Transport}, }; @@ -132,7 +129,22 @@ where Poll::Pending => Poll::Pending, Poll::Ready(Some(HybridEvent { state })) => { if let HybridStateInternal::Completed(hybrid_response) = &state { - let response = hybrid_response.as_cred_response(&["hybrid"], "cross-platform"); + let response = match hybrid_response { + AuthenticatorResponse::CredentialCreated(make_credential_response) => { + CredentialResponse::from_make_credential( + make_credential_response, + &["hybrid"], + "cross-platform", + ) + } + AuthenticatorResponse::CredentialsAsserted(get_assertion_response) => { + CredentialResponse::from_get_assertion( + // TODO: Implement credential selection for hybrid + &get_assertion_response.assertions[0], + "cross-platform", + ) + } + }; let mut cred_response = cred_response.lock().unwrap(); cred_response.replace(response); } @@ -163,9 +175,8 @@ where Poll::Pending => Poll::Pending, Poll::Ready(Some(UsbEvent { state })) => { if let UsbStateInternal::Completed(response) = &state { - let response = response.as_cred_response(&["usb"], "cross-platform"); let mut cred_response = cred_response.lock().unwrap(); - cred_response.replace(response); + cred_response.replace(response.clone()); } Poll::Ready(Some(state.into())) } @@ -179,32 +190,6 @@ enum AuthenticatorResponse { CredentialCreated(MakeCredentialResponse), CredentialsAsserted(GetAssertionResponse), } -impl AuthenticatorResponse { - fn as_cred_response(&self, transports: &[&str], modality: &str) -> CredentialResponse { - match self { - AuthenticatorResponse::CredentialCreated(make_response) => { - CredentialResponse::CreatePublicKeyCredentialResponse( - MakeCredentialResponseInternal::new( - make_response.clone(), - transports.iter().map(|s| s.to_string()).collect(), - modality.to_string(), - ), - ) - } - AuthenticatorResponse::CredentialsAsserted(GetAssertionResponse { assertions }) - if assertions.len() == 1 => - { - CredentialResponse::GetPublicKeyCredentialResponse( - GetAssertionResponseInternal::new(assertions[0].clone(), modality.to_string()), - ) - } - AuthenticatorResponse::CredentialsAsserted(GetAssertionResponse { assertions }) => { - assert!(!assertions.is_empty()); - todo!("need to support selection from multiple credentials"); - } - } - } -} impl From for AuthenticatorResponse { fn from(value: MakeCredentialResponse) -> Self { @@ -298,9 +283,7 @@ mod test { origin: Some("webauthn.io".to_string()), is_same_origin: Some(true), r#type: "public-key".to_string(), - public_key: Some(CreatePublicKeyCredentialRequest { - request_json: request_json, - }), + public_key: Some(CreatePublicKeyCredentialRequest { request_json }), } .try_into_ctap2_request() .unwrap(); diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs index 6c8685f6..5cb04d23 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs @@ -1,8 +1,10 @@ use std::time::Duration; use async_stream::stream; +use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use futures_lite::Stream; use libwebauthn::{ + ops::webauthn::GetAssertionResponse, transport::{hid::HidDevice, Device}, webauthn::{Error as WebAuthnError, WebAuthn}, UxUpdate, @@ -10,9 +12,12 @@ use libwebauthn::{ use tokio::sync::mpsc::{self, Receiver, Sender, WeakSender}; use tracing::{debug, warn}; -use crate::dbus::CredentialRequest; +use crate::{ + dbus::{CredentialRequest, GetAssertionResponseInternal}, + view_model::Credential, +}; -use super::AuthenticatorResponse; +use super::{AuthenticatorResponse, CredentialResponse}; pub(crate) trait UsbHandler { fn start( @@ -31,6 +36,7 @@ impl InProcessUsbHandler { ) -> Result<(), String> { let mut state = UsbStateInternal::Idle; let (signal_tx, mut signal_rx) = mpsc::channel(256); + let (cred_tx, mut cred_rx) = mpsc::channel(1); debug!("polling for USB status"); loop { tracing::debug!("current usb state: {:?}", state); @@ -109,9 +115,32 @@ impl InProcessUsbHandler { Some(Ok(UsbUvMessage::NeedsUserPresence)) => { Ok(UsbStateInternal::NeedsUserPresence) } - Some(Ok(UsbUvMessage::ReceivedCredential(response))) => { - Ok(UsbStateInternal::Completed(response.clone())) - } + Some(Ok(UsbUvMessage::ReceivedCredentials(response))) => match response { + AuthenticatorResponse::CredentialCreated(make_credential_response) => { + Ok(UsbStateInternal::Completed( + CredentialResponse::from_make_credential( + &make_credential_response, + &["usb"], + "cross-platform", + ), + )) + } + AuthenticatorResponse::CredentialsAsserted(get_assertion_response) => { + if get_assertion_response.assertions.len() == 1 { + Ok(UsbStateInternal::Completed( + CredentialResponse::from_get_assertion( + &get_assertion_response.assertions[0], + "cross-platform", + ), + )) + } else { + Ok(UsbStateInternal::SelectCredential { + response: get_assertion_response, + cred_tx: cred_tx.clone(), + }) + } + } + }, Some(Err(err)) => Err(err.clone()), None => Err("Channel disconnected".to_string()), } @@ -140,13 +169,68 @@ impl InProcessUsbHandler { Ok(UsbStateInternal::NeedsUserVerification { attempts_left }) } UsbUvMessage::NeedsUserPresence => Ok(UsbStateInternal::NeedsUserPresence), - UsbUvMessage::ReceivedCredential(response) => { - Ok(UsbStateInternal::Completed(response.clone())) - } + UsbUvMessage::ReceivedCredentials(response) => match response { + AuthenticatorResponse::CredentialCreated(make_credential_response) => { + Ok(UsbStateInternal::Completed( + CredentialResponse::from_make_credential( + &make_credential_response, + &["usb"], + "cross-platform", + ), + )) + } + AuthenticatorResponse::CredentialsAsserted(get_assertion_response) => { + if get_assertion_response.assertions.len() == 1 { + Ok(UsbStateInternal::Completed( + CredentialResponse::from_get_assertion( + &get_assertion_response.assertions[0], + "cross-platform", + ), + )) + } else { + Ok(UsbStateInternal::SelectCredential { + response: get_assertion_response, + cred_tx: cred_tx.clone(), + }) + } + } + }, }, None => Err("USB UV handler channel closed".to_string()), }, UsbStateInternal::Completed(_) => Ok(prev_usb_state), + UsbStateInternal::SelectCredential { + response, + cred_tx: _, + } => match cred_rx.recv().await { + Some(cred_id) => { + let assertion = response + .assertions + .iter() + .find(|c| { + c.credential_id + .as_ref() + .map(|c| URL_SAFE_NO_PAD.encode(&c.id) == cred_id) + .unwrap_or_default() + }) + .cloned(); + match assertion { + Some(assertion) => Ok(UsbStateInternal::Completed( + CredentialResponse::GetPublicKeyCredentialResponse( + GetAssertionResponseInternal::new( + assertion, + "cross-platform".to_string(), + ), + ), + )), + None => Err("Selected credential not found.".to_string()), + } + } + None => { + tracing::debug!("cred channel closed before receiving cred from client."); + Err("Cred channel disconnected".to_string()) + } + }, }; state = next_usb_state?; tx.send(state.clone()) @@ -219,7 +303,7 @@ async fn notify_ceremony_completed( response: AuthenticatorResponse, ) { signal_tx - .send(Ok(UsbUvMessage::ReceivedCredential(response))) + .send(Ok(UsbUvMessage::ReceivedCredentials(response))) .await .unwrap(); } @@ -278,8 +362,14 @@ pub(super) enum UsbStateInternal { /// The device needs evidence of user presence (e.g. touch) to release the credential. NeedsUserPresence, + // Multiple credentials have been found and the user has to select which to use + SelectCredential { + response: GetAssertionResponse, + cred_tx: mpsc::Sender, + }, + /// USB tapped, received credential - Completed(AuthenticatorResponse), + Completed(CredentialResponse), // TODO: implement cancellation // This isn't actually sent from the server. //UserCancelled, @@ -324,6 +414,13 @@ pub enum UsbState { // When we encounter multiple devices, we let all of them blink and continue // with the one that was tapped. SelectingDevice, + + // Multiple credentials have been found and the user has to select which to use + // List of user-identities to decide which to use. + SelectCredential { + creds: Vec, + cred_tx: mpsc::Sender, + }, } impl From for UsbState { @@ -346,6 +443,32 @@ impl From for UsbState { UsbStateInternal::Completed(_) => UsbState::Completed, // UsbStateInternal::UserCancelled => UsbState:://UserCancelled, UsbStateInternal::SelectingDevice(_) => UsbState::SelectingDevice, + UsbStateInternal::SelectCredential { response, cred_tx } => { + UsbState::SelectCredential { + creds: response + .assertions + .iter() + .map(|x| Credential { + id: x + .credential_id + .as_ref() + .map(|i| URL_SAFE_NO_PAD.encode(&i.id)) + .unwrap_or_else(|| String::from("")), + name: x + .user + .as_ref() + .and_then(|u| u.name.clone()) + .unwrap_or_else(|| String::from("")), + username: x + .user + .as_ref() + .map(|u| u.display_name.clone()) + .unwrap_or_default(), + }) + .collect(), + cred_tx, + } + } } } } @@ -408,5 +531,5 @@ enum UsbUvMessage { attempts_left: Option, }, NeedsUserPresence, - ReceivedCredential(AuthenticatorResponse), + ReceivedCredentials(AuthenticatorResponse), } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs index 0de17fd5..5cd437e4 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs @@ -240,6 +240,27 @@ pub(crate) enum CredentialResponse { GetPublicKeyCredentialResponse(GetAssertionResponseInternal), } +impl CredentialResponse { + pub(crate) fn from_make_credential( + response: &MakeCredentialResponse, + transports: &[&str], + modality: &str, + ) -> CredentialResponse { + CredentialResponse::CreatePublicKeyCredentialResponse(MakeCredentialResponseInternal::new( + response.clone(), + transports.iter().map(|s| s.to_string()).collect(), + modality.to_string(), + )) + } + + pub(crate) fn from_get_assertion(assertion: &Assertion, modality: &str) -> CredentialResponse { + CredentialResponse::GetPublicKeyCredentialResponse(GetAssertionResponseInternal::new( + assertion.clone(), + modality.to_string(), + )) + } +} + #[derive(Clone, Debug)] pub(crate) struct MakeCredentialResponseInternal { ctap: MakeCredentialResponse, diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs index 8e3555fa..a68f0560 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs @@ -302,6 +302,7 @@ impl ViewModel { } fn select_credential(&self, cred_id: String) { + // todo: Do we still need this? self.set_selected_credential(cred_id); } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs index 7ce38ff4..a3a835f3 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs @@ -33,6 +33,7 @@ where providers: Vec, usb_pin_tx: Option>>>, + usb_cred_tx: Option>>>, hybrid_qr_state: HybridState, hybrid_qr_code_data: Option>, @@ -61,6 +62,7 @@ impl ViewModel { selected_credential: None, providers: Vec::new(), usb_pin_tx: None, + usb_cred_tx: None, hybrid_qr_state: HybridState::default(), hybrid_qr_code_data: None, hybrid_linked_state: HybridState::default(), @@ -278,7 +280,14 @@ impl ViewModel { "Credential selected: {:?}. Current Device: {:?}", cred_id, self.selected_device ); + // TODO: Do we still need this? self.selected_credential = Some(cred_id.clone()); + + if let Some(cred_tx) = self.usb_cred_tx.take() { + if cred_tx.lock().await.send(cred_id.clone()).await.is_err() { + error!("Failed to send selected credential to device"); + } + } self.tx_update .send(ViewUpdate::SelectCredential(cred_id)) .await @@ -327,6 +336,13 @@ impl ViewModel { .unwrap(); } UsbState::Idle | UsbState::Waiting => {} + UsbState::SelectCredential { creds, cred_tx } => { + let _ = self.usb_cred_tx.insert(Arc::new(Mutex::new(cred_tx))); + self.tx_update + .send(ViewUpdate::SetCredentials(creds)) + .await + .unwrap(); + } } } Event::Background(BackgroundEvent::HybridQrStateChanged(state)) => { @@ -409,9 +425,9 @@ pub enum Event { #[derive(Clone, Debug, Default)] pub struct Credential { - id: String, - name: String, - username: Option, + pub(crate) id: String, + pub(crate) name: String, + pub(crate) username: Option, } #[derive(Debug, Default)] diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs index ea70f5c1..cd500d81 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs @@ -213,6 +213,14 @@ impl ExampleApplicationWindow { } } )); + + view_model.connect_credentials_notify(clone!( + #[weak] + stack, + move |_vm| { + stack.set_visible_child_name("choose_credential"); + } + )); } fn save_window_size(&self) -> Result<(), glib::BoolError> { From 75615424e6e34ff6c1854abdc6f289c107ac863e Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 12 Jun 2025 12:38:41 +0200 Subject: [PATCH 2/6] Also show optional display_name, if present --- .../src/view_model/gtk/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs index a68f0560..effb4b29 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs @@ -258,7 +258,11 @@ impl ViewModel { .orientation(gtk::Orientation::Horizontal) .build(); let icon = gtk::Image::builder().icon_name(icon_name).build(); - let label = gtk::Label::builder().label(credential.name()).build(); + let mut display_label = credential.name().to_string(); + if let Some(username) = credential.username() { + display_label += &format!(" ({username})"); + } + let label = gtk::Label::builder().label(display_label).build(); b.append(&icon); b.append(&label); From 3fa38d89ae5e83ddb601f806bac1ec44544f0da9 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 13 Jun 2025 13:55:38 +0200 Subject: [PATCH 3/6] Correct comment for hybrid credential selection --- .../src/credential_service/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs index 70021e0a..998424bf 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs @@ -139,7 +139,10 @@ where } AuthenticatorResponse::CredentialsAsserted(get_assertion_response) => { CredentialResponse::from_get_assertion( - // TODO: Implement credential selection for hybrid + // When doing hybrid, the authenticator is capable of displaying it's own UI. + // So we assume here, it only ever returns one assertion. + // In case this doesn't hold true, we have to implement credential selection here, + // as is done for USB. &get_assertion_response.assertions[0], "cross-platform", ) From ae51f48b2cfa680df9bffcc1ee01c7456443b2e1 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 13 Jun 2025 14:22:37 +0200 Subject: [PATCH 4/6] Hash credential ID before sending it to the UI components --- .../src/credential_service/usb.rs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs index 5cb04d23..eb4d0d2a 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/usb.rs @@ -210,7 +210,15 @@ impl InProcessUsbHandler { .find(|c| { c.credential_id .as_ref() - .map(|c| URL_SAFE_NO_PAD.encode(&c.id) == cred_id) + .map(|c| { + // In order to not expose the credential ID to the untrusted UI component, + // we hashed it, before sending it. So we have to re-hash all our credential + // IDs to identify the selected one. + URL_SAFE_NO_PAD.encode(ring::digest::digest( + &ring::digest::SHA256, + &c.id, + )) == cred_id + }) .unwrap_or_default() }) .cloned(); @@ -452,8 +460,14 @@ impl From for UsbState { id: x .credential_id .as_ref() - .map(|i| URL_SAFE_NO_PAD.encode(&i.id)) - .unwrap_or_else(|| String::from("")), + .map(|i| { + // In order to not expose the credential ID to the untrusted UI components, + // we hash and then encode it into a String. + URL_SAFE_NO_PAD + .encode(ring::digest::digest(&ring::digest::SHA256, &i.id)) + }) + .unwrap(), + name: x .user .as_ref() From 9cf2e4fee9ff759d5b3275b0d10c4a2cdc7625eb Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 13 Jun 2025 14:48:33 +0200 Subject: [PATCH 5/6] Remove unneeded selected_credential infrastructure --- .../src/view_model/gtk/mod.rs | 12 ----------- .../src/view_model/mod.rs | 14 ------------- .../src/window.rs | 21 ------------------- 3 files changed, 47 deletions(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs index effb4b29..171ddfa8 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs @@ -39,9 +39,6 @@ mod imp { #[property(get, set)] pub selected_device: RefCell>, - #[property(get, set)] - pub selected_credential: RefCell>, - #[property(get, set)] pub usb_pin_entry_visible: RefCell, @@ -124,9 +121,6 @@ impl ViewModel { ViewUpdate::WaitingForDevice(device) => { view_model.waiting_for_device(&device) } - ViewUpdate::SelectCredential(cred_id) => { - view_model.select_credential(cred_id) - } ViewUpdate::UsbNeedsPin { attempts_left } => { let prompt = match attempts_left { Some(1) => { @@ -298,18 +292,12 @@ impl ViewModel { } let device_object: DeviceObject = device.into(); self.set_selected_device(device_object); - self.set_selected_credential(""); } fn selecting_device(&self) { self.set_prompt("Multiple devices found. Please select with which to proceed."); } - fn select_credential(&self, cred_id: String) { - // todo: Do we still need this? - self.set_selected_credential(cred_id); - } - pub async fn send_thingy(&self) { self.send_event(ViewEvent::ButtonClicked).await; } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs index a3a835f3..27ee2657 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs @@ -28,7 +28,6 @@ where // This includes devices like platform authenticator, USB, hybrid devices: Vec, selected_device: Option, - selected_credential: Option, providers: Vec, @@ -59,7 +58,6 @@ impl ViewModel { title: String::default(), devices: Vec::new(), selected_device: None, - selected_credential: None, providers: Vec::new(), usb_pin_tx: None, usb_cred_tx: None, @@ -154,11 +152,6 @@ impl ViewModel { todo!(); } }; - // Remove the attribute below when we implement cancellation for at least one transport. - #[allow(unreachable_code)] - { - self.selected_credential = None; - } } // start discovery for newly selected device @@ -280,18 +273,12 @@ impl ViewModel { "Credential selected: {:?}. Current Device: {:?}", cred_id, self.selected_device ); - // TODO: Do we still need this? - self.selected_credential = Some(cred_id.clone()); if let Some(cred_tx) = self.usb_cred_tx.take() { if cred_tx.lock().await.send(cred_id.clone()).await.is_err() { error!("Failed to send selected credential to device"); } } - self.tx_update - .send(ViewUpdate::SelectCredential(cred_id)) - .await - .unwrap(); } Event::Background(BackgroundEvent::UsbPressed) => { @@ -400,7 +387,6 @@ pub enum ViewUpdate { SetDevices(Vec), SetCredentials(Vec), WaitingForDevice(Device), - SelectCredential(String), UsbNeedsPin { attempts_left: Option }, UsbNeedsUserVerification { attempts_left: Option }, UsbNeedsUserPresence, diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs index cd500d81..961cb035 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs @@ -183,27 +183,6 @@ impl ExampleApplicationWindow { } )); - view_model.connect_selected_credential_notify(clone!( - #[weak] - stack, - move |vm| { - let c = vm.selected_credential(); - if c.is_none() || c.unwrap().is_empty() { - return; - } - - let d = vm.selected_device(); - let d = d - .and_downcast_ref::() - .expect("selected device to exist at notify"); - match d.transport().try_into() { - Ok(Transport::Usb) => stack.set_visible_child_name("usb"), - Ok(Transport::HybridQr) => stack.set_visible_child_name("hybrid_qr"), - _ => {} - }; - } - )); - view_model.connect_completed_notify(clone!( #[weak] stack, From dbce93255157b62ed5a741411ebb002ca462eb11 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Fri, 13 Jun 2025 12:07:39 -0500 Subject: [PATCH 6/6] Remove unnecessary TODO comment --- xyz-iinuwa-credential-manager-portal-gtk/src/window.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs index 961cb035..2ec2f531 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs @@ -163,9 +163,6 @@ impl ExampleApplicationWindow { .and_downcast_ref::() .expect("selected device to exist at notify"); match d.transport().try_into() { - // TODO: Can multiple resident_keys exist on USB for same origin? - // If so, we need to transition this to choose_credential as well. - // For now, we'll skip it. Ok(Transport::Usb) => stack.set_visible_child_name("usb"), Ok(Transport::HybridQr) => stack.set_visible_child_name("hybrid_qr"), _ => {}