webauthn_rp

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

commit 9d4b0de0577d131bba754f47c096c6a70e2b3f8c
parent 06862a895a528d5842d3ec5ca4f10823755fb0fe
Author: Zack Newman <zack@philomathiclife.com>
Date:   Wed,  1 Apr 2026 09:05:14 -0600

remove PRECIS-enforcement

Diffstat:
MCargo.toml | 1-
MREADME.md | 69+++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/lib.rs | 111++++++++++++++++++++++++-------------------------------------------------------
Msrc/request.rs | 39+++++++++++++++++++--------------------
Msrc/request/register.rs | 538++++---------------------------------------------------------------------------
Msrc/request/register/bin.rs | 115++-----------------------------------------------------------------------------
Msrc/request/register/error.rs | 44++------------------------------------------
Msrc/request/register/ser.rs | 428++++++++++++++++++-------------------------------------------------------------
Msrc/response.rs | 6+++---
Msrc/response/ser.rs | 8++++----
10 files changed, 218 insertions(+), 1141 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -121,7 +121,6 @@ hashbrown = { version = "0.16.1", default-features = false } ml-dsa = { version = "0.1.0-rc.8", default-features = false } p256 = { version = "0.14.0-rc.8", default-features = false, features = ["ecdsa"] } p384 = { version = "0.14.0-rc.8", default-features = false, features = ["ecdsa"] } -precis-profiles = { version = "0.1.13", default-features = false } rand = { version = "0.10.0", default-features = false, features = ["thread_rng"] } rsa = { version = "0.10.0-rc.17", default-features = false, features = ["encoding", "sha2"] } serde = { version = "1.0.228", default-features = false, features = ["alloc"], optional = true } diff --git a/README.md b/README.md @@ -22,13 +22,13 @@ use webauthn_rp::{ AuthenticatedCredential64, DiscoverableAuthentication64, DiscoverableAuthenticationServerState, DiscoverableCredentialRequestOptions, CredentialCreationOptions64, RegisteredCredential64, Registration, RegistrationServerState64, - hash::hash_set::FixedCapHashSet, + hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, request::{ - AsciiDomainStatic, PublicKeyCredentialDescriptor, RpId, + PublicKeyCredentialDescriptor, RpId, auth::AuthenticationVerificationOptions, register::{ - Nickname, PublicKeyCredentialUserEntity64, RegistrationVerificationOptions, - UserHandle64, Username, + PublicKeyCredentialUserEntity64, RegistrationVerificationOptions, + UserHandle64, }, }, response::{ @@ -40,7 +40,7 @@ use webauthn_rp::{ use serde::de::{Deserialize, Deserializer}; use serde_json::Error as JsonErr; /// The RP ID our application uses. -const RP_ID: &RpId = &RpId::StaticDomain(AsciiDomainStatic::new("example.com").unwrap()); +const RP_ID: &RpId = &RpId::from_static_domain("example.com").unwrap(); /// The registration verification options. const REG_OPTS: &RegistrationVerificationOptions::<'static, 'static, &'static str, &'static str> = &RegistrationVerificationOptions::new(); /// The authentication verification options. @@ -84,12 +84,12 @@ impl From<AuthCeremonyErr> for AppErr { /// This gets sent from the user after an account is created on their side. The registration ceremony /// still has to be successfully completed for the account to be created server side. In the event of an error, /// the user should delete the created passkey since it won't be usable. -struct AccountReg<'a, 'b> { +struct AccountReg { registration: Registration, - user_name: Username<'a>, - user_display_name: Nickname<'b>, + user_name: String, + user_display_name: String, } -impl<'de: 'a + 'b, 'a, 'b> Deserialize<'de> for AccountReg<'a, 'b> { +impl<'de> Deserialize<'de> for AccountReg { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, @@ -103,18 +103,18 @@ impl<'de: 'a + 'b, 'a, 'b> Deserialize<'de> for AccountReg<'a, 'b> { /// already exist otherwise. This is similar to credential creation except a random `UserHandle64` is generated and /// will be used for subsequent credential registrations. fn start_account_creation( - reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, + reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, ) -> Result<Vec<u8>, AppErr> { let user_id = UserHandle64::new(); let (server, client) = - CredentialCreationOptions64::first_passkey_with_blank_user_info( - RP_ID, &user_id, + CredentialCreationOptions64::passkey( + RP_ID, PublicKeyCredentialUserEntity64 { id: &user_id, name: "", display_name: "", }, Vec::new() ) .start_ceremony() .unwrap_or_else(|_e| { unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") }); - if reg_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) + if matches!(reg_ceremonies.insert_remove_all_expired(server), InsertRemoveExpired::Success) { Ok(serde_json::to_vec(&client) .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState64::serialize"))) @@ -131,10 +131,10 @@ fn start_account_creation( /// Note if this errors, then the user should be notified to delete the passkey created on their /// authenticator. fn finish_account_creation( - reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, + reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, client_data: &[u8], ) -> Result<(), AppErr> { - let account = serde_json::from_slice::<AccountReg<'_, '_>>(client_data)?; + let account = serde_json::from_slice::<AccountReg>(client_data)?; insert_account( &account, reg_ceremonies @@ -156,15 +156,15 @@ fn finish_account_creation( /// the authenticator. fn start_cred_registration( user_id: &UserHandle64, - reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, + reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, ) -> Result<Vec<u8>, AppErr> { - let (entity, creds) = select_user_info(user_id)?.ok_or(AppErr::NoAccount)?; - let (server, client) = CredentialCreationOptions64::passkey(RP_ID, entity, creds) + let (username, user_display_name, creds) = select_user_info(user_id)?.ok_or(AppErr::NoAccount)?; + let (server, client) = CredentialCreationOptions64::passkey(RP_ID, PublicKeyCredentialUserEntity64 { name: &username, id: user_id, display_name: &user_display_name, }, creds) .start_ceremony() .unwrap_or_else(|_e| { unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") }); - if reg_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) + if matches!(reg_ceremonies.insert_remove_all_expired(server), InsertRemoveExpired::Success) { Ok(serde_json::to_vec(&client) .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState64::serialize"))) @@ -181,7 +181,7 @@ fn start_cred_registration( /// Note if this errors, then the user should be notified to delete the passkey created on their /// authenticator. fn finish_cred_registration( - reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, + reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, client_data: &[u8], ) -> Result<(), AppErr> { // `Registration::from_json_custom` is available iff `serde_relaxed` is enabled. @@ -200,14 +200,14 @@ fn finish_cred_registration( } /// Starts the passkey authentication ceremony. fn start_auth( - auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>, + auth_ceremonies: &mut MaxLenHashSet<DiscoverableAuthenticationServerState>, ) -> Result<Vec<u8>, AppErr> { let (server, client) = DiscoverableCredentialRequestOptions::passkey(RP_ID) .start_ceremony() .unwrap_or_else(|_e| { unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") }); - if auth_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) + if matches!(auth_ceremonies.insert_remove_all_expired(server), InsertRemoveExpired::Success) { Ok(serde_json::to_vec(&client).unwrap_or_else(|_e| { unreachable!("bug in DiscoverableAuthenticationClientState::serialize") @@ -218,7 +218,7 @@ fn start_auth( } /// Finishes the passkey authentication ceremony. fn finish_auth( - auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>, + auth_ceremonies: &mut MaxLenHashSet<DiscoverableAuthenticationServerState>, client_data: &[u8], ) -> Result<(), AppErr> { // `DiscoverableAuthentication64::from_json_custom` is available iff `serde_relaxed` is enabled. @@ -252,7 +252,7 @@ fn finish_auth( /// Errors iff writing `account` or `cred` errors, there already exists a credential using the same /// `CredentialId`, or there already exists an account using the same `UserHandle64`. fn insert_account( - account: &AccountReg<'_, '_>, + account: &AccountReg, cred: RegisteredCredential64<'_>, ) -> Result<(), AppErr> { // ⋮ @@ -266,8 +266,9 @@ fn select_user_info( user_id: &UserHandle64, ) -> Result< Option<( - PublicKeyCredentialUserEntity64<'static, 'static, '_>, - Vec<PublicKeyCredentialDescriptor<Vec<u8>>>, + String, + String, + Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, )>, AppErr, > { @@ -330,9 +331,8 @@ unsuitable or only partially suitable (e.g., human-readable output is desired), [`custom`](#custom) to allow construction of certain types (e.g., `AuthenticatedCredential`). If possible and desired, one may wish to save the data "directly" to avoid any potential temporary allocations. -For example `StaticState::encode` will return a `Vec` containing hundreds (and possibly thousands in the -extreme case) of bytes if the underlying public key is an RSA key. This additional allocation and copy of data -is obviously avoided if `StaticState` is stored as a +For example `StaticState::encode` will return a `Vec` containing thousands of bytes if the underlying public key +is an ML-DSA key. This additional allocation and copy of data is obviously avoided if `StaticState` is stored as a [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its fields are stored in separate columns when written to a relational database (RDB). @@ -409,7 +409,7 @@ storing such data in memory: increasing. If data resides in memory, a monotonic `Instant` can be used instead. It is for those reasons data like `RegistrationServerState` are not serializable by default and require the -use of in-memory collections (e.g., `FixedCapHashSet`). To better ensure OOM is not a concern, RPs should set +use of in-memory collections (e.g., `MaxLenHashSet`). To better ensure OOM is not a concern, RPs should set reasonable timeouts. Since ceremonies can only be completed by moving data (e.g., `RegistrationServerState::verify`), ceremony completion is guaranteed to free up the memory used— `RegistrationServerState` instances are as small as 48 bytes on `x86_64-unknown-linux-gnu` platforms. To avoid @@ -425,6 +425,12 @@ not guaranteed to be the server that finishes the ceremony. The only supported signature algorithms are the following: +* ML-DSA-87 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This + corresponds to `CoseAlgorithmIdentifier::Mldsa87`. +* ML-DSA-65 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This + corresponds to `CoseAlgorithmIdentifier::Mldsa65`. +* ML-DSA-44 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This + corresponds to `CoseAlgorithmIdentifier::Mldsa44`. * Ed25519 as defined in [RFC 8032 § 5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1). This corresponds to `CoseAlgorithmIdentifier::Eddsa`. * ECDSA as defined in [SEC 1 Version 2.0 § 4.1](https://www.secg.org/sec1-v2.pdf#subsection.4.1) using SHA-256 @@ -445,8 +451,7 @@ the following ways: * [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form). * `Deserialize` implementations requiring _exact_ conformance (e.g., not allowing unknown data). * More thorough interrelated data validation (e.g., all places a Credential ID exists must match). -* Implement a lot of recommended (i.e., SHOULD) criteria (e.g., - [User display names conforming to the Nickname Profile as defined in RFC 8266](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name)). +* Implement a lot of recommended (i.e., SHOULD) criteria. Unfortunately like almost all software, this library has not been formally verified; however great care is employed in the following ways: diff --git a/src/lib.rs b/src/lib.rs @@ -27,8 +27,8 @@ //! PublicKeyCredentialDescriptor, RpId, //! auth::AuthenticationVerificationOptions, //! register::{ -//! DisplayName, PublicKeyCredentialUserEntity64, RegistrationVerificationOptions, -//! UserHandle64, Username, +//! PublicKeyCredentialUserEntity64, RegistrationVerificationOptions, +//! UserHandle64, //! }, //! }, //! response::{ @@ -88,13 +88,13 @@ //! /// This gets sent from the user after an account is created on their side. The registration ceremony //! /// still has to be successfully completed for the account to be created server side. In the event of an error, //! /// the user should delete the created passkey since it won't be usable. -//! struct AccountReg<'a, 'b> { +//! struct AccountReg { //! registration: Registration, -//! user_name: Username<'a>, -//! user_display_name: DisplayName<'b>, +//! user_name: String, +//! user_display_name: String, //! } //! # #[cfg(feature = "serde")] -//! impl<'de: 'a + 'b, 'a, 'b> Deserialize<'de> for AccountReg<'a, 'b> { +//! impl<'de> Deserialize<'de> for AccountReg { //! fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> //! where //! D: Deserializer<'de>, @@ -113,11 +113,9 @@ //! reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, //! ) -> Result<Vec<u8>, AppErr> { //! let user_id = UserHandle64::new(); -//! let user_name = Username::try_from("blank").unwrap(); -//! let user_display_name = DisplayName::Blank; //! let (server, client) = //! CredentialCreationOptions64::passkey( -//! RP_ID, PublicKeyCredentialUserEntity64 { id: &user_id, name: user_name, display_name: user_display_name, }, Vec::new() +//! RP_ID, PublicKeyCredentialUserEntity64 { id: &user_id, name: "", display_name: "", }, Vec::new() //! ) //! .start_ceremony() //! .unwrap_or_else(|_e| { @@ -144,7 +142,7 @@ //! reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, //! client_data: &[u8], //! ) -> Result<(), AppErr> { -//! let account = serde_json::from_slice::<AccountReg<'_, '_>>(client_data)?; +//! let account = serde_json::from_slice::<AccountReg>(client_data)?; //! insert_account( //! &account, //! reg_ceremonies @@ -169,8 +167,8 @@ //! user_id: &UserHandle64, //! reg_ceremonies: &mut MaxLenHashSet<RegistrationServerState64>, //! ) -> Result<Vec<u8>, AppErr> { -//! let (entity, creds) = select_user_info(user_id)?.ok_or(AppErr::NoAccount)?; -//! let (server, client) = CredentialCreationOptions64::passkey(RP_ID, entity, creds) +//! let (username, user_display_name, creds) = select_user_info(user_id)?.ok_or(AppErr::NoAccount)?; +//! let (server, client) = CredentialCreationOptions64::passkey(RP_ID, PublicKeyCredentialUserEntity64 { name: &username, id: user_id, display_name: &user_display_name, }, creds) //! .start_ceremony() //! .unwrap_or_else(|_e| { //! unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") @@ -266,7 +264,7 @@ //! /// Errors iff writing `account` or `cred` errors, there already exists a credential using the same //! /// `CredentialId`, or there already exists an account using the same `UserHandle64`. //! fn insert_account( -//! account: &AccountReg<'_, '_>, +//! account: &AccountReg, //! cred: RegisteredCredential64<'_>, //! ) -> Result<(), AppErr> { //! // ⋮ @@ -277,11 +275,12 @@ //! /// # Errors //! /// //! /// Errors iff fetching the data errors. -//! fn select_user_info<'name, 'display_name>( +//! fn select_user_info( //! user_id: &UserHandle64, //! ) -> Result< //! Option<( -//! PublicKeyCredentialUserEntity64<'name, 'display_name, '_>, +//! String, +//! String, //! Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, //! )>, //! AppErr, @@ -350,11 +349,10 @@ //! [`custom`](#custom) to allow construction of certain types (e.g., [`AuthenticatedCredential`]). //! //! If possible and desired, one may wish to save the data "directly" to avoid any potential temporary allocations. -//! For example [`StaticState::encode`] will return a [`Vec`] containing hundreds (and possibly thousands in the -//! extreme case) of bytes if the underlying public key is an RSA key. This additional allocation and copy of data -//! is obviously avoided if [`StaticState`] is stored as a -//! [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its fields are stored in separate -//! columns when written to a relational database (RDB). +//! For example [`StaticState::encode`] will return a [`Vec`] containing thousands of bytes if the underlying +//! public key is an ML-DSA key. This additional allocation and copy of data is obviously avoided if +//! [`StaticState`] is stored as a [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its +//! fields are stored in separate columns when written to a relational database (RDB). //! //! ### `custom` //! @@ -446,6 +444,12 @@ //! //! The only supported signature algorithms are the following: //! +//! * ML-DSA-87 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This +//! corresponds to [`CoseAlgorithmIdentifier::Mldsa87`]. +//! * ML-DSA-65 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This +//! corresponds to [`CoseAlgorithmIdentifier::Mldsa65`]. +//! * ML-DSA-44 as defined in [NIST FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf). This +//! corresponds to [`CoseAlgorithmIdentifier::Mldsa44`]. //! * Ed25519 as defined in [RFC 8032 § 5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1). This corresponds //! to [`CoseAlgorithmIdentifier::Eddsa`]. //! * ECDSA as defined in [SEC 1 Version 2.0 § 4.1](https://www.secg.org/sec1-v2.pdf#subsection.4.1) using SHA-256 @@ -466,8 +470,7 @@ //! * [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form). //! * `Deserialize` implementations requiring _exact_ conformance (e.g., not allowing unknown data). //! * More thorough interrelated data validation (e.g., all places a Credential ID exists must match). -//! * Implement a lot of recommended (i.e., SHOULD) criteria (e.g., -//! [User display names conforming to the Nickname Profile as defined in RFC 8266](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name)). +//! * Implement a lot of recommended (i.e., SHOULD) criteria. //! //! Unfortunately like almost all software, this library has not been formally verified; however great care is //! employed in the following ways: @@ -527,6 +530,11 @@ use crate::request::{ use crate::response::error::CredentialIdErr; #[cfg(feature = "serde_relaxed")] use crate::response::ser_relaxed::SerdeJsonErr; +#[cfg(feature = "bin")] +use crate::response::{ + bin::DecodeAuthTransportsErr, + register::bin::{DecodeDynamicStateErr, DecodeStaticStateErr}, +}; #[cfg(doc)] use crate::{ hash::hash_set::MaxLenHashSet, @@ -535,8 +543,8 @@ use crate::{ TimedCeremony, Url, auth::{AllowedCredential, AllowedCredentials, PublicKeyCredentialRequestOptions}, register::{ - CoseAlgorithmIdentifier, DisplayName, Nickname, PublicKeyCredentialCreationOptions, - PublicKeyCredentialUserEntity, UserHandle16, UserHandle64, Username, + CoseAlgorithmIdentifier, PublicKeyCredentialCreationOptions, + PublicKeyCredentialUserEntity, UserHandle16, UserHandle64, }, }, response::{ @@ -549,21 +557,12 @@ use crate::{ }, }, }; -#[cfg(feature = "bin")] -use crate::{ - request::register::bin::{DecodeDisplayNameErr, DecodeUsernameErr}, - response::{ - bin::DecodeAuthTransportsErr, - register::bin::{DecodeDynamicStateErr, DecodeStaticStateErr}, - }, -}; use crate::{ request::{ auth::error::{InvalidTimeout, NonDiscoverableCredentialRequestOptionsErr}, error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr}, register::{ - ResidentKeyRequirement, USER_HANDLE_MAX_LEN, UserHandle, - error::{CreationOptionsErr, NicknameErr, UsernameErr}, + ResidentKeyRequirement, USER_HANDLE_MAX_LEN, UserHandle, error::CreationOptionsErr, }, }, response::{ @@ -829,7 +828,7 @@ fn verify_static_and_dynamic_state<T>( /// Note that [`RpId`] and user information other than the `UserHandle` are not stored in `RegisteredCredential`. /// RPs that wish to store such information must do so on their own. Since user information is likely the same /// for a given `UserHandle` and `RpId` is likely static, it makes little sense to store such information -/// automatically. Types like [`Username`] implement `Encode` and `Decode` to assist such a thing. +/// automatically. /// /// When registering a credential, [`AttestedCredentialData::aaguid`], [`AttestedCredentialData::credential_id`], /// and [`AttestedCredentialData::credential_public_key`] will be the sources for [`Metadata::aaguid`], @@ -1140,10 +1139,6 @@ pub enum AggErr { CreationOptions(CreationOptionsErr), /// Variant when [`NonDiscoverableCredentialRequestOptions::start_ceremony`] errors. NonDiscoverableCredentialRequestOptions(NonDiscoverableCredentialRequestOptionsErr), - /// Variant when [`Nickname::try_from`] errors. - Nickname(NicknameErr), - /// Variant when [`Username::try_from`] errors. - Username(UsernameErr), /// Variant when [`RegistrationServerState::verify`] errors. RegCeremony(RegCeremonyErr), /// Variant when [`DiscoverableAuthenticationServerState::verify`] or. @@ -1172,12 +1167,6 @@ pub enum AggErr { /// Variant when [`DynamicState::decode`] errors. #[cfg(feature = "bin")] DecodeDynamicState(DecodeDynamicStateErr), - /// Variant when [`DisplayName::decode`] errors. - #[cfg(feature = "bin")] - DecodeDisplayName(DecodeDisplayNameErr), - /// Variant when [`Username::decode`] errors. - #[cfg(feature = "bin")] - DecodeUsername(DecodeUsernameErr), /// Variant when [`RegistrationServerState::decode`] errors. #[cfg(feature = "serializable_server_state")] DecodeRegistrationServerState(DecodeRegistrationServerStateErr), @@ -1263,18 +1252,6 @@ impl From<NonDiscoverableCredentialRequestOptionsErr> for AggErr { Self::NonDiscoverableCredentialRequestOptions(value) } } -impl From<NicknameErr> for AggErr { - #[inline] - fn from(value: NicknameErr) -> Self { - Self::Nickname(value) - } -} -impl From<UsernameErr> for AggErr { - #[inline] - fn from(value: UsernameErr) -> Self { - Self::Username(value) - } -} impl From<RegCeremonyErr> for AggErr { #[inline] fn from(value: RegCeremonyErr) -> Self { @@ -1345,20 +1322,6 @@ impl From<DecodeDynamicStateErr> for AggErr { Self::DecodeDynamicState(value) } } -#[cfg(feature = "bin")] -impl From<DecodeDisplayNameErr> for AggErr { - #[inline] - fn from(value: DecodeDisplayNameErr) -> Self { - Self::DecodeDisplayName(value) - } -} -#[cfg(feature = "bin")] -impl From<DecodeUsernameErr> for AggErr { - #[inline] - fn from(value: DecodeUsernameErr) -> Self { - Self::DecodeUsername(value) - } -} #[cfg(feature = "serializable_server_state")] impl From<DecodeRegistrationServerStateErr> for AggErr { #[inline] @@ -1427,8 +1390,6 @@ impl Display for AggErr { Self::InvalidTimeout(err) => err.fmt(f), Self::CreationOptions(err) => err.fmt(f), Self::NonDiscoverableCredentialRequestOptions(err) => err.fmt(f), - Self::Nickname(err) => err.fmt(f), - Self::Username(err) => err.fmt(f), Self::RegCeremony(ref err) => err.fmt(f), Self::AuthCeremony(ref err) => err.fmt(f), Self::AttestationObject(err) => err.fmt(f), @@ -1444,10 +1405,6 @@ impl Display for AggErr { Self::DecodeStaticState(err) => err.fmt(f), #[cfg(feature = "bin")] Self::DecodeDynamicState(err) => err.fmt(f), - #[cfg(feature = "bin")] - Self::DecodeDisplayName(err) => err.fmt(f), - #[cfg(feature = "bin")] - Self::DecodeUsername(err) => err.fmt(f), #[cfg(feature = "serializable_server_state")] Self::DecodeRegistrationServerState(err) => err.fmt(f), #[cfg(feature = "serializable_server_state")] diff --git a/src/request.rs b/src/request.rs @@ -106,7 +106,7 @@ pub mod error; /// # hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, /// # request::{ /// # register::{ -/// # CredentialCreationOptions, DisplayName, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64, +/// # CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MAX_LEN, UserHandle64, /// # }, /// # PublicKeyCredentialDescriptor, RpId /// # }, @@ -154,9 +154,9 @@ pub mod error; /// fn get_user_entity(user: &UserHandle64) -> Result<PublicKeyCredentialUserEntity<'_, '_, '_, USER_HANDLE_MAX_LEN>, AggErr> { /// // ⋮ /// # Ok(PublicKeyCredentialUserEntity { -/// # name: "foo".try_into()?, +/// # name: "foo", /// # id: user, -/// # display_name: DisplayName::Blank, +/// # display_name: "", /// # }) /// } /// /// Fetch the `PublicKeyCredentialDescriptor`s associated with `user`. @@ -1902,9 +1902,8 @@ mod tests { Extension as AuthExt, NonDiscoverableCredentialRequestOptions, PrfInputOwned, }, register::{ - CredProtect, CredentialCreationOptions, DisplayName, Extension as RegExt, - FourToSixtyThree, PublicKeyCredentialUserEntity, RegistrationVerificationOptions, - UserHandle, + CredProtect, CredentialCreationOptions, Extension as RegExt, FourToSixtyThree, + PublicKeyCredentialUserEntity, RegistrationVerificationOptions, UserHandle, }, }; use super::{AsciiDomainStatic, Hints, PublicKeyCredentialHint}; @@ -1991,9 +1990,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -2558,9 +2557,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -5425,9 +5424,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -7652,9 +7651,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -9244,9 +9243,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -9623,9 +9622,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -10039,9 +10038,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( RP_ID, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); diff --git a/src/request/register.rs b/src/request/register.rs @@ -1,4 +1,3 @@ -extern crate alloc; use super::{ super::{ DynamicState, Metadata, RegisteredCredential, StaticState, @@ -15,7 +14,7 @@ use super::{ BackupReq, Ceremony, Challenge, CredentialMediationRequirement, ExtensionInfo, ExtensionReq, FIVE_MINUTES, Hints, Origin, PrfInput, PublicKeyCredentialDescriptor, RpId, SentChallenge, TimedCeremony, UserVerificationRequirement, - register::error::{CreationOptionsErr, NicknameErr, UsernameErr}, + register::error::CreationOptionsErr, }; #[cfg(doc)] use crate::{ @@ -25,7 +24,6 @@ use crate::{ }, response::{AuthTransports, AuthenticatorTransport, Backup, CollectedClientData, Flag}, }; -use alloc::borrow::Cow; use core::{ borrow::Borrow, cmp::Ordering, @@ -35,7 +33,6 @@ use core::{ num::{NonZeroU32, NonZeroU64}, time::Duration, }; -use precis_profiles::{UsernameCasePreserved, precis_core::profile::Profile as _}; #[cfg(any(doc, not(feature = "serializable_server_state")))] use std::time::Instant; #[cfg(any(doc, feature = "serializable_server_state"))] @@ -190,500 +187,6 @@ impl Display for CredProtect { } } } -/// String returned from the [Nickname Enforcement rule](https://www.rfc-editor.org/rfc/rfc8266#section-2.3) -/// as defined in RFC 8266. -/// -/// Note [string truncation](https://www.w3.org/TR/webauthn-3/#sctn-strings-truncation) is allowed, so one may -/// want to enforce [`Self::RECOMMENDED_MAX_LEN`]. -#[derive(Clone, Debug)] -pub struct Nickname<'a>(Cow<'a, str>); -impl<'a> Nickname<'a> { - /// The maximum allowed length. - pub const MAX_LEN: usize = 1023; - /// The recommended maximum length to allow. - pub const RECOMMENDED_MAX_LEN: usize = 64; - /// Returns a `Nickname` that consumes `self`. When `self` owns the data, the data is simply moved; - /// when the data is borrowed, then it is cloned into an owned instance. - #[inline] - #[must_use] - pub fn into_owned<'b>(self) -> Nickname<'b> { - Nickname(Cow::Owned(self.0.into_owned())) - } - /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of - /// [`Self::MAX_LEN`]. - /// - /// # Errors - /// - /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3) - /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`]. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { - precis_profiles::Nickname::new() - .enforce(value) - .map_err(|_e| NicknameErr::Rfc8266) - .and_then(|val| { - if val.len() <= Self::RECOMMENDED_MAX_LEN { - Ok(Self(val)) - } else { - Err(NicknameErr::Len) - } - }) - } - /// Same as [`Self::try_from`]. - /// - /// # Errors - /// - /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3) - /// or the resulting length exceeds [`Self::MAX_LEN`]. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { - Self::try_from(value) - } -} -impl AsRef<str> for Nickname<'_> { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl Borrow<str> for Nickname<'_> { - #[inline] - fn borrow(&self) -> &str { - self.0.as_ref() - } -} -impl<'a: 'b, 'b> From<&'a Nickname<'_>> for Nickname<'b> { - #[inline] - fn from(value: &'a Nickname<'_>) -> Self { - match value.0 { - Cow::Borrowed(val) => Self(Cow::Borrowed(val)), - Cow::Owned(ref val) => Self(Cow::Borrowed(val.as_str())), - } - } -} -impl<'a: 'b, 'b> From<Nickname<'a>> for Cow<'b, str> { - #[inline] - fn from(value: Nickname<'a>) -> Self { - value.0 - } -} -impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for Nickname<'b> { - type Error = NicknameErr; - /// # Examples - /// - /// ``` - /// # use std::borrow::Cow; - /// # use webauthn_rp::request::register::{error::NicknameErr, Nickname}; - /// assert_eq!( - /// Nickname::try_from(Cow::Borrowed("Srinivasa Ramanujan"))?.as_ref(), - /// "Srinivasa Ramanujan" - /// ); - /// assert_eq!( - /// Nickname::try_from(Cow::Borrowed("श्रीनिवास रामानुजन्"))?.as_ref(), - /// "श्रीनिवास रामानुजन्" - /// ); - /// // Empty strings are not valid. - /// assert!(Nickname::try_from(Cow::Borrowed("")).map_or_else( - /// |e| matches!(e, NicknameErr::Rfc8266), - /// |_| false - /// )); - /// # Ok::<_, webauthn_rp::AggErr>(()) - /// ``` - #[inline] - fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> { - precis_profiles::Nickname::new() - .enforce(value) - .map_err(|_e| NicknameErr::Rfc8266) - .and_then(|val| { - if val.len() <= Self::MAX_LEN { - Ok(Self(val)) - } else { - Err(NicknameErr::Len) - } - }) - } -} -impl<'a: 'b, 'b> TryFrom<&'a str> for Nickname<'b> { - type Error = NicknameErr; - /// Same as [`Nickname::try_from`] except the input is a `str`. - #[inline] - fn try_from(value: &'a str) -> Result<Self, Self::Error> { - Self::try_from(Cow::Borrowed(value)) - } -} -impl TryFrom<String> for Nickname<'_> { - type Error = NicknameErr; - /// Same as [`Nickname::try_from`] except the input is a `String`. - #[inline] - fn try_from(value: String) -> Result<Self, Self::Error> { - Self::try_from(Cow::Owned(value)) - } -} -impl PartialEq<Nickname<'_>> for Nickname<'_> { - #[inline] - fn eq(&self, other: &Nickname<'_>) -> bool { - self.0 == other.0 - } -} -impl PartialEq<&Nickname<'_>> for Nickname<'_> { - #[inline] - fn eq(&self, other: &&Nickname<'_>) -> bool { - *self == **other - } -} -impl PartialEq<Nickname<'_>> for &Nickname<'_> { - #[inline] - fn eq(&self, other: &Nickname<'_>) -> bool { - **self == *other - } -} -impl Eq for Nickname<'_> {} -impl Hash for Nickname<'_> { - #[inline] - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.hash(state); - } -} -impl PartialOrd<Nickname<'_>> for Nickname<'_> { - #[inline] - fn partial_cmp(&self, other: &Nickname<'_>) -> Option<Ordering> { - self.0.partial_cmp(&other.0) - } -} -impl Ord for Nickname<'_> { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } -} -/// Name intended to be displayed to a user. -#[derive(Clone, Debug)] -pub enum DisplayName<'a> { - /// A blank string. - Blank, - /// A non-blank string conforming to RFC 8266. - Nickname(Nickname<'a>), -} -impl<'a> DisplayName<'a> { - /// The maximum allowed length. - pub const MAX_LEN: usize = 1023; - /// The recommended maximum length to allow. - pub const RECOMMENDED_MAX_LEN: usize = 64; - /// Returns a `DisplayName` that consumes `self`. When `self` owns the data, the data is simply moved; - /// when the data is borrowed, then it is cloned into an owned instance. - #[inline] - #[must_use] - pub fn into_owned<'b>(self) -> DisplayName<'b> { - match self { - Self::Blank => DisplayName::Blank, - Self::Nickname(val) => DisplayName::Nickname(val.into_owned()), - } - } - /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of - /// [`Self::MAX_LEN`]. - /// - /// # Errors - /// - /// Errors iff `value` is not empty and [`Nickname::with_recommended_len`] errors. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { - if value.is_empty() { - Ok(Self::Blank) - } else { - Nickname::with_recommended_len(value).map(Self::Nickname) - } - } - /// Same as [`Self::try_from`]. - /// - /// # Errors - /// - /// Errors iff `value` is not empty and [`Nickname::with_max_len`] errors. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { - Self::try_from(value) - } -} -impl AsRef<str> for DisplayName<'_> { - #[inline] - fn as_ref(&self) -> &str { - match *self { - Self::Blank => "", - Self::Nickname(ref val) => val.as_ref(), - } - } -} -impl Borrow<str> for DisplayName<'_> { - #[inline] - fn borrow(&self) -> &str { - self.as_ref() - } -} -impl<'a: 'b, 'b> From<&'a DisplayName<'_>> for DisplayName<'b> { - #[inline] - fn from(value: &'a DisplayName<'_>) -> Self { - match *value { - DisplayName::Blank => Self::Blank, - DisplayName::Nickname(ref val) => Self::Nickname(val.into()), - } - } -} -impl<'a: 'b, 'b> From<DisplayName<'a>> for Cow<'b, str> { - #[inline] - fn from(value: DisplayName<'a>) -> Self { - match value { - DisplayName::Blank => Cow::Borrowed(""), - DisplayName::Nickname(val) => val.into(), - } - } -} -impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for DisplayName<'b> { - type Error = NicknameErr; - /// # Examples - /// - /// ``` - /// # use std::borrow::Cow; - /// # use webauthn_rp::request::register::{error::NicknameErr, DisplayName}; - /// assert_eq!( - /// DisplayName::try_from(Cow::Borrowed(""))?.as_ref(), - /// "" - /// ); - /// assert_eq!( - /// DisplayName::try_from(Cow::Borrowed("Sir Isaac Newton"))?.as_ref(), - /// "Sir Isaac Newton" - /// ); - /// # Ok::<_, NicknameErr>(()) - /// ``` - #[inline] - fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> { - if value.is_empty() { - Ok(Self::Blank) - } else { - Nickname::try_from(value).map(Self::Nickname) - } - } -} -impl<'a: 'b, 'b> TryFrom<&'a str> for DisplayName<'b> { - type Error = NicknameErr; - /// Same as [`DisplayName::try_from`] except the input is a `str`. - #[inline] - fn try_from(value: &'a str) -> Result<Self, Self::Error> { - Self::try_from(Cow::Borrowed(value)) - } -} -impl TryFrom<String> for DisplayName<'_> { - type Error = NicknameErr; - /// Same as [`DisplayName::try_from`] except the input is a `String`. - #[inline] - fn try_from(value: String) -> Result<Self, Self::Error> { - Self::try_from(Cow::Owned(value)) - } -} -impl PartialEq<DisplayName<'_>> for DisplayName<'_> { - #[inline] - fn eq(&self, other: &DisplayName<'_>) -> bool { - self.as_ref() == other.as_ref() - } -} -impl PartialEq<&DisplayName<'_>> for DisplayName<'_> { - #[inline] - fn eq(&self, other: &&DisplayName<'_>) -> bool { - *self == **other - } -} -impl PartialEq<DisplayName<'_>> for &DisplayName<'_> { - #[inline] - fn eq(&self, other: &DisplayName<'_>) -> bool { - **self == *other - } -} -impl Eq for DisplayName<'_> {} -impl Hash for DisplayName<'_> { - #[inline] - fn hash<H: Hasher>(&self, state: &mut H) { - self.as_ref().hash(state); - } -} -impl PartialOrd<DisplayName<'_>> for DisplayName<'_> { - #[inline] - fn partial_cmp(&self, other: &DisplayName<'_>) -> Option<Ordering> { - Some(self.cmp(other)) - } -} -impl Ord for DisplayName<'_> { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - self.as_ref().cmp(other.as_ref()) - } -} -/// String returned from the -/// [UsernameCasePreserved Enforcement rule](https://www.rfc-editor.org/rfc/rfc8265#section-3.4.3) as defined in -/// RFC 8265. -/// -/// Note [string truncation](https://www.w3.org/TR/webauthn-3/#sctn-strings-truncation) is allowed, so one may -/// want to enforce [`Self::RECOMMENDED_MAX_LEN`]. -#[derive(Clone, Debug)] -pub struct Username<'a>(Cow<'a, str>); -impl<'a> Username<'a> { - /// The maximum allowed length. - pub const MAX_LEN: usize = 1023; - /// The recommended maximum length to allow. - pub const RECOMMENDED_MAX_LEN: usize = 64; - /// Returns a `Username` that consumes `self`. When `self` owns the data, the data is simply moved; - /// when the data is borrowed, then it is cloned into an owned instance. - #[inline] - #[must_use] - pub fn into_owned<'b>(self) -> Username<'b> { - Username(Cow::Owned(self.0.into_owned())) - } - /// Same as [`Self::with_max_len`] except the length must not exceed [`Self::RECOMMENDED_MAX_LEN`] instead of - /// [`Self::MAX_LEN`]. - /// - /// # Errors - /// - /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3) - /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`]. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> { - UsernameCasePreserved::default() - .enforce(value) - .map_err(|_e| UsernameErr::Rfc8265) - .and_then(|val| { - if val.len() <= Self::RECOMMENDED_MAX_LEN { - Ok(Self(val)) - } else { - Err(UsernameErr::Len) - } - }) - } - /// Same as [`Self::try_from`]. - /// - /// # Errors - /// - /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3) - /// or the resulting length exceeds [`Self::MAX_LEN`]. - #[expect(single_use_lifetimes, reason = "false positive")] - #[inline] - pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> { - Self::try_from(value) - } -} -impl AsRef<str> for Username<'_> { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -impl Borrow<str> for Username<'_> { - #[inline] - fn borrow(&self) -> &str { - self.0.as_ref() - } -} -impl<'a: 'b, 'b> TryFrom<Cow<'a, str>> for Username<'b> { - type Error = UsernameErr; - /// # Examples - /// - /// ``` - /// # use std::borrow::Cow; - /// # use webauthn_rp::request::register::{error::UsernameErr, Username}; - /// assert_eq!( - /// Username::try_from(Cow::Borrowed("leonhard.euler"))?.as_ref(), - /// "leonhard.euler" - /// ); - /// // Empty strings are not valid. - /// assert!(Username::try_from(Cow::Borrowed("")).map_or_else( - /// |e| matches!(e, UsernameErr::Rfc8265), - /// |_| false - /// )); - /// # Ok::<_, webauthn_rp::AggErr>(()) - /// ``` - #[inline] - fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> { - UsernameCasePreserved::default() - .enforce(value) - .map_err(|_e| UsernameErr::Rfc8265) - .and_then(|val| { - if val.len() <= Self::MAX_LEN { - Ok(Self(val)) - } else { - Err(UsernameErr::Len) - } - }) - } -} -impl<'a: 'b, 'b> TryFrom<&'a str> for Username<'b> { - type Error = UsernameErr; - /// Same as [`Username::try_from`] except the input is a `str`. - #[inline] - fn try_from(value: &'a str) -> Result<Self, Self::Error> { - Self::try_from(Cow::Borrowed(value)) - } -} -impl TryFrom<String> for Username<'_> { - type Error = UsernameErr; - /// Same as [`Username::try_from`] except the input is a `String`. - #[inline] - fn try_from(value: String) -> Result<Self, Self::Error> { - Self::try_from(Cow::Owned(value)) - } -} -impl<'a: 'b, 'b> From<Username<'a>> for Cow<'b, str> { - #[inline] - fn from(value: Username<'a>) -> Self { - value.0 - } -} -impl<'a: 'b, 'b> From<&'a Username<'_>> for Username<'b> { - #[inline] - fn from(value: &'a Username<'_>) -> Self { - match value.0 { - Cow::Borrowed(val) => Self(Cow::Borrowed(val)), - Cow::Owned(ref val) => Self(Cow::Borrowed(val.as_str())), - } - } -} -impl PartialEq<Username<'_>> for Username<'_> { - #[inline] - fn eq(&self, other: &Username<'_>) -> bool { - self.0 == other.0 - } -} -impl PartialEq<&Username<'_>> for Username<'_> { - #[inline] - fn eq(&self, other: &&Username<'_>) -> bool { - *self == **other - } -} -impl PartialEq<Username<'_>> for &Username<'_> { - #[inline] - fn eq(&self, other: &Username<'_>) -> bool { - **self == *other - } -} -impl Eq for Username<'_> {} -impl Hash for Username<'_> { - #[inline] - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.hash(state); - } -} -impl PartialOrd<Username<'_>> for Username<'_> { - #[inline] - fn partial_cmp(&self, other: &Username<'_>) -> Option<Ordering> { - self.0.partial_cmp(&other.0) - } -} -impl Ord for Username<'_> { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } -} /// [`COSEAlgorithmIdentifier`](https://www.w3.org/TR/webauthn-3/#typedefdef-cosealgorithmidentifier). /// /// Note the order of variants is the following: @@ -1292,11 +795,17 @@ impl UserHandle16 { #[derive(Clone, Debug)] pub struct PublicKeyCredentialUserEntity<'name, 'display_name, 'id, const LEN: usize> { /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name). - pub name: Username<'name>, + /// + /// Note the spec recommends RPs enforce + /// [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265#section-3.4.3). + pub name: &'name str, /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id). pub id: &'id UserHandle<LEN>, /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname). - pub display_name: DisplayName<'display_name>, + /// + /// Note the spec recommends RPs enforce [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266#section-2.3) + /// when this isn't empty. + pub display_name: &'display_name str, } /// `PublicKeyCredentialUserEntity` based on a [`UserHandle64`]. pub type PublicKeyCredentialUserEntity64<'name, 'display_name, 'id> = @@ -1576,9 +1085,9 @@ impl< /// CredentialCreationOptions::passkey( /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), /// PublicKeyCredentialUserEntity { - /// name: "bernard.riemann".try_into()?, + /// name: "bernard.riemann", /// id: &UserHandle64::new(), - /// display_name: "Georg Friedrich Bernhard Riemann".try_into()?, + /// display_name: "Georg Friedrich Bernhard Riemann", /// }, /// Vec::new() /// ).start_ceremony()?.0.expiration() > Instant::now() @@ -1766,9 +1275,9 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// PublicKeyCredentialCreationOptions::passkey( /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), /// PublicKeyCredentialUserEntity { - /// name: "archimedes.of.syracuse".try_into()?, + /// name: "archimedes.of.syracuse", /// id: &UserHandle64::new(), - /// display_name: "Αρχιμήδης ο Συρακούσιος".try_into()?, + /// display_name: "Αρχιμήδης ο Συρακούσιος", /// }, /// Vec::new() /// ) @@ -1837,9 +1346,9 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// PublicKeyCredentialCreationOptions::second_factor( /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), /// PublicKeyCredentialUserEntity { - /// name: "carl.gauss".try_into()?, + /// name: "carl.gauss", /// id: &UserHandle64::new(), - /// display_name: "Johann Carl Friedrich Gauß".try_into()?, + /// display_name: "Johann Carl Friedrich Gauß", /// }, /// Vec::new() /// ) @@ -1955,9 +1464,9 @@ impl< /// CredentialCreationOptions::passkey( /// &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), /// PublicKeyCredentialUserEntity { - /// name: "david.hilbert".try_into()?, + /// name: "david.hilbert", /// id: &UserHandle64::new(), - /// display_name: "David Hilbert".try_into()?, + /// display_name: "David Hilbert", /// }, /// Vec::new() /// ) @@ -2622,7 +2131,7 @@ mod tests { ))] use super::{ super::{super::AggErr, ExtensionInfo}, - Challenge, CredProtect, CredentialCreationOptions, DisplayName, FourToSixtyThree, PrfInput, + Challenge, CredProtect, CredentialCreationOptions, FourToSixtyThree, PrfInput, PublicKeyCredentialUserEntity, RpId, UserHandle, }; #[cfg(all(feature = "custom", feature = "serializable_server_state"))] @@ -2647,7 +2156,7 @@ mod tests { AuthTransports, }, AuthenticatorAttachment, BackupReq, ExtensionErr, ExtensionReq, RegCeremonyErr, - Registration, RegistrationVerificationOptions, UserVerificationRequirement, Username, + Registration, RegistrationVerificationOptions, UserVerificationRequirement, }; #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] use rsa::sha2::{Digest as _, Sha256}; @@ -2676,9 +2185,9 @@ mod tests { let mut opts = CredentialCreationOptions::passkey( &rp_id, PublicKeyCredentialUserEntity { - name: "foo".try_into()?, + name: "foo", id: &id, - display_name: DisplayName::Blank, + display_name: "", }, Vec::new(), ); @@ -3089,7 +2598,6 @@ mod tests { } attestation_object } - #[expect(clippy::unwrap_in_result, clippy::unwrap_used, reason = "OK in tests")] #[cfg(all(feature = "custom", not(any(feature = "bin", feature = "serde"))))] fn validate(options: TestOptions) -> Result<(), AggErr> { let rp_id = RpId::Domain("example.com".to_owned().try_into()?); @@ -3125,8 +2633,8 @@ mod tests { &rp_id, PublicKeyCredentialUserEntity { id: &user, - name: Username::try_from("blank").unwrap(), - display_name: DisplayName::Blank, + name: "", + display_name: "", }, Vec::new(), ); diff --git a/src/request/register/bin.rs b/src/request/register/bin.rs @@ -1,14 +1,8 @@ -extern crate alloc; use super::{ super::super::bin::{Decode, Encode}, - DisplayName, Nickname, NicknameErr, UserHandle, Username, UsernameErr, -}; -use alloc::borrow::Cow; -use core::{ - convert::Infallible, - error::Error, - fmt::{self, Display, Formatter}, + UserHandle, }; +use core::convert::Infallible; impl<const LEN: usize> Encode for UserHandle<LEN> { type Output<'a> = [u8; LEN] @@ -31,108 +25,3 @@ where Ok(Self(input)) } } -impl Encode for DisplayName<'_> { - type Output<'a> - = &'a str - where - Self: 'a; - type Err = Infallible; - #[inline] - fn encode(&self) -> Result<Self::Output<'_>, Self::Err> { - Ok(self.as_ref()) - } -} -/// Error returned from [`DisplayName::decode`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum DecodeDisplayNameErr { - /// Variant returned when the encoded data could not be decoded - /// into a [`DisplayName`]. - Nickname(NicknameErr), - /// Variant returned when the [`DisplayName`] was not encoded - /// into its canonical form. - NotCanonical, -} -impl Display for DecodeDisplayNameErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Self::Nickname(e) => e.fmt(f), - Self::NotCanonical => f.write_str("DisplayName was not encoded in its canonical form"), - } - } -} -impl Error for DecodeDisplayNameErr {} -impl<'b> Decode for DisplayName<'b> { - type Input<'a> = &'b str; - type Err = DecodeDisplayNameErr; - #[inline] - fn decode(input: Self::Input<'_>) -> Result<Self, Self::Err> { - match DisplayName::try_from(input).map_err(DecodeDisplayNameErr::Nickname) { - Ok(v) => match v { - DisplayName::Blank => Ok(Self::Blank), - DisplayName::Nickname(name) => match name.0 { - Cow::Borrowed(val) => { - if val == input { - Ok(Self::Nickname(Nickname(Cow::Borrowed(input)))) - } else { - Err(DecodeDisplayNameErr::NotCanonical) - } - } - Cow::Owned(_) => Err(DecodeDisplayNameErr::NotCanonical), - }, - }, - Err(e) => Err(e), - } - } -} -impl Encode for Username<'_> { - type Output<'a> - = &'a str - where - Self: 'a; - type Err = Infallible; - #[inline] - fn encode(&self) -> Result<Self::Output<'_>, Self::Err> { - Ok(self.as_ref()) - } -} -/// Error returned from [`Username::decode`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum DecodeUsernameErr { - /// Variant returned when the encoded data could not be decoded - /// into a [`Username`]. - Username(UsernameErr), - /// Variant returned when the [`Username`] was not encoded - /// into its canonical form. - NotCanonical, -} -impl Display for DecodeUsernameErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Self::Username(e) => e.fmt(f), - Self::NotCanonical => f.write_str("Username was not encoded in its canonical form"), - } - } -} -impl Error for DecodeUsernameErr {} -impl<'b> Decode for Username<'b> { - type Input<'a> = &'b str; - type Err = DecodeUsernameErr; - #[inline] - fn decode(input: Self::Input<'_>) -> Result<Self, Self::Err> { - match Username::try_from(input).map_err(DecodeUsernameErr::Username) { - Ok(v) => match v.0 { - Cow::Borrowed(name) => { - if name == input { - Ok(Self(Cow::Borrowed(input))) - } else { - Err(DecodeUsernameErr::NotCanonical) - } - } - Cow::Owned(_) => Err(DecodeUsernameErr::NotCanonical), - }, - Err(e) => Err(e), - } - } -} diff --git a/src/request/register/error.rs b/src/request/register/error.rs @@ -1,8 +1,8 @@ #[cfg(doc)] use super::{ - AuthenticatorSelectionCriteria, CredProtect, CredentialCreationOptions, Extension, Nickname, + AuthenticatorSelectionCriteria, CredProtect, CredentialCreationOptions, Extension, PublicKeyCredentialCreationOptions, USER_HANDLE_MAX_LEN, USER_HANDLE_MIN_LEN, UserHandle, - UserVerificationRequirement, Username, + UserVerificationRequirement, }; use core::{ error::Error, @@ -10,46 +10,6 @@ use core::{ }; #[cfg(doc)] use std::time::{Instant, SystemTime}; -/// Error returned by [`Nickname::try_from`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum NicknameErr { - /// Error returned when the [Nickname Enforcement rule](https://www.rfc-editor.org/rfc/rfc8266#section-2.3) - /// fails. - Rfc8266, - /// Error returned when the length of the transformed string would exceed [`Nickname::MAX_LEN`] or - /// [`Nickname::RECOMMENDED_MAX_LEN`]. - Len, -} -impl Display for NicknameErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::Rfc8266 => "nickname does not conform to RFC 8266", - Self::Len => "length of nickname is too long", - }) - } -} -impl Error for NicknameErr {} -/// Error returned by [`Username::try_from`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum UsernameErr { - /// Error returned when the - /// [UsernameCasePreserved Enforcement rule](https://www.rfc-editor.org/rfc/rfc8265#section-3.4.3) fails. - Rfc8265, - /// Error returned when the length of the transformed string would exceed [`Username::MAX_LEN`] or - /// [`Username::RECOMMENDED_MAX_LEN`]. - Len, -} -impl Display for UsernameErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::Rfc8265 => "username does not conform to RFC 8265", - Self::Len => "length of username is too long", - }) - } -} -impl Error for UsernameErr {} /// Error returned by [`CredentialCreationOptions::start_ceremony`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CreationOptionsErr { diff --git a/src/request/register/ser.rs b/src/request/register/ser.rs @@ -1,4 +1,3 @@ -extern crate alloc; use super::{ super::{ super::response::ser::{Null, Type}, @@ -7,19 +6,17 @@ use super::{ }, AuthenticatorAttachment, AuthenticatorSelectionCriteria, Challenge, CoseAlgorithmIdentifier, CoseAlgorithmIdentifiers, CredProtect, CredentialCreationOptions, - CredentialMediationRequirement, DisplayName, Extension, ExtensionInfo, ExtensionReq, - FIVE_MINUTES, FourToSixtyThree, Hints, Nickname, PrfInput, PublicKeyCredentialCreationOptions, + CredentialMediationRequirement, Extension, ExtensionInfo, ExtensionReq, FIVE_MINUTES, + FourToSixtyThree, Hints, PrfInput, PublicKeyCredentialCreationOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RegistrationClientState, - ResidentKeyRequirement, RpId, UserHandle, UserVerificationRequirement, Username, + ResidentKeyRequirement, RpId, UserHandle, UserVerificationRequirement, }; -use alloc::borrow::Cow; #[cfg(doc)] use core::str::FromStr; use core::{ convert, error::Error as E, fmt::{self, Display, Formatter}, - marker::PhantomData, num::NonZeroU32, str, }; @@ -27,69 +24,6 @@ use serde::{ de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Unexpected, Visitor}, ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer}, }; -impl Serialize for Nickname<'_> { - /// Serializes `self` as a [`prim@str`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::Nickname; - /// assert_eq!( - /// serde_json::to_string(&Nickname::try_from("Giuseppe Luigi Lagrangia")?).unwrap(), - /// r#""Giuseppe Luigi Lagrangia""# - /// ); - /// # Ok::<_, webauthn_rp::AggErr>(()) - /// ``` - #[inline] - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} -impl Serialize for DisplayName<'_> { - /// Serializes `self` as a [`prim@str`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::DisplayName; - /// assert_eq!( - /// serde_json::to_string(&DisplayName::try_from("Terence Tao")?).unwrap(), - /// r#""Terence Tao""# - /// ); - /// # Ok::<_, webauthn_rp::AggErr>(()) - /// ``` - #[inline] - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_str(self.as_ref()) - } -} -impl Serialize for Username<'_> { - /// Serializes `self` as a [`prim@str`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::Username; - /// assert_eq!( - /// serde_json::to_string(&Username::try_from("john.von.neumann")?).unwrap(), - /// r#""john.von.neumann""# - /// ); - /// # Ok::<_, webauthn_rp::AggErr>(()) - /// ``` - #[inline] - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} /// `"type"` const TYPE: &str = "type"; /// `"public-key"` @@ -316,7 +250,7 @@ where /// # Examples /// /// ``` - /// # use webauthn_rp::request::register::{DisplayName, PublicKeyCredentialUserEntity, UserHandle}; + /// # use webauthn_rp::request::register::{PublicKeyCredentialUserEntity, UserHandle}; /// # #[cfg(feature = "custom")] /// // We create this manually purely for example. One should almost always /// // randomly generate this (e.g., `UserHandle::new`). @@ -324,9 +258,9 @@ where /// # #[cfg(feature = "custom")] /// assert_eq!( /// serde_json::to_string(&PublicKeyCredentialUserEntity { - /// name: "georg.cantor".try_into()?, + /// name: "georg.cantor", /// id: &id, - /// display_name: "Гео́рг Ка́нтор".try_into()?, + /// display_name: "Гео́рг Ка́нтор", /// }).unwrap(), /// r#"{"name":"georg.cantor","id":"AA","displayName":"Гео́рг Ка́нтор"}"# /// ); @@ -335,9 +269,9 @@ where /// # #[cfg(feature = "custom")] /// assert_eq!( /// serde_json::to_string(&PublicKeyCredentialUserEntity { - /// name: "georg.cantor".try_into()?, + /// name: "georg.cantor", /// id: &id, - /// display_name: DisplayName::Blank, + /// display_name: "", /// }).unwrap(), /// r#"{"name":"georg.cantor","id":"AA","displayName":""}"# /// ); @@ -829,7 +763,7 @@ where /// creds.push(PublicKeyCredentialDescriptor { id, transports }); /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); /// let user_handle = UserHandle64::new(); - /// let mut options = CredentialCreationOptions::passkey(&rp_id, PublicKeyCredentialUserEntity { name: "pierre.de.fermat".try_into()?, id: &user_handle, display_name: "Pierre de Fermat".try_into()?, }, creds); + /// let mut options = CredentialCreationOptions::passkey(&rp_id, PublicKeyCredentialUserEntity { name: "pierre.de.fermat", id: &user_handle, display_name: "Pierre de Fermat", }, creds); /// options.public_key.authenticator_selection.authenticator_attachment = AuthenticatorAttachment::None; /// options.public_key.hints = Hints::EMPTY.add(PublicKeyCredentialHint::SecurityKey); /// options.public_key.extensions.min_pin_length = Some((FourToSixtyThree::Sixteen, ExtensionInfo::RequireEnforceValue)); @@ -924,139 +858,6 @@ where self.0.serialize(serializer) } } -impl<'de: 'a, 'a> Deserialize<'de> for Nickname<'a> { - /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::Nickname; - /// assert_eq!( - /// serde_json::from_str::<Nickname>(r#""Henri Poincaré""#)?.as_ref(), - /// "Henri Poincaré" - /// ); - /// # Ok::<_, serde_json::Error>(()) - ///``` - #[inline] - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - /// `Visitor` for `Nickname`. - struct NicknameVisitor<'b>(PhantomData<fn() -> &'b ()>); - impl<'d: 'b, 'b> Visitor<'d> for NicknameVisitor<'b> { - type Value = Nickname<'b>; - fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { - formatter.write_str("Nickname") - } - fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E> - where - E: Error, - { - Nickname::try_from(v).map_err(E::custom) - } - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, - { - Nickname::try_from(v) - .map_err(E::custom) - .map(|name| Nickname(Cow::Owned(name.0.into_owned()))) - } - } - deserializer.deserialize_str(NicknameVisitor(PhantomData)) - } -} -impl<'de: 'a, 'a> Deserialize<'de> for DisplayName<'a> { - /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::DisplayName; - /// assert_eq!( - /// serde_json::from_str::<DisplayName>(r#""Alexander Grothendieck""#)?.as_ref(), - /// "Alexander Grothendieck" - /// ); - /// # Ok::<_, serde_json::Error>(()) - ///``` - #[inline] - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - /// `Visitor` for `DisplayName`. - struct DisplayNameVisitor<'b>(PhantomData<fn() -> &'b ()>); - impl<'d: 'b, 'b> Visitor<'d> for DisplayNameVisitor<'b> { - type Value = DisplayName<'b>; - fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { - formatter.write_str("DisplayName") - } - fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E> - where - E: Error, - { - DisplayName::try_from(v).map_err(E::custom) - } - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, - { - if v.is_empty() { - Ok(DisplayName::Blank) - } else { - Nickname::try_from(v).map_err(E::custom).map(|name| { - DisplayName::Nickname(Nickname(Cow::Owned(name.0.into_owned()))) - }) - } - } - } - deserializer.deserialize_str(DisplayNameVisitor(PhantomData)) - } -} -impl<'de: 'a, 'a> Deserialize<'de> for Username<'a> { - /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`]. - /// - /// # Examples - /// - /// ``` - /// # use webauthn_rp::request::register::Username; - /// assert_eq!( - /// serde_json::from_str::<Username>(r#""augustin.cauchy""#)?.as_ref(), - /// "augustin.cauchy" - /// ); - /// # Ok::<_, serde_json::Error>(()) - ///``` - #[inline] - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - /// `Visitor` for `Username`. - struct UsernameVisitor<'b>(PhantomData<fn() -> &'b ()>); - impl<'d: 'b, 'b> Visitor<'d> for UsernameVisitor<'b> { - type Value = Username<'b>; - fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { - formatter.write_str("Username") - } - fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E> - where - E: Error, - { - Username::try_from(v).map_err(E::custom) - } - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: Error, - { - Username::try_from(v) - .map_err(E::custom) - .map(|name| Username(Cow::Owned(name.0.into_owned()))) - } - } - deserializer.deserialize_str(UsernameVisitor(PhantomData)) - } -} impl<'de, const LEN: usize> Deserialize<'de> for UserHandle<LEN> where Self: Default, @@ -1178,7 +979,7 @@ impl<'de> Deserialize<'de> for PublicKeyCredentialRpEntityHelper { /// ```json /// { /// "id": null | <RpId>, - /// "name": null | "" | <RpId> | <Nickname> + /// "name": null | <RpId> | <Nickname> /// } /// ``` /// @@ -1246,19 +1047,11 @@ impl<'de> Deserialize<'de> for PublicKeyCredentialRpEntityHelper { fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("RpId name") } - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + fn visit_str<E>(self, _: &str) -> Result<Self::Value, E> where E: Error, { - if v.is_empty() { - Ok(Name) - } else { - Nickname::try_from(v).map(|_n| Name).or_else(|_e| { - RpId::try_from(v.to_owned()) - .map_err(E::custom) - .map(|_r| Name) - }) - } + Ok(Name) } } deserializer.deserialize_str(NameVisitor) @@ -1299,13 +1092,13 @@ impl<'de> Deserialize<'de> for PublicKeyCredentialRpEntityHelper { /// /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`]. #[derive(Debug, Default)] -pub struct PublicKeyCredentialUserEntityOwned<'name, 'display_name, const LEN: usize> { +pub struct PublicKeyCredentialUserEntityOwned<const LEN: usize> { /// See [`PublicKeyCredentialUserEntity::name`]. - pub name: Option<Username<'name>>, + pub name: Option<String>, /// See [`PublicKeyCredentialUserEntity::id`]. pub id: Option<UserHandle<LEN>>, /// See [`PublicKeyCredentialUserEntity::display_name`]. - pub display_name: Option<DisplayName<'display_name>>, + pub display_name: Option<String>, } /// Error returned when converting a [`PublicKeyCredentialUserEntityOwned`] into a /// [`PublicKeyCredentialUserEntity`] (e.g., via [`PublicKeyCredentialUserEntityOwned::with_id`]). @@ -1329,7 +1122,7 @@ impl Display for PublicKeyCredentialUserEntityOwnedErr { } } impl E for PublicKeyCredentialUserEntityOwnedErr {} -impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> { +impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<LEN> { /// Returns a `PublicKeyCredentialUserEntity` based on `self`. /// /// # Errors @@ -1352,9 +1145,9 @@ impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> { .as_ref() .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName) .map(|display| PublicKeyCredentialUserEntity { - name: username.into(), + name: username, id, - display_name: display.into(), + display_name: display, }) }) }) @@ -1382,9 +1175,9 @@ impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> { .as_ref() .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName) .map(|display| PublicKeyCredentialUserEntity { - name: username.into(), + name: username, id, - display_name: display.into(), + display_name: display, }) }) } @@ -1399,8 +1192,8 @@ impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> { #[inline] pub fn with_name_and_display_name<'name, 'display_name>( &self, - name: Username<'name>, - display_name: DisplayName<'display_name>, + name: &'name str, + display_name: &'display_name str, ) -> Result< PublicKeyCredentialUserEntity<'name, 'display_name, '_, LEN>, PublicKeyCredentialUserEntityOwnedErr, @@ -1415,8 +1208,7 @@ impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> { }) } } -impl<'de: 'name + 'display_name, 'name, 'display_name, const LEN: usize> Deserialize<'de> - for PublicKeyCredentialUserEntityOwned<'name, 'display_name, LEN> +impl<'de, const LEN: usize> Deserialize<'de> for PublicKeyCredentialUserEntityOwned<LEN> where UserHandle<LEN>: Deserialize<'de>, { @@ -1426,10 +1218,10 @@ where /// Note none of the fields are required and all of them are allowed to be `null`. /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-id) is deserialized /// according to [`UserHandle::deserialize`], - /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-name) is deserialized - /// according to [`Username::deserialize`], and - /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-displayname) is - /// deserialized according to [`DisplayName::deserialize`]. + /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-name) deserializes + /// [`prim@str`]. + /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-displayname) + /// deserializes [`prim@str`]. /// /// Unknown or duplicate fields lead to an error. Missing fields are interpreted the same as if the field /// were assigned `null`. @@ -1438,9 +1230,9 @@ where /// /// ``` /// # use webauthn_rp::request::register::ser::PublicKeyCredentialUserEntityOwned; - /// let val = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 16>>(r#"{"name":"paul.erdos","displayName":"Erdős Pál"}"#)?; - /// assert!(val.name.is_some_and(|name| name.as_ref() == "paul.erdos")); - /// assert!(val.display_name.is_some_and(|display| display.as_ref() == "Erdős Pál")); + /// let val = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<16>>(r#"{"name":"paul.erdos","displayName":"Erdős Pál"}"#)?; + /// assert!(val.name.is_some_and(|name| name == "paul.erdos")); + /// assert!(val.display_name.is_some_and(|display| display == "Erdős Pál")); /// assert!(val.id.is_none()); /// # Ok::<_, serde_json::Error>(()) /// ``` @@ -1450,15 +1242,12 @@ where D: Deserializer<'de>, { /// `Visitor` for `PublicKeyCredentialUserEntityOwned`. - struct PublicKeyCredentialUserEntityOwnedVisitor<'a, 'b, const L: usize>( - PhantomData<fn() -> (&'a (), &'b ())>, - ); - impl<'d: 'a + 'b, 'a, 'b, const L: usize> Visitor<'d> - for PublicKeyCredentialUserEntityOwnedVisitor<'a, 'b, L> + struct PublicKeyCredentialUserEntityOwnedVisitor<const L: usize>; + impl<'d, const L: usize> Visitor<'d> for PublicKeyCredentialUserEntityOwnedVisitor<L> where UserHandle<L>: Deserialize<'d>, { - type Value = PublicKeyCredentialUserEntityOwned<'a, 'b, L>; + type Value = PublicKeyCredentialUserEntityOwned<L>; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("PublicKeyCredentialUserEntityOwned") } @@ -1539,7 +1328,7 @@ where deserializer.deserialize_struct( "PublicKeyCredentialUserEntityOwned", FIELDS, - PublicKeyCredentialUserEntityOwnedVisitor(PhantomData), + PublicKeyCredentialUserEntityOwnedVisitor, ) } } @@ -2473,15 +2262,11 @@ impl<'de> Deserialize<'de> for ExtensionOwned { /// /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`]. #[derive(Debug)] -pub struct PublicKeyCredentialCreationOptionsOwned< - 'user_name, - 'user_display_name, - const USER_LEN: usize, -> { +pub struct PublicKeyCredentialCreationOptionsOwned<const USER_LEN: usize> { /// See [`PublicKeyCredentialCreationOptions::rp_id`]. pub rp_id: Option<RpId>, /// See [`PublicKeyCredentialCreationOptions::user`]. - pub user: PublicKeyCredentialUserEntityOwned<'user_name, 'user_display_name, USER_LEN>, + pub user: PublicKeyCredentialUserEntityOwned<USER_LEN>, /// See [`PublicKeyCredentialCreationOptions::pub_key_cred_params`]. pub pub_key_cred_params: CoseAlgorithmIdentifiers, /// See [`PublicKeyCredentialCreationOptions::timeout`]. @@ -2519,7 +2304,7 @@ impl From<PublicKeyCredentialUserEntityOwnedErr> for PublicKeyCredentialCreation Self::UserEntity(value) } } -impl<const USER_LEN: usize> PublicKeyCredentialCreationOptionsOwned<'_, '_, USER_LEN> { +impl<const USER_LEN: usize> PublicKeyCredentialCreationOptionsOwned<USER_LEN> { /// Returns a `PublicKeyCredentialCreationOptions` based on `self` and `exclude_credentials`. /// /// # Errors @@ -2815,7 +2600,7 @@ impl<const USER_LEN: usize> PublicKeyCredentialCreationOptionsOwned<'_, '_, USER } } } -impl<const USER_LEN: usize> Default for PublicKeyCredentialCreationOptionsOwned<'_, '_, USER_LEN> { +impl<const USER_LEN: usize> Default for PublicKeyCredentialCreationOptionsOwned<USER_LEN> { #[inline] fn default() -> Self { Self { @@ -2833,11 +2618,10 @@ impl<const USER_LEN: usize> Default for PublicKeyCredentialCreationOptionsOwned< } } } -impl<'de: 'user_name + 'user_display_name, 'user_name, 'user_display_name, const USER_LEN: usize> - Deserialize<'de> - for PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN> +impl<'de, const USER_LEN: usize> Deserialize<'de> + for PublicKeyCredentialCreationOptionsOwned<USER_LEN> where - PublicKeyCredentialUserEntityOwned<'user_name, 'user_display_name, USER_LEN>: Deserialize<'de>, + PublicKeyCredentialUserEntityOwned<USER_LEN>: Deserialize<'de>, { /// Deserializes a `struct` based on /// [`PublicKeyCredentialCreationOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson). @@ -2871,15 +2655,12 @@ where D: Deserializer<'de>, { /// `Visitor` for `PublicKeyCredentialCreationOptionsOwned`. - struct PublicKeyCredentialCreationOptionsOwnedVisitor<'a, 'b, const LEN: usize>( - PhantomData<fn() -> (&'a (), &'b ())>, - ); - impl<'d: 'a + 'b, 'a, 'b, const LEN: usize> Visitor<'d> - for PublicKeyCredentialCreationOptionsOwnedVisitor<'a, 'b, LEN> + struct PublicKeyCredentialCreationOptionsOwnedVisitor<const LEN: usize>; + impl<'d, const LEN: usize> Visitor<'d> for PublicKeyCredentialCreationOptionsOwnedVisitor<LEN> where - PublicKeyCredentialUserEntityOwned<'a, 'b, LEN>: Deserialize<'d>, + PublicKeyCredentialUserEntityOwned<LEN>: Deserialize<'d>, { - type Value = PublicKeyCredentialCreationOptionsOwned<'a, 'b, LEN>; + type Value = PublicKeyCredentialCreationOptionsOwned<LEN>; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("PublicKeyCredentialCreationOptionsOwned") } @@ -3069,7 +2850,7 @@ where deserializer.deserialize_struct( "PublicKeyCredentialCreationOptionsOwned", FIELDS, - PublicKeyCredentialCreationOptionsOwnedVisitor(PhantomData), + PublicKeyCredentialCreationOptionsOwnedVisitor, ) } } @@ -3085,18 +2866,15 @@ where /// /// To facilitate this, [`Self::deserialize`] can be used to deserialize the data sent from the client. #[derive(Debug)] -pub struct ClientCredentialCreationOptions<'user_name, 'user_display_name, const USER_LEN: usize> { +pub struct ClientCredentialCreationOptions<const USER_LEN: usize> { /// See [`CredentialCreationOptions::mediation`]. pub mediation: CredentialMediationRequirement, /// See [`CredentialCreationOptions::public_key`]. - pub public_key: - PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN>, + pub public_key: PublicKeyCredentialCreationOptionsOwned<USER_LEN>, } -impl<'de: 'user_name + 'user_display_name, 'user_name, 'user_display_name, const USER_LEN: usize> - Deserialize<'de> for ClientCredentialCreationOptions<'user_name, 'user_display_name, USER_LEN> +impl<'de, const USER_LEN: usize> Deserialize<'de> for ClientCredentialCreationOptions<USER_LEN> where - PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN>: - Deserialize<'de>, + PublicKeyCredentialCreationOptionsOwned<USER_LEN>: Deserialize<'de>, { /// Deserializes a `struct` according to the following pseudo-schema: /// @@ -3118,15 +2896,12 @@ where D: Deserializer<'de>, { /// `Visitor` for `ClientCredentialCreationOptions`. - struct ClientCredentialCreationOptionsVisitor<'a, 'b, const LEN: usize>( - PhantomData<fn() -> (&'a (), &'b ())>, - ); - impl<'d: 'a + 'b, 'a, 'b, const LEN: usize> Visitor<'d> - for ClientCredentialCreationOptionsVisitor<'a, 'b, LEN> + struct ClientCredentialCreationOptionsVisitor<const LEN: usize>; + impl<'d, const LEN: usize> Visitor<'d> for ClientCredentialCreationOptionsVisitor<LEN> where - PublicKeyCredentialCreationOptionsOwned<'a, 'b, LEN>: Deserialize<'d>, + PublicKeyCredentialCreationOptionsOwned<LEN>: Deserialize<'d>, { - type Value = ClientCredentialCreationOptions<'a, 'b, LEN>; + type Value = ClientCredentialCreationOptions<LEN>; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("ClientCredentialCreationOptions") } @@ -3196,7 +2971,7 @@ where deserializer.deserialize_struct( "ClientCredentialCreationOptions", FIELDS, - ClientCredentialCreationOptionsVisitor(PhantomData), + ClientCredentialCreationOptionsVisitor, ) } } @@ -3223,13 +2998,13 @@ mod test { #[test] fn client_options() -> Result<(), Error> { let mut err = - serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 16>>(r#"{"bob":true}"#) + serde_json::from_str::<ClientCredentialCreationOptions<16>>(r#"{"bob":true}"#) .unwrap_err(); assert_eq!( err.to_string().get(..56), Some("unknown field `bob`, expected `mediation` or `publicKey`") ); - err = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>( + err = serde_json::from_str::<ClientCredentialCreationOptions<1>>( r#"{"mediation":"required","mediation":"required"}"#, ) .unwrap_err(); @@ -3237,7 +3012,7 @@ mod test { err.to_string().get(..27), Some("duplicate field `mediation`") ); - let mut options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>("{}")?; + let mut options = serde_json::from_str::<ClientCredentialCreationOptions<1>>("{}")?; assert!(matches!( options.mediation, CredentialMediationRequirement::Required @@ -3273,7 +3048,7 @@ mod test { )); assert!(options.public_key.extensions.min_pin_length.is_none()); assert!(options.public_key.extensions.prf.is_none()); - options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>( + options = serde_json::from_str::<ClientCredentialCreationOptions<1>>( r#"{"mediation":null,"publicKey":null}"#, )?; assert!(matches!( @@ -3311,9 +3086,8 @@ mod test { )); assert!(options.public_key.extensions.min_pin_length.is_none()); assert!(options.public_key.extensions.prf.is_none()); - options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>( - r#"{"publicKey":{}}"#, - )?; + options = + serde_json::from_str::<ClientCredentialCreationOptions<1>>(r#"{"publicKey":{}}"#)?; assert!(options.public_key.rp_id.is_none()); assert!(options.public_key.user.name.is_none()); assert!(options.public_key.user.id.is_none()); @@ -3345,7 +3119,7 @@ mod test { )); assert!(options.public_key.extensions.min_pin_length.is_none()); assert!(options.public_key.extensions.prf.is_none()); - options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>( + options = serde_json::from_str::<ClientCredentialCreationOptions<1>>( r#"{"mediation":"conditional","publicKey":{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}}"#, )?; assert!(matches!( @@ -3358,19 +3132,13 @@ mod test { .rp_id .is_some_and(|val| val.as_ref() == "example.com") ); - assert!( - options - .public_key - .user - .name - .is_some_and(|val| val.as_ref() == "bob") - ); + assert!(options.public_key.user.name.is_some_and(|val| val == "bob")); assert!( options .public_key .user .display_name - .is_some_and(|val| val.as_ref() == "Bob") + .is_some_and(|val| val == "Bob") ); assert!( options @@ -3447,17 +3215,16 @@ mod test { )] #[test] fn key_options() -> Result<(), Error> { - let mut err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 16>>( - r#"{"bob":true}"#, - ) - .unwrap_err(); + let mut err = + serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<16>>(r#"{"bob":true}"#) + .unwrap_err(); assert_eq!( err.to_string().get(..201), Some( "unknown field `bob`, expected one of `rp`, `user`, `challenge`, `pubKeyCredParams`, `timeout`, `excludeCredentials`, `authenticatorSelection`, `hints`, `extensions`, `attestation`, `attestationFormats`" ) ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"attestation":"none","attestation":"none"}"#, ) .unwrap_err(); @@ -3465,7 +3232,7 @@ mod test { err.to_string().get(..29), Some("duplicate field `attestation`") ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"challenge":"AAAAAAAAAAAAAAAAAAAAAA"}"#, ) .unwrap_err(); @@ -3473,12 +3240,12 @@ mod test { err.to_string().get(..41), Some("invalid type: Option value, expected null") ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"excludeCredentials":[{"type":"public-key","transports":["usb"],"id":"AAAAAAAAAAAAAAAAAAAAAA"}]}"#, ) .unwrap_err(); assert_eq!(err.to_string().get(..19), Some("trailing characters")); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"attestation":"foo"}"#, ) .unwrap_err(); @@ -3486,7 +3253,7 @@ mod test { err.to_string().get(..27), Some("invalid value: string \"foo\"") ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"attestationFormats":["none","none"]}"#, ) .unwrap_err(); @@ -3496,7 +3263,7 @@ mod test { "attestationFormats must be an empty sequence or contain exactly one string whose value is 'none'" ) ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"attestationFormats":["foo"]}"#, ) .unwrap_err(); @@ -3504,15 +3271,14 @@ mod test { err.to_string().get(..42), Some("invalid value: string \"foo\", expected none") ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( - r#"{"timeout":0}"#, - ) - .unwrap_err(); + err = + serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(r#"{"timeout":0}"#) + .unwrap_err(); assert_eq!( err.to_string().get(..50), Some("invalid value: integer `0`, expected a nonzero u32") ); - err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"timeout":4294967296}"#, ) .unwrap_err(); @@ -3520,8 +3286,7 @@ mod test { err.to_string().get(..59), Some("invalid value: integer `4294967296`, expected a nonzero u32") ); - let mut key = - serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>("{}")?; + let mut key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>("{}")?; assert!(key.rp_id.is_none()); assert!(key.user.name.is_none()); assert!(key.user.id.is_none()); @@ -3544,7 +3309,7 @@ mod test { assert!(matches!(key.extensions.cred_protect, CredProtect::None)); assert!(key.extensions.min_pin_length.is_none()); assert!(key.extensions.prf.is_none()); - key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"rp":null,"user":null,"timeout":null,"excludeCredentials":null,"attestation":null,"attestationFormats":null,"authenticatorSelection":null,"extensions":null,"pubKeyCredParams":null,"hints":null,"challenge":null}"#, )?; assert!(key.rp_id.is_none()); @@ -3569,7 +3334,7 @@ mod test { assert!(matches!(key.extensions.cred_protect, CredProtect::None)); assert!(key.extensions.min_pin_length.is_none()); assert!(key.extensions.prf.is_none()); - key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"rp":{},"user":{},"excludeCredentials":[],"attestationFormats":[],"authenticatorSelection":{},"extensions":{},"pubKeyCredParams":[],"hints":[]}"#, )?; assert!(key.rp_id.is_none()); @@ -3593,7 +3358,7 @@ mod test { assert!(matches!(key.extensions.cred_protect, CredProtect::None)); assert!(key.extensions.min_pin_length.is_none()); assert!(key.extensions.prf.is_none()); - key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"rp":{"name":null,"id":null},"user":{"name":null,"id":null,"displayName":null},"authenticatorSelection":{"residentKey":null,"requireResidentKey":null,"userVerification":null,"authenticatorAttachment":null},"extensions":{"credProps":null,"credentialProtectionPolicy":null,"enforceCredentialProtectionPolicy":null,"minPinLength":null,"prf":null}}"#, )?; assert!(key.rp_id.is_none()); @@ -3617,16 +3382,12 @@ mod test { assert!(matches!(key.extensions.cred_protect, CredProtect::None)); assert!(key.extensions.min_pin_length.is_none()); assert!(key.extensions.prf.is_none()); - key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}"#, )?; assert!(key.rp_id.is_some_and(|val| val.as_ref() == "example.com")); - assert!(key.user.name.is_some_and(|val| val.as_ref() == "bob")); - assert!( - key.user - .display_name - .is_some_and(|val| val.as_ref() == "Bob") - ); + assert!(key.user.name.is_some_and(|val| val == "bob")); + assert!(key.user.display_name.is_some_and(|val| val == "Bob")); assert!(key.user.id.is_some_and(|val| val.as_ref() == [1; 1])); assert_eq!( key.pub_key_cred_params.0, @@ -3669,7 +3430,7 @@ mod test { assert!(key.extensions.prf.is_some_and(|prf| prf.first.is_empty() && prf.second.is_some_and(|p| p.is_empty()) && matches!(prf.ext_req, ExtensionReq::Allow))); - key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>( + key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>( r#"{"timeout":4294967295}"#, )?; assert_eq!(key.timeout, NonZeroU32::MAX); @@ -3768,35 +3529,34 @@ mod test { )] #[test] fn user_entity() -> Result<(), Error> { - let mut err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 16>>( - r#"{"bob":true}"#, - ) - .unwrap_err(); + let mut err = + serde_json::from_str::<PublicKeyCredentialUserEntityOwned<16>>(r#"{"bob":true}"#) + .unwrap_err(); assert_eq!( err.to_string().get(..64), Some("unknown field `bob`, expected one of `id`, `name`, `displayName`") ); - err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>( + err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>( r#"{"name":"bob","name":"bob"}"#, ) .unwrap_err(); assert_eq!(err.to_string().get(..22), Some("duplicate field `name`")); - let mut user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>( + let mut user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>( r#"{"id":"AQ","name":"bob","displayName":"Bob"}"#, )?; assert!( user.id .is_some_and(|val| val.as_slice() == [1; 1].as_slice()) ); - assert!(user.name.is_some_and(|val| val.as_ref() == "bob")); - assert!(user.display_name.is_some_and(|val| val.as_ref() == "Bob")); - user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>( + assert!(user.name.is_some_and(|val| val == "bob")); + assert!(user.display_name.is_some_and(|val| val == "Bob")); + user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>( r#"{"id":null,"name":null,"displayName":null}"#, )?; assert!(user.name.is_none()); assert!(user.display_name.is_none()); assert!(user.id.is_none()); - user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>("{}")?; + user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>("{}")?; assert!(user.name.is_none()); assert!(user.display_name.is_none()); assert!(user.id.is_none()); diff --git a/src/response.rs b/src/response.rs @@ -143,7 +143,7 @@ pub mod error; /// # use core::convert; /// # use webauthn_rp::{ /// # hash::hash_set::{InsertRemoveExpired, MaxLenHashSet}, -/// # request::{register::{error::CreationOptionsErr, CredentialCreationOptions, DisplayName, PublicKeyCredentialUserEntity, RegistrationClientState, UserHandle, UserHandle64, USER_HANDLE_MAX_LEN, RegistrationVerificationOptions}, PublicKeyCredentialDescriptor, RpId}, +/// # request::{register::{error::CreationOptionsErr, CredentialCreationOptions, PublicKeyCredentialUserEntity, RegistrationClientState, UserHandle, UserHandle64, USER_HANDLE_MAX_LEN, RegistrationVerificationOptions}, PublicKeyCredentialDescriptor, RpId}, /// # response::{register::{error::RegCeremonyErr, Registration}, error::CollectedClientDataErr, CollectedClientData}, /// # RegisteredCredential /// # }; @@ -209,9 +209,9 @@ pub mod error; /// fn get_user_entity(user: &UserHandle<USER_HANDLE_MAX_LEN>) -> PublicKeyCredentialUserEntity<'_, '_, '_, USER_HANDLE_MAX_LEN> { /// // ⋮ /// # PublicKeyCredentialUserEntity { -/// # name: "foo".try_into().unwrap(), +/// # name: "foo", /// # id: user, -/// # display_name: DisplayName::Blank, +/// # display_name: "", /// # } /// } /// /// Send `RegistrationClientState` and receive `Registration` JSON from client. diff --git a/src/response/ser.rs b/src/response/ser.rs @@ -861,15 +861,15 @@ where /// # #[cfg(feature = "bin")] /// # use webauthn_rp::bin::Decode; /// # use webauthn_rp::{ - /// # request::{register::{DisplayName, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MIN_LEN, Username}, AsciiDomain, RpId}, + /// # request::{register::{PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MIN_LEN}, AsciiDomain, RpId}, /// # response::CurrentUserDetailsOptions, /// # AggErr, /// # }; /// /// Retrieves the `PublicKeyCredentialUserEntity` info associated with `user_id` from the database. /// # #[cfg(feature = "bin")] - /// fn get_user_info(user_id: UserHandle<USER_HANDLE_MIN_LEN>) -> Result<(Username<'static>, DisplayName<'static>), AggErr> { + /// fn get_user_info(user_id: UserHandle<USER_HANDLE_MIN_LEN>) -> Result<(String, String), AggErr> { /// // ⋮ - /// # Ok((Username::decode("foo").unwrap(), DisplayName::decode("foo").unwrap())) + /// # Ok(("foo".to_owned(), "foo".to_owned())) /// } /// /// Retrieves the `UserHandle` from a session cookie. /// # #[cfg(feature = "custom")] @@ -885,7 +885,7 @@ where /// assert_eq!( /// serde_json::to_string(&CurrentUserDetailsOptions { /// rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), - /// user: PublicKeyCredentialUserEntity { name, id: &user_handle, display_name, }, + /// user: PublicKeyCredentialUserEntity { name: &name, id: &user_handle, display_name: &display_name, }, /// }) /// .unwrap(), /// r#"{"rpId":"example.com","userId":"AA","name":"foo","displayName":"foo"}"#