@@ -17,6 +17,7 @@ use libwebauthn::ops::webauthn::{
1717 DatFilePublicSuffixList , GetAssertionRequest , JsonFormat , MakeCredentialRequest , RequestOrigin ,
1818 WebAuthnIDL as _, WebAuthnIDLResponse as _,
1919} ;
20+ use libwebauthn:: transport:: cable:: channel:: CableChannel ;
2021use libwebauthn:: transport:: { Channel as _, Device } ;
2122use libwebauthn:: webauthn:: WebAuthn ;
2223
@@ -108,25 +109,53 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
108109 println ! ( "Waiting for 5 seconds before contacting the device..." ) ;
109110 sleep ( Duration :: from_secs ( 5 ) ) . await ;
110111
112+ // Second leg: prefer state-assisted reconnection if the peer offered
113+ // linking info, otherwise fall back to a fresh QR. Many authenticators
114+ // don't send linking info, so the fallback is the common path.
111115 let all_devices = device_info_store. list_all ( ) . await ;
112- let ( _known_device_id, known_device_info) =
113- all_devices. first ( ) . expect ( "No known devices found" ) ;
114-
115- let mut known_device: CableKnownDevice = CableKnownDevice :: new (
116- ClientPayloadHint :: GetAssertion ,
117- known_device_info,
118- device_info_store. clone ( ) ,
119- )
120- . await
121- . unwrap ( ) ;
116+ if let Some ( ( _, known_device_info) ) = all_devices. first ( ) {
117+ println ! ( "Reconnecting state-assisted to known device..." ) ;
118+ let mut known_device: CableKnownDevice = CableKnownDevice :: new (
119+ ClientPayloadHint :: GetAssertion ,
120+ known_device_info,
121+ device_info_store. clone ( ) ,
122+ )
123+ . await
124+ . unwrap ( ) ;
125+ let mut channel = known_device. channel ( ) . await . unwrap ( ) ;
126+ println ! ( "Channel established {:?}" , channel) ;
127+ run_get_assertion ( & mut channel, & request_origin, & psl) . await ?;
128+ } else {
129+ println ! ( "No known devices (peer did not offer linking). Falling back to QR." ) ;
130+ let mut device: CableQrCodeDevice = CableQrCodeDevice :: new_persistent (
131+ QrCodeOperationHint :: GetAssertionRequest ,
132+ device_info_store. clone ( ) ,
133+ CableTransports :: CloudAssistedOnly ,
134+ ) ?;
135+ let qr_code = QrCode :: new ( device. qr_code . to_string ( ) ) . unwrap ( ) ;
136+ let image = qr_code
137+ . render :: < unicode:: Dense1x2 > ( )
138+ . dark_color ( unicode:: Dense1x2 :: Light )
139+ . light_color ( unicode:: Dense1x2 :: Dark )
140+ . build ( ) ;
141+ println ! ( "{}" , image) ;
142+ let mut channel = device. channel ( ) . await . unwrap ( ) ;
143+ println ! ( "Channel established {:?}" , channel) ;
144+ run_get_assertion ( & mut channel, & request_origin, & psl) . await ?;
145+ }
122146
123- let mut channel = known_device . channel ( ) . await . unwrap ( ) ;
124- println ! ( "Channel established {:?}" , channel ) ;
147+ Ok ( ( ) )
148+ }
125149
150+ async fn run_get_assertion (
151+ channel : & mut CableChannel ,
152+ request_origin : & RequestOrigin ,
153+ psl : & DatFilePublicSuffixList ,
154+ ) -> Result < ( ) , Box < dyn Error > > {
126155 let state_recv = channel. get_ux_update_receiver ( ) ;
127156 tokio:: spawn ( common:: handle_cable_updates ( state_recv) ) ;
128157
129- let request = GetAssertionRequest :: from_json ( & request_origin, & psl, GET_ASSERTION_REQUEST )
158+ let request = GetAssertionRequest :: from_json ( request_origin, psl, GET_ASSERTION_REQUEST )
130159 . expect ( "Failed to parse request JSON" ) ;
131160 let response = retry_user_errors ! ( channel. webauthn_get_assertion( & request) ) . unwrap ( ) ;
132161 for assertion in & response. assertions {
@@ -135,6 +164,5 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
135164 . expect ( "Failed to serialize GetAssertion response" ) ;
136165 println ! ( "WebAuthn GetAssertion response (JSON):\n {assertion_json}" ) ;
137166 }
138-
139167 Ok ( ( ) )
140168}
0 commit comments