ser.rs (50100B)
1 #[cfg(test)] 2 mod tests; 3 use super::{ 4 super::{super::response::ser::Null, ser::PrfHelper}, 5 AllowedCredential, AllowedCredentials, Challenge, CredentialMediationRequirement, 6 CredentialUiMode, Credentials as _, DiscoverableAuthenticationClientState, 7 DiscoverableCredentialRequestOptions, Extension, ExtensionReq, FIVE_MINUTES, Hints, 8 NonDiscoverableAuthenticationClientState, NonDiscoverableCredentialRequestOptions, PrfInput, 9 PrfInputOwned, PublicKeyCredentialRequestOptions, RpId, UserVerificationRequirement, 10 }; 11 use core::{ 12 error::Error as E, 13 fmt::{self, Display, Formatter}, 14 num::NonZeroU32, 15 }; 16 use serde::{ 17 de::{Deserialize, Deserializer, Error, MapAccess, Unexpected, Visitor}, 18 ser::{Serialize, SerializeMap as _, SerializeStruct as _, Serializer}, 19 }; 20 /// `"immediate"`. 21 const IMMEDIATE: &str = "immediate"; 22 impl Serialize for CredentialUiMode { 23 /// Serializes `self` as a [`prim@str`] conforming with 24 /// [`CredentialUiMode`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialuimode). 25 /// 26 /// # Examples 27 /// 28 /// ``` 29 /// # use webauthn_rp::request::auth::CredentialUiMode; 30 /// assert_eq!( 31 /// serde_json::to_string(&CredentialUiMode::Immediate)?, 32 /// r#""immediate""# 33 /// ); 34 /// # Ok::<_, serde_json::Error>(()) 35 /// ``` 36 #[inline] 37 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 38 where 39 S: Serializer, 40 { 41 serializer.serialize_str(IMMEDIATE) 42 } 43 } 44 impl Serialize for PrfInputOwned { 45 /// See [`PrfInput::serialize`] 46 #[inline] 47 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 48 where 49 S: Serializer, 50 { 51 PrfInput { 52 first: self.first.as_slice(), 53 second: self.second.as_deref(), 54 } 55 .serialize(serializer) 56 } 57 } 58 impl Serialize for AllowedCredential { 59 /// Serializes `self` to conform with 60 /// [`PublicKeyCredentialDescriptorJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptorjson). 61 /// 62 /// # Examples 63 /// 64 /// ``` 65 /// # #[cfg(all(feature = "bin", feature = "custom"))] 66 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 67 /// # use webauthn_rp::{ 68 /// # request::{auth::AllowedCredential, PublicKeyCredentialDescriptor}, 69 /// # response::{AuthTransports, CredentialId}, 70 /// # }; 71 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 72 /// /// from the database. 73 /// # #[cfg(all(feature = "bin", feature = "custom"))] 74 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 75 /// // ⋮ 76 /// # AuthTransports::decode(32) 77 /// } 78 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 79 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 80 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 81 /// # #[cfg(all(feature = "bin", feature = "custom"))] 82 /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?; 83 /// # #[cfg(all(feature = "bin", feature = "custom"))] 84 /// let transports = get_transports((&id).into())?; 85 /// # #[cfg(all(feature = "bin", feature = "custom"))] 86 /// assert_eq!( 87 /// serde_json::to_string(&AllowedCredential::from(PublicKeyCredentialDescriptor { 88 /// id, 89 /// transports 90 /// })).unwrap(), 91 /// r#"{"type":"public-key","id":"AAAAAAAAAAAAAAAAAAAAAA","transports":["usb"]}"# 92 /// ); 93 /// # Ok::<_, webauthn_rp::AggErr>(()) 94 /// ``` 95 #[inline] 96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 97 where 98 S: Serializer, 99 { 100 self.credential.serialize(serializer) 101 } 102 } 103 impl Serialize for AllowedCredentials { 104 /// Serializes `self` to conform with 105 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-allowcredentials). 106 /// 107 /// # Examples 108 /// 109 /// ``` 110 /// # #[cfg(all(feature = "bin", feature = "custom"))] 111 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 112 /// # use webauthn_rp::{ 113 /// # request::{auth::AllowedCredentials, PublicKeyCredentialDescriptor, Credentials}, 114 /// # response::{AuthTransports, CredentialId}, 115 /// # }; 116 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 117 /// /// from the database. 118 /// # #[cfg(all(feature = "bin", feature = "custom"))] 119 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 120 /// // ⋮ 121 /// # AuthTransports::decode(32) 122 /// } 123 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 124 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 125 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 126 /// # #[cfg(all(feature = "bin", feature = "custom"))] 127 /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?; 128 /// # #[cfg(all(feature = "bin", feature = "custom"))] 129 /// let transports = get_transports((&id).into())?; 130 /// let mut creds = AllowedCredentials::with_capacity(1); 131 /// # #[cfg(all(feature = "bin", feature = "custom"))] 132 /// creds.push(PublicKeyCredentialDescriptor { id, transports }.into()); 133 /// # #[cfg(all(feature = "bin", feature = "custom"))] 134 /// assert_eq!( 135 /// serde_json::to_string(&creds).unwrap(), 136 /// r#"[{"type":"public-key","id":"AAAAAAAAAAAAAAAAAAAAAA","transports":["usb"]}]"# 137 /// ); 138 /// # Ok::<_, webauthn_rp::AggErr>(()) 139 /// ``` 140 #[inline] 141 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 142 where 143 S: Serializer, 144 { 145 self.creds.serialize(serializer) 146 } 147 } 148 /// [`evalByCredential`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfinputs-evalbycredential). 149 struct PrfCreds<'a>(&'a AllowedCredentials); 150 impl Serialize for PrfCreds<'_> { 151 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 152 where 153 S: Serializer, 154 { 155 serializer 156 .serialize_map(Some(self.0.prf_count)) 157 .and_then(|mut ser| { 158 self.0 159 .creds 160 .iter() 161 .try_fold((), |(), cred| { 162 cred.extension.prf.as_ref().map_or(Ok(()), |input| { 163 ser.serialize_entry(&cred.credential.id, input) 164 }) 165 }) 166 .and_then(|()| ser.end()) 167 }) 168 } 169 } 170 /// [`AuthenticationExtensionsPRFInputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfinputs). 171 struct PrfInputs<'a, 'b, 'c> { 172 /// [`eval`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfinputs-eval). 173 eval: Option<PrfInput<'a, 'b>>, 174 /// [`evalByCredential`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfinputs-evalbycredential). 175 eval_by_credential: PrfCreds<'c>, 176 } 177 impl Serialize for PrfInputs<'_, '_, '_> { 178 #[expect( 179 clippy::arithmetic_side_effects, 180 reason = "comment explains how overflow is not possible" 181 )] 182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 183 where 184 S: Serializer, 185 { 186 serializer 187 .serialize_struct( 188 "PrfInputs", 189 // The max is 1 + 1 = 2, so overflow is not an issue. 190 usize::from(self.eval.is_some()) 191 + usize::from(self.eval_by_credential.0.prf_count > 0), 192 ) 193 .and_then(|mut ser| { 194 self.eval 195 .map_or(Ok(()), |eval| ser.serialize_field("eval", &eval)) 196 .and_then(|()| { 197 if self.eval_by_credential.0.prf_count == 0 { 198 Ok(()) 199 } else { 200 ser.serialize_field("evalByCredential", &self.eval_by_credential) 201 } 202 }) 203 .and_then(|()| ser.end()) 204 }) 205 } 206 } 207 /// Serializes `self` to conform with 208 /// [`AuthenticationExtensionsClientInputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientinputsjson). 209 struct ExtensionHelper<'a, 'b, 'c> { 210 /// [`extension`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-extensions). 211 extension: &'a Extension<'b, 'c>, 212 /// [`extension`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-extensions). 213 /// 214 /// Some extensions contain records, so we need both this and above. 215 allow_credentials: &'a AllowedCredentials, 216 } 217 impl Serialize for ExtensionHelper<'_, '_, '_> { 218 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 219 where 220 S: Serializer, 221 { 222 let ext_count = 223 usize::from(self.extension.prf.is_some() || self.allow_credentials.prf_count > 0); 224 serializer 225 .serialize_struct("Extension", ext_count) 226 .and_then(|mut ser| { 227 if ext_count == 0 { 228 Ok(()) 229 } else { 230 ser.serialize_field( 231 "prf", 232 &PrfInputs { 233 eval: self.extension.prf.map(|prf| prf.0), 234 eval_by_credential: PrfCreds(self.allow_credentials), 235 }, 236 ) 237 } 238 .and_then(|()| ser.end()) 239 }) 240 } 241 } 242 /// `"challenge"` 243 const CHALLENGE: &str = "challenge"; 244 /// `"timeout"` 245 const TIMEOUT: &str = "timeout"; 246 /// `"rpId"` 247 const RP_ID: &str = "rpId"; 248 /// `"allowCredentials"` 249 const ALLOW_CREDENTIALS: &str = "allowCredentials"; 250 /// `"extensions"` 251 const EXTENSIONS: &str = "extensions"; 252 /// `"hints"` 253 const HINTS: &str = "hints"; 254 /// `"userVerification"` 255 const USER_VERIFICATION: &str = "userVerification"; 256 /// Helper type that peforms the serialization for both [`DiscoverableAuthenticationClientState`] and 257 /// [`NonDiscoverableAuthenticationClientState`] and 258 struct AuthenticationClientState<'rp_id, 'prf_first, 'prf_second, 'opt, 'cred>( 259 &'opt PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second>, 260 &'cred AllowedCredentials, 261 ); 262 impl Serialize for AuthenticationClientState<'_, '_, '_, '_, '_> { 263 #[inline] 264 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 265 where 266 S: Serializer, 267 { 268 serializer 269 .serialize_struct("PublicKeyCredentialRequestOptions", 7) 270 .and_then(|mut ser| { 271 ser.serialize_field(CHALLENGE, &self.0.challenge) 272 .and_then(|()| { 273 ser.serialize_field(TIMEOUT, &self.0.timeout) 274 .and_then(|()| { 275 ser.serialize_field(RP_ID, &self.0.rp_id).and_then(|()| { 276 ser.serialize_field(ALLOW_CREDENTIALS, &self.1) 277 .and_then(|()| { 278 ser.serialize_field( 279 USER_VERIFICATION, 280 &self.0.user_verification, 281 ) 282 .and_then( 283 |()| { 284 ser.serialize_field(HINTS, &self.0.hints) 285 .and_then(|()| { 286 ser.serialize_field( 287 EXTENSIONS, 288 &ExtensionHelper { 289 extension: &self.0.extensions, 290 allow_credentials: self.1, 291 }, 292 ) 293 .and_then(|()| ser.end()) 294 }) 295 }, 296 ) 297 }) 298 }) 299 }) 300 }) 301 }) 302 } 303 } 304 /// `"mediation"`. 305 const MEDIATION: &str = "mediation"; 306 /// `"uiMode"`. 307 const UI_MODE: &str = "uiMode"; 308 /// `"publicKey"`. 309 const PUBLIC_KEY: &str = "publicKey"; 310 impl Serialize for DiscoverableCredentialRequestOptions<'_, '_, '_> { 311 /// Serializes `self` to conform with 312 /// [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions). 313 /// 314 /// Note [`signal`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-signal) 315 /// is not present, and [`publicKey`](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry) 316 /// is serialized to conform to 317 /// [`PublicKeyCredentialRequestOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptionsjson). 318 /// Additionally [`uiMode`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-uiMode) 319 /// is not present iff [`Self::ui_mode`] is `None`. 320 #[inline] 321 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 322 where 323 S: Serializer, 324 { 325 serializer 326 .serialize_struct( 327 "DiscoverableCredentialRequestOptions", 328 if self.ui_mode.is_none() { 2 } else { 3 }, 329 ) 330 .and_then(|mut ser| { 331 ser.serialize_field(MEDIATION, &self.mediation) 332 .and_then(|()| { 333 if self.ui_mode.is_none() { 334 Ok(()) 335 } else { 336 ser.serialize_field(UI_MODE, &self.ui_mode) 337 } 338 .and_then(|()| { 339 ser.serialize_field( 340 PUBLIC_KEY, 341 &AuthenticationClientState( 342 &self.public_key, 343 &AllowedCredentials::with_capacity(0), 344 ), 345 ) 346 .and_then(|()| ser.end()) 347 }) 348 }) 349 }) 350 } 351 } 352 impl Serialize for NonDiscoverableCredentialRequestOptions<'_, '_, '_> { 353 /// Serializes `self` to conform with 354 /// [`CredentialRequestOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialrequestoptions). 355 /// 356 /// Note [`signal`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-signal) 357 /// and [`uiMode`](https://www.w3.org/TR/credential-management-1/#dom-credentialrequestoptions-uiMode) 358 /// are not present, and [`publicKey`](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry) 359 /// is serialized to conform to 360 /// [`PublicKeyCredentialRequestOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptionsjson). 361 #[inline] 362 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 363 where 364 S: Serializer, 365 { 366 serializer 367 .serialize_struct("NonDiscoverableCredentialRequestOptions", 2) 368 .and_then(|mut ser| { 369 ser.serialize_field(MEDIATION, &self.mediation) 370 .and_then(|()| { 371 ser.serialize_field( 372 PUBLIC_KEY, 373 &AuthenticationClientState(&self.options, &self.allow_credentials), 374 ) 375 .and_then(|()| ser.end()) 376 }) 377 }) 378 } 379 } 380 impl Serialize for DiscoverableAuthenticationClientState<'_, '_, '_> { 381 /// Serializes `self` according to [`DiscoverableCredentialRequestOptions::serialize`]. 382 /// 383 /// # Examples 384 /// 385 /// ``` 386 /// # use webauthn_rp::{ 387 /// # request::{ 388 /// # auth::{ 389 /// # AllowedCredential, AllowedCredentials, CredentialSpecificExtension, Extension, 390 /// # PrfInputOwned, DiscoverableCredentialRequestOptions 391 /// # }, 392 /// # AsciiDomain, ExtensionReq, Hints, PublicKeyCredentialHint, PrfInput, RpId, PublicKeyCredentialDescriptor, Credentials, UserVerificationRequirement, 393 /// # }, 394 /// # response::{AuthTransports, CredentialId}, 395 /// # }; 396 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 397 /// let mut options = DiscoverableCredentialRequestOptions::passkey(&rp_id); 398 /// options.public_key.hints = Hints::EMPTY.add(PublicKeyCredentialHint::SecurityKey); 399 /// options.public_key.extensions = Extension { 400 /// prf: Some((PrfInput { 401 /// first: [0; 4].as_slice(), 402 /// second: None, 403 /// }, ExtensionReq::Require)), 404 /// }; 405 /// let client_state = serde_json::to_string(&options.start_ceremony()?.1).unwrap(); 406 /// let json = serde_json::json!({ 407 /// "mediation":"required", 408 /// "publicKey":{ 409 /// "challenge":"AAAAAAAAAAAAAAAAAAAAAA", 410 /// "timeout":300000, 411 /// "rpId":"example.com", 412 /// "allowCredentials":[], 413 /// "userVerification":"required", 414 /// "hints":[ 415 /// "security-key" 416 /// ], 417 /// "extensions":{ 418 /// "prf":{ 419 /// "eval":{ 420 /// "first":"AAAAAA" 421 /// }, 422 /// } 423 /// } 424 /// } 425 /// }).to_string(); 426 /// // Since `Challenge`s are randomly generated, we don't know what it will be; thus 427 /// // we test the JSON string for everything except it. 428 /// # #[cfg(all(feature = "bin", feature = "custom"))] 429 /// assert_eq!(client_state.get(..50), json.get(..50)); 430 /// # #[cfg(all(feature = "bin", feature = "custom"))] 431 /// assert_eq!(client_state.get(72..), json.get(72..)); 432 /// # Ok::<_, webauthn_rp::AggErr>(()) 433 /// ``` 434 #[inline] 435 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 436 where 437 S: Serializer, 438 { 439 self.0.serialize(serializer) 440 } 441 } 442 impl Serialize for NonDiscoverableAuthenticationClientState<'_, '_, '_> { 443 /// Serializes `self` according to [`NonDiscoverableCredentialRequestOptions::serialize`]. 444 /// 445 /// # Examples 446 /// 447 /// ``` 448 /// # #[cfg(all(feature = "bin", feature = "custom"))] 449 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 450 /// # use webauthn_rp::{ 451 /// # request::{ 452 /// # auth::{ 453 /// # AllowedCredential, AllowedCredentials, CredentialSpecificExtension, Extension, 454 /// # PrfInputOwned, NonDiscoverableCredentialRequestOptions 455 /// # }, 456 /// # AsciiDomain, ExtensionReq, Hints, PublicKeyCredentialHint, PrfInput, RpId, PublicKeyCredentialDescriptor, Credentials, UserVerificationRequirement, 457 /// # }, 458 /// # response::{AuthTransports, CredentialId}, 459 /// # }; 460 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 461 /// /// from the database. 462 /// # #[cfg(all(feature = "bin", feature = "custom"))] 463 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 464 /// // ⋮ 465 /// # AuthTransports::decode(32) 466 /// } 467 /// let mut creds = AllowedCredentials::with_capacity(1); 468 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 469 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 470 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 471 /// # #[cfg(all(feature = "bin", feature = "custom"))] 472 /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?; 473 /// # #[cfg(all(feature = "bin", feature = "custom"))] 474 /// let transports = get_transports((&id).into())?; 475 /// # #[cfg(all(feature = "bin", feature = "custom"))] 476 /// creds.push(AllowedCredential { 477 /// credential: PublicKeyCredentialDescriptor { id, transports }, 478 /// extension: CredentialSpecificExtension { 479 /// prf: Some(PrfInputOwned { 480 /// first: vec![2; 6], 481 /// second: Some(vec![3; 2]), 482 /// ext_req: ExtensionReq::Require, 483 /// }), 484 /// }, 485 /// }); 486 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 487 /// # #[cfg(all(feature = "bin", feature = "custom"))] 488 /// let mut options = NonDiscoverableCredentialRequestOptions::second_factor(&rp_id, creds); 489 /// # #[cfg(all(feature = "bin", feature = "custom"))] 490 /// let opts = &mut options.options; 491 /// # #[cfg(not(all(feature = "bin", feature = "custom")))] 492 /// # let mut opts = webauthn_rp::DiscoverableCredentialRequestOptions::passkey(&rp_id).public_key; 493 /// opts.hints = Hints::EMPTY.add(PublicKeyCredentialHint::SecurityKey); 494 /// // This is actually useless since `CredentialSpecificExtension` takes priority 495 /// // when the client receives the payload. We set it for illustration purposes only. 496 /// // If `creds` contained an `AllowedCredential` that didn't set 497 /// // `CredentialSpecificExtension::prf`, then this would be used for it. 498 /// opts.extensions = Extension { 499 /// prf: Some((PrfInput { 500 /// first: [0; 4].as_slice(), 501 /// second: None, 502 /// }, ExtensionReq::Require)), 503 /// }; 504 /// // Since we are requesting the PRF extension, we must require user verification; otherwise 505 /// // `NonDiscoverableCredentialRequestOptions::start_ceremony` would error. 506 /// opts.user_verification = UserVerificationRequirement::Required; 507 /// # #[cfg(all(feature = "bin", feature = "custom"))] 508 /// let client_state = serde_json::to_string(&options.start_ceremony()?.1).unwrap(); 509 /// let json = serde_json::json!({ 510 /// "mediation":"required", 511 /// "publicKey":{ 512 /// "challenge":"AAAAAAAAAAAAAAAAAAAAAA", 513 /// "timeout":300000, 514 /// "rpId":"example.com", 515 /// "allowCredentials":[ 516 /// { 517 /// "type":"public-key", 518 /// "id":"AAAAAAAAAAAAAAAAAAAAAA", 519 /// "transports":["usb"] 520 /// } 521 /// ], 522 /// "userVerification":"required", 523 /// "hints":[ 524 /// "security-key" 525 /// ], 526 /// "extensions":{ 527 /// "prf":{ 528 /// "eval":{ 529 /// "first":"AAAAAA" 530 /// }, 531 /// "evalByCredential":{ 532 /// "AAAAAAAAAAAAAAAAAAAAAA":{ 533 /// "first":"AgICAgIC", 534 /// "second":"AwM" 535 /// } 536 /// } 537 /// } 538 /// } 539 /// } 540 /// }).to_string(); 541 /// // Since `Challenge`s are randomly generated, we don't know what it will be; thus 542 /// // we test the JSON string for everything except it. 543 /// # #[cfg(all(feature = "bin", feature = "custom"))] 544 /// assert_eq!(client_state.get(..50), json.get(..50)); 545 /// # #[cfg(all(feature = "bin", feature = "custom"))] 546 /// assert_eq!(client_state.get(72..), json.get(72..)); 547 /// # Ok::<_, webauthn_rp::AggErr>(()) 548 /// ``` 549 #[inline] 550 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 551 where 552 S: Serializer, 553 { 554 self.0.serialize(serializer) 555 } 556 } 557 impl<'de> Deserialize<'de> for CredentialUiMode { 558 /// Deserializes a [`prim@str`] based on 559 /// [`CredentialUiMode`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialuimode). 560 /// 561 /// # Examples 562 /// 563 /// ``` 564 /// # use webauthn_rp::request::auth::CredentialUiMode; 565 /// assert_eq!( 566 /// serde_json::from_str::<CredentialUiMode>(r#""immediate""#)?, 567 /// CredentialUiMode::Immediate, 568 /// ); 569 /// # Ok::<_, serde_json::Error>(()) 570 /// ``` 571 #[inline] 572 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 573 where 574 D: Deserializer<'de>, 575 { 576 struct CredentialUiModeVisitor; 577 impl Visitor<'_> for CredentialUiModeVisitor { 578 type Value = CredentialUiMode; 579 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 580 write!(formatter, "'{IMMEDIATE}'") 581 } 582 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 583 where 584 E: Error, 585 { 586 if v == IMMEDIATE { 587 Ok(CredentialUiMode::Immediate) 588 } else { 589 Err(E::invalid_value( 590 Unexpected::Str(v), 591 &format!("'{IMMEDIATE}'").as_str(), 592 )) 593 } 594 } 595 } 596 deserializer.deserialize_str(CredentialUiModeVisitor) 597 } 598 } 599 /// Similar to [`Extension`] except [`PrfInputOwned`] is used. 600 /// 601 /// This is primarily useful to assist [`ClientCredentialRequestOptions::deserialize`]. 602 #[derive(Debug, Default)] 603 pub struct ExtensionOwned { 604 /// See [`Extension::prf`]. 605 pub prf: Option<PrfInputOwned>, 606 } 607 impl ExtensionOwned { 608 /// Returns an `Extension` based on `self`. 609 #[inline] 610 #[must_use] 611 pub fn as_extension(&self) -> Extension<'_, '_> { 612 Extension { 613 prf: self.prf.as_ref().map(|prf| { 614 ( 615 PrfInput { 616 first: &prf.first, 617 second: prf.second.as_deref(), 618 }, 619 prf.ext_req, 620 ) 621 }), 622 } 623 } 624 /// Returns an `Extension` based on `self` and `prf`. 625 /// 626 /// Note `prf` is used _unconditionally_ regardless if [`Self::prf`] is `Some`. 627 #[inline] 628 #[must_use] 629 pub const fn with_prf<'prf_first, 'prf_second>( 630 &self, 631 prf: (PrfInput<'prf_first, 'prf_second>, ExtensionReq), 632 ) -> Extension<'prf_first, 'prf_second> { 633 Extension { prf: Some(prf) } 634 } 635 } 636 impl<'de> Deserialize<'de> for ExtensionOwned { 637 /// Deserializes a `struct` according to the following pseudo-schema: 638 /// 639 /// ```json 640 /// { 641 /// "prf": null | PRFJSON 642 /// } 643 /// // PRFJSON: 644 /// { 645 /// "eval": PRFInputs 646 /// } 647 /// // PRFInputs: 648 /// { 649 /// "first": <base64url-encoded string>, 650 /// "second": null | <base64url-encoded string> 651 /// } 652 /// ``` 653 /// 654 /// where the only required fields are `"eval"` and `"first"`. 655 /// 656 /// All extensions are not required to have a response sent back; but _if_ a response is sent back, its value 657 /// will be enforced. 658 /// 659 /// Unknown or duplicate fields lead to an error. 660 /// 661 /// # Examples 662 /// 663 /// ``` 664 /// # use webauthn_rp::request::{ExtensionReq, auth::ser::ExtensionOwned}; 665 /// let ext = serde_json::from_str::<ExtensionOwned>( 666 /// r#"{"prf":{"eval":{"first":"","second":null}}}"#, 667 /// )?; 668 /// assert!(ext.prf.map_or(false, |prf| prf.first.is_empty() 669 /// && prf.second.is_none() 670 /// && matches!(prf.ext_req, ExtensionReq::Allow))); 671 /// # Ok::<_, serde_json::Error>(()) 672 /// ``` 673 #[inline] 674 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 675 where 676 D: Deserializer<'de>, 677 { 678 /// `Visitor` for `ExtensionOwned`. 679 struct ExtensionOwnedVisitor; 680 impl<'d> Visitor<'d> for ExtensionOwnedVisitor { 681 type Value = ExtensionOwned; 682 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 683 formatter.write_str("ExtensionOwned") 684 } 685 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 686 where 687 A: MapAccess<'d>, 688 { 689 /// Field for `ExtensionOwned`. 690 struct Field; 691 impl<'e> Deserialize<'e> for Field { 692 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 693 where 694 D: Deserializer<'e>, 695 { 696 /// `Visitor` for `Field`. 697 struct FieldVisitor; 698 impl Visitor<'_> for FieldVisitor { 699 type Value = Field; 700 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 701 write!(formatter, "'{PRF}'") 702 } 703 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 704 where 705 E: Error, 706 { 707 if v == PRF { 708 Ok(Field) 709 } else { 710 Err(E::unknown_field(v, FIELDS)) 711 } 712 } 713 } 714 deserializer.deserialize_identifier(FieldVisitor) 715 } 716 } 717 map.next_key::<Field>().and_then(|opt_key| { 718 opt_key 719 .map_or_else( 720 || Ok(None), 721 |_k| { 722 map.next_value::<Option<PrfHelper>>().and_then(|prf| { 723 map.next_key::<Field>().and_then(|opt_key2| { 724 opt_key2.map_or_else( 725 || Ok(prf.map(|val| val.0)), 726 |_k2| Err(Error::duplicate_field(PRF)), 727 ) 728 }) 729 }) 730 }, 731 ) 732 .map(|prf| ExtensionOwned { prf }) 733 }) 734 } 735 } 736 /// `"prf"`. 737 const PRF: &str = "prf"; 738 /// Fields for `ExtensionOwned`. 739 const FIELDS: &[&str; 1] = &[PRF]; 740 deserializer.deserialize_struct("ExtensionOwned", FIELDS, ExtensionOwnedVisitor) 741 } 742 } 743 /// Error returned by [`PublicKeyCredentialRequestOptionsOwned::as_options`] when 744 /// [`PublicKeyCredentialRequestOptionsOwned::rp_id`] is `None`. 745 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 746 pub struct PublicKeyCredentialRequestOptionsOwnedErr; 747 impl Display for PublicKeyCredentialRequestOptionsOwnedErr { 748 #[inline] 749 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 750 f.write_str("request options did not have an RP ID") 751 } 752 } 753 impl E for PublicKeyCredentialRequestOptionsOwnedErr {} 754 /// Similar to [`PublicKeyCredentialRequestOptions`] except the fields are based on owned data, and 755 /// [`Self::rp_id`] is optional. 756 /// 757 /// This is primarily useful to assist [`ClientCredentialRequestOptions::deserialize`], 758 #[derive(Debug)] 759 pub struct PublicKeyCredentialRequestOptionsOwned { 760 /// See [`PublicKeyCredentialRequestOptions::rp_id`]. 761 pub rp_id: Option<RpId>, 762 /// See [`PublicKeyCredentialRequestOptions::timeout`]. 763 pub timeout: NonZeroU32, 764 /// See [`PublicKeyCredentialRequestOptions::user_verification`]. 765 pub user_verification: UserVerificationRequirement, 766 /// See [`PublicKeyCredentialRequestOptions::hints`]. 767 pub hints: Hints, 768 /// See [`PublicKeyCredentialRequestOptions::extensions`]. 769 pub extensions: ExtensionOwned, 770 } 771 impl PublicKeyCredentialRequestOptionsOwned { 772 /// Returns a `PublicKeyCredentialRequestOptions` based on `self`. 773 /// 774 /// # Errors 775 /// 776 /// Errors iff [`Self::rp_id`] is `None`. 777 #[inline] 778 pub fn as_options( 779 &self, 780 ) -> Result< 781 PublicKeyCredentialRequestOptions<'_, '_, '_>, 782 PublicKeyCredentialRequestOptionsOwnedErr, 783 > { 784 self.rp_id 785 .as_ref() 786 .ok_or(PublicKeyCredentialRequestOptionsOwnedErr) 787 .map(|rp_id| PublicKeyCredentialRequestOptions { 788 challenge: Challenge::new(), 789 timeout: self.timeout, 790 rp_id, 791 user_verification: self.user_verification, 792 hints: self.hints, 793 extensions: self.extensions.as_extension(), 794 }) 795 } 796 /// Returns a `PublicKeyCredentialRequestOptions` based on `self` and `rp_id`. 797 /// 798 /// Note `rp_id` is used _unconditionally_ regardless if [`Self::rp_id`] is `Some`. 799 #[inline] 800 #[must_use] 801 pub fn with_rp_id<'rp_id>( 802 &self, 803 rp_id: &'rp_id RpId, 804 ) -> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> { 805 PublicKeyCredentialRequestOptions { 806 challenge: Challenge::new(), 807 timeout: self.timeout, 808 rp_id, 809 user_verification: self.user_verification, 810 hints: self.hints, 811 extensions: self.extensions.as_extension(), 812 } 813 } 814 /// Returns a `PublicKeyCredentialRequestOptions` based on `self`, `exclude_credentials`, and `extensions`. 815 /// 816 /// Note `extensions` is used _unconditionally_ regardless of what [`Self::extensions`] is. 817 /// 818 /// # Errors 819 /// 820 /// Errors iff [`Self::rp_id`] is `None`. 821 #[inline] 822 pub fn with_extensions<'prf_first, 'prf_second>( 823 &self, 824 extensions: Extension<'prf_first, 'prf_second>, 825 ) -> Result< 826 PublicKeyCredentialRequestOptions<'_, 'prf_first, 'prf_second>, 827 PublicKeyCredentialRequestOptionsOwnedErr, 828 > { 829 self.rp_id 830 .as_ref() 831 .ok_or(PublicKeyCredentialRequestOptionsOwnedErr) 832 .map(|rp_id| PublicKeyCredentialRequestOptions { 833 challenge: Challenge::new(), 834 timeout: self.timeout, 835 rp_id, 836 user_verification: self.user_verification, 837 hints: self.hints, 838 extensions, 839 }) 840 } 841 /// Returns a `PublicKeyCredentialRequestOptions` based on `self`, `rp_id`, and `extensions`. 842 /// 843 /// Note `rp_id` and `extensions` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` or what 844 /// [`Self::extensions`] is. 845 #[inline] 846 #[must_use] 847 pub fn with_rp_id_and_extensions<'rp_id, 'prf_first, 'prf_second>( 848 &self, 849 rp_id: &'rp_id RpId, 850 extensions: Extension<'prf_first, 'prf_second>, 851 ) -> PublicKeyCredentialRequestOptions<'rp_id, 'prf_first, 'prf_second> { 852 PublicKeyCredentialRequestOptions { 853 challenge: Challenge::new(), 854 timeout: self.timeout, 855 rp_id, 856 user_verification: self.user_verification, 857 hints: self.hints, 858 extensions, 859 } 860 } 861 } 862 impl Default for PublicKeyCredentialRequestOptionsOwned { 863 #[inline] 864 fn default() -> Self { 865 Self { 866 rp_id: None, 867 timeout: FIVE_MINUTES, 868 user_verification: UserVerificationRequirement::Preferred, 869 hints: Hints::default(), 870 extensions: ExtensionOwned::default(), 871 } 872 } 873 } 874 impl<'de> Deserialize<'de> for PublicKeyCredentialRequestOptionsOwned { 875 /// Deserializes a `struct` based on 876 /// [`PublicKeyCredentialRequestOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptionsjson). 877 /// 878 /// Note that none of the fields are required, and all are allowed to be `null`. 879 /// 880 /// If [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-challenge) 881 /// exists, it must be `null`. If 882 /// [`allowCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-allowcredentials) 883 /// exists, it must be `null` or empty. 884 /// 885 /// If [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-timeout) exists, 886 /// it must be `null` or positive. If `timeout` is missing or is `null`, then [`FIVE_MINUTES`] will be used. 887 /// 888 /// If `userVerification` is missing or is `null`, then [`UserVerificationRequirement::Required`] will be used. 889 /// 890 /// Unknown or duplicate fields lead to an error. 891 #[expect(clippy::too_many_lines, reason = "131 lines is fine")] 892 #[inline] 893 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 894 where 895 D: Deserializer<'de>, 896 { 897 /// `Visitor` for `PublicKeyCredentialRequestOptionsOwned`. 898 struct PublicKeyCredentialRequestOptionsOwnedVisitor; 899 impl<'d> Visitor<'d> for PublicKeyCredentialRequestOptionsOwnedVisitor { 900 type Value = PublicKeyCredentialRequestOptionsOwned; 901 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 902 formatter.write_str("PublicKeyCredentialRequestOptionsOwned") 903 } 904 #[expect(clippy::too_many_lines, reason = "104 lines is fine")] 905 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 906 where 907 A: MapAccess<'d>, 908 { 909 /// Field for `PublicKeyCredentialRequestOptionsOwned`. 910 enum Field { 911 /// `rpId`. 912 RpId, 913 /// `userVerification`. 914 UserVerification, 915 /// `challenge`. 916 Challenge, 917 /// `timeout`. 918 Timeout, 919 /// `allowCredentials`. 920 AllowCredentials, 921 /// `hints`. 922 Hints, 923 /// `extensions`. 924 Extensions, 925 } 926 impl<'e> Deserialize<'e> for Field { 927 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 928 where 929 D: Deserializer<'e>, 930 { 931 /// `Visitor` for `Field`. 932 struct FieldVisitor; 933 impl Visitor<'_> for FieldVisitor { 934 type Value = Field; 935 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 936 write!( 937 formatter, 938 "'{RP_ID}', '{USER_VERIFICATION}', '{CHALLENGE}', '{TIMEOUT}', '{ALLOW_CREDENTIALS}', '{HINTS}', or '{EXTENSIONS}'" 939 ) 940 } 941 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 942 where 943 E: Error, 944 { 945 match v { 946 RP_ID => Ok(Field::RpId), 947 USER_VERIFICATION => Ok(Field::UserVerification), 948 CHALLENGE => Ok(Field::Challenge), 949 TIMEOUT => Ok(Field::Timeout), 950 ALLOW_CREDENTIALS => Ok(Field::AllowCredentials), 951 HINTS => Ok(Field::Hints), 952 EXTENSIONS => Ok(Field::Extensions), 953 _ => Err(E::unknown_field(v, FIELDS)), 954 } 955 } 956 } 957 deserializer.deserialize_identifier(FieldVisitor) 958 } 959 } 960 let mut rp = None; 961 let mut user_veri = None; 962 let mut chall = None; 963 let mut time = None; 964 let mut allow = None; 965 let mut hint = None; 966 let mut ext = None; 967 while let Some(key) = map.next_key()? { 968 match key { 969 Field::RpId => { 970 if rp.is_some() { 971 return Err(Error::duplicate_field(RP_ID)); 972 } 973 rp = map.next_value::<Option<_>>().map(Some)?; 974 } 975 Field::UserVerification => { 976 if user_veri.is_some() { 977 return Err(Error::duplicate_field(USER_VERIFICATION)); 978 } 979 user_veri = map.next_value::<Option<_>>().map(Some)?; 980 } 981 Field::Challenge => { 982 if chall.is_some() { 983 return Err(Error::duplicate_field(CHALLENGE)); 984 } 985 chall = map.next_value::<Null>().map(Some)?; 986 } 987 Field::Timeout => { 988 if time.is_some() { 989 return Err(Error::duplicate_field(TIMEOUT)); 990 } 991 time = map.next_value::<Option<_>>().map(Some)?; 992 } 993 Field::AllowCredentials => { 994 if allow.is_some() { 995 return Err(Error::duplicate_field(ALLOW_CREDENTIALS)); 996 } 997 allow = map.next_value::<Option<[(); 0]>>().map(Some)?; 998 } 999 Field::Hints => { 1000 if hint.is_some() { 1001 return Err(Error::duplicate_field(HINTS)); 1002 } 1003 hint = map.next_value::<Option<_>>().map(Some)?; 1004 } 1005 Field::Extensions => { 1006 if ext.is_some() { 1007 return Err(Error::duplicate_field(EXTENSIONS)); 1008 } 1009 ext = map.next_value::<Option<_>>().map(Some)?; 1010 } 1011 } 1012 } 1013 Ok(PublicKeyCredentialRequestOptionsOwned { 1014 rp_id: rp.flatten(), 1015 user_verification: user_veri 1016 .flatten() 1017 .unwrap_or(UserVerificationRequirement::Preferred), 1018 timeout: time.flatten().unwrap_or(FIVE_MINUTES), 1019 extensions: ext.flatten().unwrap_or_default(), 1020 hints: hint.flatten().unwrap_or_default(), 1021 }) 1022 } 1023 } 1024 /// Fields for `PublicKeyCredentialRequestOptionsOwned`. 1025 const FIELDS: &[&str; 7] = &[ 1026 RP_ID, 1027 USER_VERIFICATION, 1028 CHALLENGE, 1029 TIMEOUT, 1030 ALLOW_CREDENTIALS, 1031 HINTS, 1032 EXTENSIONS, 1033 ]; 1034 deserializer.deserialize_struct( 1035 "PublicKeyCredentialRequestOptionsOwned", 1036 FIELDS, 1037 PublicKeyCredentialRequestOptionsOwnedVisitor, 1038 ) 1039 } 1040 } 1041 /// Deserializes client-supplied data to assist in the creation of [`DiscoverableCredentialRequestOptions`] 1042 /// and [`NonDiscoverableCredentialRequestOptions`]. 1043 /// 1044 /// It's common to tailor an authentication ceremony based on a user's environment. The options that should be 1045 /// used are then sent to the server. To facilitate this, [`Self::deserialize`] can be used to deserialize the data 1046 /// sent from the client. 1047 /// 1048 /// Note one may want to change some of the [`Extension`] data since [`ExtensionReq::Allow`] is unconditionally 1049 /// used. Read [`ExtensionOwned::deserialize`] for more information. 1050 /// 1051 /// Additionally, one may want to change the value of [`PublicKeyCredentialRequestOptions::rp_id`] since 1052 /// `"example.invalid"` is used in the event the RP ID was not supplied. 1053 #[derive(Debug)] 1054 pub struct ClientCredentialRequestOptions { 1055 /// See [`DiscoverableCredentialRequestOptions::mediation`] and 1056 /// [`NonDiscoverableCredentialRequestOptions::mediation`]. 1057 pub mediation: CredentialMediationRequirement, 1058 /// See [`DiscoverableCredentialRequestOptions::mediation`]. 1059 pub ui_mode: Option<CredentialUiMode>, 1060 /// See [`DiscoverableCredentialRequestOptions::public_key`] and 1061 /// See [`NonDiscoverableCredentialRequestOptions::options`]. 1062 pub public_key: PublicKeyCredentialRequestOptionsOwned, 1063 } 1064 impl<'de> Deserialize<'de> for ClientCredentialRequestOptions { 1065 /// Deserializes a `struct` according to the following pseudo-schema: 1066 /// 1067 /// ```json 1068 /// { 1069 /// "mediation": null | "required" | "conditional", 1070 /// "uiMode": null | "immediate", 1071 /// "publicKey": null | <PublicKeyCredentialRequestOptionsOwned> 1072 /// } 1073 /// ``` 1074 /// 1075 /// where none of the fields are required and `"publicKey"` is deserialized according to 1076 /// [`PublicKeyCredentialRequestOptionsOwned::deserialize`]. If any field is missing or is `null`, then 1077 /// the corresponding [`Default`] `impl` will be used. 1078 /// 1079 /// Unknown or duplicate fields lead to an error. 1080 #[inline] 1081 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1082 where 1083 D: Deserializer<'de>, 1084 { 1085 /// `Visitor` for `ClientCredentialRequestOptions`. 1086 struct ClientCredentialRequestOptionsVisitor; 1087 impl<'d> Visitor<'d> for ClientCredentialRequestOptionsVisitor { 1088 type Value = ClientCredentialRequestOptions; 1089 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1090 formatter.write_str("ClientCredentialRequestOptions") 1091 } 1092 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1093 where 1094 A: MapAccess<'d>, 1095 { 1096 /// Field in `ClientCredentialRequestOptions`. 1097 enum Field { 1098 /// `mediation`. 1099 Mediation, 1100 /// `uiMode`. 1101 UiMode, 1102 /// `publicKey` 1103 PublicKey, 1104 } 1105 impl<'e> Deserialize<'e> for Field { 1106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1107 where 1108 D: Deserializer<'e>, 1109 { 1110 /// `Visitor` for `Field`. 1111 struct FieldVisitor; 1112 impl Visitor<'_> for FieldVisitor { 1113 type Value = Field; 1114 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1115 write!(formatter, "'{MEDIATION}', '{UI_MODE}', or '{PUBLIC_KEY}'") 1116 } 1117 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1118 where 1119 E: Error, 1120 { 1121 match v { 1122 MEDIATION => Ok(Field::Mediation), 1123 UI_MODE => Ok(Field::UiMode), 1124 PUBLIC_KEY => Ok(Field::PublicKey), 1125 _ => Err(E::unknown_field(v, FIELDS)), 1126 } 1127 } 1128 } 1129 deserializer.deserialize_identifier(FieldVisitor) 1130 } 1131 } 1132 let mut med = None; 1133 let mut ui = None; 1134 let mut key = None; 1135 while let Some(k) = map.next_key()? { 1136 match k { 1137 Field::Mediation => { 1138 if med.is_some() { 1139 return Err(Error::duplicate_field(MEDIATION)); 1140 } 1141 med = map.next_value::<Option<_>>().map(Some)?; 1142 } 1143 Field::UiMode => { 1144 if ui.is_some() { 1145 return Err(Error::duplicate_field(UI_MODE)); 1146 } 1147 ui = map.next_value().map(Some)?; 1148 } 1149 Field::PublicKey => { 1150 if key.is_some() { 1151 return Err(Error::duplicate_field(PUBLIC_KEY)); 1152 } 1153 key = map.next_value::<Option<_>>().map(Some)?; 1154 } 1155 } 1156 } 1157 Ok(ClientCredentialRequestOptions { 1158 mediation: med.flatten().unwrap_or_default(), 1159 ui_mode: ui.flatten(), 1160 public_key: key.flatten().unwrap_or_default(), 1161 }) 1162 } 1163 } 1164 /// Fields for `ClientCredentialRequestOptions`. 1165 const FIELDS: &[&str; 3] = &[MEDIATION, UI_MODE, PUBLIC_KEY]; 1166 deserializer.deserialize_struct( 1167 "ClientCredentialRequestOptions", 1168 FIELDS, 1169 ClientCredentialRequestOptionsVisitor, 1170 ) 1171 } 1172 }