webauthn_rp

WebAuthn Level 3 RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

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 }