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