1use passkey::client::WebauthnError;
2use thiserror::Error;
3
4use super::{
5 Fido2Authenticator, PublicKeyCredentialAuthenticatorAssertionResponse,
6 PublicKeyCredentialAuthenticatorAttestationResponse,
7 authenticator::GetSelectedCredentialError,
8 get_string_name_from_enum,
9 types::{
10 AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData,
11 ClientExtensionResults, CredPropsResult, Origin,
12 },
13};
14use crate::types::InvalidOriginError;
15
16#[allow(missing_docs)]
17#[derive(Debug, Error)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
19pub enum Fido2ClientError {
20 #[error(transparent)]
21 InvalidOrigin(#[from] InvalidOriginError),
22 #[error(transparent)]
23 Serde(#[from] serde_json::Error),
24 #[error(transparent)]
25 GetSelectedCredential(#[from] GetSelectedCredentialError),
26
27 #[error("Webauthn error: {0:?}")]
28 Webauthn(WebauthnError),
29}
30
31impl From<WebauthnError> for Fido2ClientError {
32 fn from(e: WebauthnError) -> Self {
33 Self::Webauthn(e)
34 }
35}
36
37#[allow(missing_docs)]
38pub struct Fido2Client<'a> {
39 pub authenticator: Fido2Authenticator<'a>,
40}
41
42impl Fido2Client<'_> {
43 #[allow(missing_docs)]
44 pub async fn register(
45 &mut self,
46 origin: Origin,
47 request: String,
48 client_data: ClientData,
49 ) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse, Fido2ClientError> {
50 let origin: passkey::client::Origin = origin.try_into()?;
51 let request: passkey::types::webauthn::CredentialCreationOptions =
52 serde_json::from_str(&request)?;
53
54 let uv = request
56 .public_key
57 .authenticator_selection
58 .as_ref()
59 .map(|s| s.user_verification.into());
60 *self
61 .authenticator
62 .requested_uv
63 .get_mut()
64 .expect("Mutex is not poisoned") = uv;
65
66 let rp_id = request.public_key.rp.id.clone();
67
68 let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(true));
69 let result = client.register(origin, request, client_data).await?;
70
71 Ok(PublicKeyCredentialAuthenticatorAttestationResponse {
72 id: result.id,
73 raw_id: result.raw_id.into(),
74 ty: get_string_name_from_enum(result.ty)?,
75 authenticator_attachment: result
76 .authenticator_attachment
77 .map(get_string_name_from_enum)
78 .transpose()?,
79 client_extension_results: ClientExtensionResults {
80 cred_props: result.client_extension_results.cred_props.map(Into::into),
81 },
82 response: AuthenticatorAttestationResponse {
83 client_data_json: result.response.client_data_json.into(),
84 authenticator_data: result.response.authenticator_data.into(),
85 public_key: result.response.public_key.map(|x| x.into()),
86 public_key_algorithm: result.response.public_key_algorithm,
87 attestation_object: result.response.attestation_object.into(),
88 transports: if rp_id.unwrap_or_default() == "https://google.com" {
89 Some(vec!["internal".to_string(), "usb".to_string()])
90 } else {
91 Some(vec!["internal".to_string()])
92 },
93 },
94 selected_credential: self.authenticator.get_selected_credential()?,
95 })
96 }
97
98 #[allow(missing_docs)]
99 pub async fn authenticate(
100 &mut self,
101 origin: Origin,
102 request: String,
103 client_data: ClientData,
104 ) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse, Fido2ClientError> {
105 let origin: passkey::client::Origin = origin.try_into()?;
106 let request: passkey::types::webauthn::CredentialRequestOptions =
107 serde_json::from_str(&request)?;
108
109 let uv = request.public_key.user_verification.into();
111 self.authenticator
112 .requested_uv
113 .get_mut()
114 .expect("Mutex is not poisoned")
115 .replace(uv);
116
117 let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(false));
118 let result = client.authenticate(origin, request, client_data).await?;
119
120 Ok(PublicKeyCredentialAuthenticatorAssertionResponse {
121 id: result.id,
122 raw_id: result.raw_id.into(),
123 ty: get_string_name_from_enum(result.ty)?,
124
125 authenticator_attachment: result
126 .authenticator_attachment
127 .map(get_string_name_from_enum)
128 .transpose()?,
129 client_extension_results: ClientExtensionResults {
130 cred_props: result
131 .client_extension_results
132 .cred_props
133 .map(|c| CredPropsResult {
134 rk: c.discoverable,
135 authenticator_display_name: c.authenticator_display_name,
136 }),
137 },
138 response: AuthenticatorAssertionResponse {
139 client_data_json: result.response.client_data_json.into(),
140 authenticator_data: result.response.authenticator_data.into(),
141 signature: result.response.signature.into(),
142 user_handle: result.response.user_handle.unwrap_or_default().into(),
143 },
144 selected_credential: self.authenticator.get_selected_credential()?,
145 })
146 }
147}