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