Skip to content

Commit 9c4b96f

Browse files
feat(cable): fall back to a fresh QR when the peer offers no linking info
The state-assisted second leg of the cable example panicked with 'No known devices found' against any peer that didn't send linking info in its post-handshake message (GMS Fido for one). Detect the empty known-devices list and show a new QR for GetAssertion instead, so the example demonstrates a working two-ceremony flow regardless of the peer's state-assisted support.
1 parent df8842e commit 9c4b96f

1 file changed

Lines changed: 42 additions & 14 deletions

File tree

libwebauthn/examples/ceremony/webauthn_cable.rs

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
2021
use libwebauthn::transport::{Channel as _, Device};
2122
use 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

Comments
 (0)