webauthn_rp

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

ser.rs (56295B)


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