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