1+ use libwebauthn:: UxUpdate ;
12use std:: error:: Error ;
23use std:: fmt:: Display ;
34use std:: io:: { self , Write } ;
45use std:: time:: Duration ;
56use text_io:: read;
7+ use tokio:: sync:: mpsc:: Receiver ;
68use tracing_subscriber:: { self , EnvFilter } ;
79
810use libwebauthn:: management:: BioEnrollment ;
9- use libwebauthn:: pin:: { UvProvider , StdinPromptPinProvider } ;
11+ use libwebauthn:: pin:: PinRequestReason ;
1012use libwebauthn:: proto:: ctap2:: { Ctap2 , Ctap2GetInfoResponse , Ctap2LastEnrollmentSampleStatus } ;
1113use libwebauthn:: transport:: hid:: list_devices;
1214use libwebauthn:: transport:: Device ;
@@ -21,6 +23,46 @@ fn setup_logging() {
2123 . init ( ) ;
2224}
2325
26+ async fn handle_updates ( mut state_recv : Receiver < UxUpdate > ) {
27+ while let Some ( update) = state_recv. recv ( ) . await {
28+ match update {
29+ UxUpdate :: PresenceRequired => println ! ( "Please touch your device!" ) ,
30+ UxUpdate :: UvRetry { attempts_left } => {
31+ print ! ( "UV failed." ) ;
32+ if let Some ( attempts_left) = attempts_left {
33+ print ! ( " You have {attempts_left} attempts left." ) ;
34+ }
35+ }
36+ UxUpdate :: PinRequired ( update) => {
37+ let mut attempts_str = String :: new ( ) ;
38+ if let Some ( attempts) = update. attempts_left {
39+ attempts_str = format ! ( ". You have {attempts} attempts left!" ) ;
40+ } ;
41+
42+ match update. reason {
43+ PinRequestReason :: RelyingPartyRequest => println ! ( "RP required a PIN." ) ,
44+ PinRequestReason :: AuthenticatorPolicy => {
45+ println ! ( "Your device requires a PIN." )
46+ }
47+ PinRequestReason :: FallbackFromUV => {
48+ println ! ( "UV failed too often and is blocked. Falling back to PIN." )
49+ }
50+ }
51+ print ! ( "PIN: Please enter the PIN for your authenticator{attempts_str}: " ) ;
52+ io:: stdout ( ) . flush ( ) . unwrap ( ) ;
53+ let pin_raw: String = read ! ( "{}\n " ) ;
54+
55+ if pin_raw. is_empty ( ) {
56+ println ! ( "PIN: No PIN provided, cancelling operation." ) ;
57+ update. cancel ( ) ;
58+ } else {
59+ let _ = update. send_pin ( & pin_raw) ;
60+ }
61+ }
62+ }
63+ }
64+ }
65+
2466#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
2567enum Operation {
2668 GetModality ,
@@ -103,13 +145,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
103145
104146 let devices = list_devices ( ) . await . unwrap ( ) ;
105147 println ! ( "Devices found: {:?}" , devices) ;
106- let mut pin_provider: Box < dyn UvProvider > = Box :: new ( StdinPromptPinProvider :: new ( ) ) ;
107148
108149 for mut device in devices {
109150 println ! ( "Selected HID authenticator: {}" , & device) ;
110- device. wink ( TIMEOUT ) . await ?;
151+ let ( mut channel, state_recv) = device. channel ( ) . await ?;
152+ channel. wink ( TIMEOUT ) . await ?;
153+
154+ tokio:: spawn ( handle_updates ( state_recv) ) ;
111155
112- let mut channel = device. channel ( ) . await ?;
113156 let info = channel. ctap2_get_info ( ) . await ?;
114157 let options = get_supported_options ( & info) ;
115158
@@ -131,15 +174,12 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
131174 . await
132175 . map ( |x| format ! ( "{x:?}" ) ) ,
133176 Operation :: EnumerateEnrollments => channel
134- . get_bio_enrollments ( & mut pin_provider , TIMEOUT )
177+ . get_bio_enrollments ( TIMEOUT )
135178 . await
136179 . map ( |x| format ! ( "{x:?}" ) ) ,
137180 Operation :: RemoveEnrollment => {
138181 let enrollments = loop {
139- match channel
140- . get_bio_enrollments ( & mut pin_provider, TIMEOUT )
141- . await
142- {
182+ match channel. get_bio_enrollments ( TIMEOUT ) . await {
143183 Ok ( r) => break r,
144184 Err ( WebAuthnError :: Ctap ( ctap_error) ) => {
145185 if ctap_error. is_retryable_user_error ( ) {
@@ -159,18 +199,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
159199 channel
160200 . remove_bio_enrollment (
161201 & enrollments[ idx] . template_id . as_ref ( ) . unwrap ( ) ,
162- & mut pin_provider,
163202 TIMEOUT ,
164203 )
165204 . await
166205 . map ( |x| format ! ( "{x:?}" ) )
167206 }
168207 Operation :: RenameEnrollment => {
169208 let enrollments = loop {
170- match channel
171- . get_bio_enrollments ( & mut pin_provider, TIMEOUT )
172- . await
173- {
209+ match channel. get_bio_enrollments ( TIMEOUT ) . await {
174210 Ok ( r) => break r,
175211 Err ( WebAuthnError :: Ctap ( ctap_error) ) => {
176212 if ctap_error. is_retryable_user_error ( ) {
@@ -194,36 +230,28 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
194230 . rename_bio_enrollment (
195231 & enrollments[ idx] . template_id . as_ref ( ) . unwrap ( ) ,
196232 & new_name,
197- & mut pin_provider,
198233 TIMEOUT ,
199234 )
200235 . await
201236 . map ( |x| format ! ( "{x:?}" ) )
202237 }
203238 Operation :: AddNewEnrollment => {
204- let ( template_id, mut sample_status, mut remaining_samples) = match channel
205- . start_new_bio_enrollment ( & mut pin_provider , None , TIMEOUT )
206- . await
207- {
208- Ok ( r ) => r ,
209- Err ( WebAuthnError :: Ctap ( ctap_error ) ) => {
210- if ctap_error . is_retryable_user_error ( ) {
211- println ! ( "Oops, try again! Error: {}" , ctap_error ) ;
212- continue ;
239+ let ( template_id, mut sample_status, mut remaining_samples) =
240+ match channel . start_new_bio_enrollment ( None , TIMEOUT ) . await {
241+ Ok ( r ) => r ,
242+ Err ( WebAuthnError :: Ctap ( ctap_error ) ) => {
243+ if ctap_error . is_retryable_user_error ( ) {
244+ println ! ( "Oops, try again! Error: {}" , ctap_error ) ;
245+ continue ;
246+ }
247+ break Err ( WebAuthnError :: Ctap ( ctap_error ) ) ;
213248 }
214- break Err ( WebAuthnError :: Ctap ( ctap_error) ) ;
215- }
216- Err ( err) => break Err ( err) ,
217- } ;
249+ Err ( err) => break Err ( err) ,
250+ } ;
218251 while remaining_samples > 0 {
219252 print_status_update ( sample_status, remaining_samples) ;
220253 ( sample_status, remaining_samples) = match channel
221- . capture_next_bio_enrollment_sample (
222- & template_id,
223- & mut pin_provider,
224- None ,
225- TIMEOUT ,
226- )
254+ . capture_next_bio_enrollment_sample ( & template_id, None , TIMEOUT )
227255 . await
228256 {
229257 Ok ( r) => r,
0 commit comments