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