Skip to main content

passkey_authenticator/
credential_store.rs

1#[cfg(any(feature = "tokio", test))]
2use std::sync::Arc;
3
4use passkey_types::{
5    Passkey,
6    ctap2::{
7        Ctap2Error, StatusCode,
8        make_credential::{Options, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity},
9    },
10    webauthn::PublicKeyCredentialDescriptor,
11};
12
13use crate::passkey::PasskeyAccessor;
14
15/// A struct that defines the capabilities of a store.
16pub struct StoreInfo {
17    /// How the store handles discoverability.
18    pub discoverability: DiscoverabilitySupport,
19}
20
21/// Enum to define how the store handles discoverability.
22/// Note that this does not say anything about which storage mode will be used.
23#[derive(PartialEq)]
24pub enum DiscoverabilitySupport {
25    /// The store supports both discoverable and non-credentials.
26    Full,
27
28    /// The store only supports non-discoverable credentials.
29    /// An error will be returned if a discoverable credential is requested.
30    OnlyNonDiscoverable,
31
32    /// The store only supports discoverable credential.
33    /// No error will be returned if a non-discoverable credential is requested.
34    ForcedDiscoverable,
35}
36
37impl DiscoverabilitySupport {
38    /// Helper method to determine if the store created a discoverable credential or not.
39    pub fn is_passkey_discoverable(&self, rk_input: bool) -> bool {
40        match self {
41            DiscoverabilitySupport::Full => rk_input,
42            DiscoverabilitySupport::OnlyNonDiscoverable => false,
43            DiscoverabilitySupport::ForcedDiscoverable => true,
44        }
45    }
46}
47
48/// Use this on a type that enables storage and fetching of credentials
49#[async_trait::async_trait]
50pub trait CredentialStore {
51    /// Defines the return type of find_credentials(...)
52    type PasskeyItem: PasskeyAccessor + Send + Sync;
53
54    /// Find all credentials matching the given `ids` and `rp_id`.
55    ///
56    /// If multiple are found, it is recommended to sort the credentials using their creation date
57    /// before returning as the algorithm will take the first credential from the list for assertions.
58    async fn find_credentials(
59        &self,
60        ids: Option<&[PublicKeyCredentialDescriptor]>,
61        rp_id: &str,
62        user_handle: Option<&[u8]>,
63    ) -> Result<Vec<Self::PasskeyItem>, StatusCode>;
64
65    /// Save the new credential into your store
66    async fn save_credential(
67        &mut self,
68        cred: Passkey,
69        user: PublicKeyCredentialUserEntity,
70        rp: PublicKeyCredentialRpEntity,
71        options: Options,
72    ) -> Result<(), StatusCode>;
73
74    /// Update the credential in your store
75    async fn update_credential(&mut self, cred: &Self::PasskeyItem) -> Result<(), StatusCode>;
76
77    /// Get information about the store
78    async fn get_info(&self) -> StoreInfo;
79}
80
81/// In-memory store for Passkeys
82///
83/// Useful for tests.
84pub type MemoryStore = std::collections::HashMap<Vec<u8>, Passkey>;
85
86#[async_trait::async_trait]
87impl CredentialStore for MemoryStore {
88    type PasskeyItem = Passkey;
89
90    async fn find_credentials(
91        &self,
92        allow_credentials: Option<&[PublicKeyCredentialDescriptor]>,
93        _rp_id: &str,
94        _user_handle: Option<&[u8]>,
95    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
96        let creds: Vec<Passkey> = allow_credentials
97            .into_iter()
98            .flatten()
99            .filter_map(|id| self.get(&*id.id))
100            .cloned()
101            .collect();
102        if creds.is_empty() {
103            Err(Ctap2Error::NoCredentials.into())
104        } else {
105            Ok(creds)
106        }
107    }
108
109    async fn save_credential(
110        &mut self,
111        cred: Passkey,
112        _user: PublicKeyCredentialUserEntity,
113        _rp: PublicKeyCredentialRpEntity,
114        _options: Options,
115    ) -> Result<(), StatusCode> {
116        self.insert(cred.credential_id.clone().into(), cred);
117        Ok(())
118    }
119
120    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
121        self.insert(cred.credential_id.clone().into(), cred.clone());
122        Ok(())
123    }
124
125    async fn get_info(&self) -> StoreInfo {
126        StoreInfo {
127            discoverability: DiscoverabilitySupport::ForcedDiscoverable,
128        }
129    }
130}
131
132#[async_trait::async_trait]
133impl CredentialStore for Option<Passkey> {
134    type PasskeyItem = Passkey;
135
136    async fn find_credentials(
137        &self,
138        id: Option<&[PublicKeyCredentialDescriptor]>,
139        _rp_id: &str,
140        _user_handle: Option<&[u8]>,
141    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
142        if let Some(id) = id {
143            id.iter().find_map(|id| {
144                // TODO: && pk.rp_id == rp_id) need rp_id on typeshared passkey
145                self.clone().filter(|pk| pk.credential_id == id.id)
146            })
147        } else {
148            self.clone() // TODO: .filter(|pk| pk.rp_id == rp_id) need rp_id on typeshared passkey
149        }
150        .map(|pk| vec![pk])
151        .ok_or(Ctap2Error::NoCredentials.into())
152    }
153
154    async fn save_credential(
155        &mut self,
156        cred: Passkey,
157        _user: PublicKeyCredentialUserEntity,
158        _rp: PublicKeyCredentialRpEntity,
159        _options: Options,
160    ) -> Result<(), StatusCode> {
161        self.replace(cred);
162        Ok(())
163    }
164
165    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
166        self.replace(cred.clone());
167        Ok(())
168    }
169
170    async fn get_info(&self) -> StoreInfo {
171        StoreInfo {
172            discoverability: DiscoverabilitySupport::ForcedDiscoverable,
173        }
174    }
175}
176
177#[cfg(any(feature = "tokio", test))]
178#[async_trait::async_trait]
179impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
180    for Arc<tokio::sync::Mutex<S>>
181{
182    type PasskeyItem = Passkey;
183
184    async fn find_credentials(
185        &self,
186        ids: Option<&[PublicKeyCredentialDescriptor]>,
187        rp_id: &str,
188        user_handle: Option<&[u8]>,
189    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
190        self.lock()
191            .await
192            .find_credentials(ids, rp_id, user_handle)
193            .await
194    }
195
196    async fn save_credential(
197        &mut self,
198        cred: Passkey,
199        user: PublicKeyCredentialUserEntity,
200        rp: PublicKeyCredentialRpEntity,
201        options: Options,
202    ) -> Result<(), StatusCode> {
203        self.lock()
204            .await
205            .save_credential(cred, user, rp, options)
206            .await
207    }
208
209    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
210        self.lock().await.update_credential(cred).await
211    }
212
213    async fn get_info(&self) -> StoreInfo {
214        self.lock().await.get_info().await
215    }
216}
217
218#[cfg(any(feature = "tokio", test))]
219#[async_trait::async_trait]
220impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
221    for Arc<tokio::sync::RwLock<S>>
222{
223    type PasskeyItem = Passkey;
224
225    async fn find_credentials(
226        &self,
227        ids: Option<&[PublicKeyCredentialDescriptor]>,
228        rp_id: &str,
229        user_handle: Option<&[u8]>,
230    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
231        self.read()
232            .await
233            .find_credentials(ids, rp_id, user_handle)
234            .await
235    }
236
237    async fn save_credential(
238        &mut self,
239        cred: Passkey,
240        user: PublicKeyCredentialUserEntity,
241        rp: PublicKeyCredentialRpEntity,
242        options: Options,
243    ) -> Result<(), StatusCode> {
244        self.write()
245            .await
246            .save_credential(cred, user, rp, options)
247            .await
248    }
249
250    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
251        self.write().await.update_credential(cred).await
252    }
253
254    async fn get_info(&self) -> StoreInfo {
255        self.read().await.get_info().await
256    }
257}
258
259#[cfg(any(feature = "tokio", test))]
260#[async_trait::async_trait]
261impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
262    for tokio::sync::Mutex<S>
263{
264    type PasskeyItem = Passkey;
265
266    async fn find_credentials(
267        &self,
268        ids: Option<&[PublicKeyCredentialDescriptor]>,
269        rp_id: &str,
270        user_handle: Option<&[u8]>,
271    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
272        self.lock()
273            .await
274            .find_credentials(ids, rp_id, user_handle)
275            .await
276    }
277
278    async fn save_credential(
279        &mut self,
280        cred: Passkey,
281        user: PublicKeyCredentialUserEntity,
282        rp: PublicKeyCredentialRpEntity,
283        options: Options,
284    ) -> Result<(), StatusCode> {
285        self.lock()
286            .await
287            .save_credential(cred, user, rp, options)
288            .await
289    }
290
291    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
292        self.lock().await.update_credential(cred).await
293    }
294
295    async fn get_info(&self) -> StoreInfo {
296        self.lock().await.get_info().await
297    }
298}
299
300#[cfg(any(feature = "tokio", test))]
301#[async_trait::async_trait]
302impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
303    for tokio::sync::RwLock<S>
304{
305    type PasskeyItem = Passkey;
306
307    async fn find_credentials(
308        &self,
309        ids: Option<&[PublicKeyCredentialDescriptor]>,
310        rp_id: &str,
311        user_handle: Option<&[u8]>,
312    ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
313        self.read()
314            .await
315            .find_credentials(ids, rp_id, user_handle)
316            .await
317    }
318
319    async fn save_credential(
320        &mut self,
321        cred: Passkey,
322        user: PublicKeyCredentialUserEntity,
323        rp: PublicKeyCredentialRpEntity,
324        options: Options,
325    ) -> Result<(), StatusCode> {
326        self.write()
327            .await
328            .save_credential(cred, user, rp, options)
329            .await
330    }
331
332    async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
333        self.write().await.update_credential(cred).await
334    }
335
336    async fn get_info(&self) -> StoreInfo {
337        self.read().await.get_info().await
338    }
339}