#[cfg(any(feature = "tokio", test))]
use std::sync::Arc;
use passkey_types::{
Passkey,
ctap2::{
Ctap2Error, StatusCode,
get_assertion::Options,
make_credential::{PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity},
},
webauthn::PublicKeyCredentialDescriptor,
};
use crate::passkey::PasskeyAccessor;
pub struct StoreInfo {
pub discoverability: DiscoverabilitySupport,
}
#[derive(PartialEq)]
pub enum DiscoverabilitySupport {
Full,
OnlyNonDiscoverable,
ForcedDiscoverable,
}
impl DiscoverabilitySupport {
pub fn is_passkey_discoverable(&self, rk_input: bool) -> bool {
match self {
DiscoverabilitySupport::Full => rk_input,
DiscoverabilitySupport::OnlyNonDiscoverable => false,
DiscoverabilitySupport::ForcedDiscoverable => true,
}
}
}
#[async_trait::async_trait]
pub trait CredentialStore {
type PasskeyItem: PasskeyAccessor + Send + Sync;
async fn find_credentials(
&self,
ids: Option<&[PublicKeyCredentialDescriptor]>,
rp_id: &str,
user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode>;
async fn save_credential(
&mut self,
cred: Passkey,
user: PublicKeyCredentialUserEntity,
rp: PublicKeyCredentialRpEntity,
options: Options,
) -> Result<(), StatusCode>;
async fn update_credential(&mut self, cred: &Self::PasskeyItem) -> Result<(), StatusCode>;
async fn get_info(&self) -> StoreInfo;
}
pub type MemoryStore = std::collections::HashMap<Vec<u8>, Passkey>;
#[async_trait::async_trait]
impl CredentialStore for MemoryStore {
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
allow_credentials: Option<&[PublicKeyCredentialDescriptor]>,
_rp_id: &str,
_user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
let creds: Vec<Passkey> = allow_credentials
.into_iter()
.flatten()
.filter_map(|id| self.get(&*id.id))
.cloned()
.collect();
if creds.is_empty() {
Err(Ctap2Error::NoCredentials.into())
} else {
Ok(creds)
}
}
async fn save_credential(
&mut self,
cred: Passkey,
_user: PublicKeyCredentialUserEntity,
_rp: PublicKeyCredentialRpEntity,
_options: Options,
) -> Result<(), StatusCode> {
self.insert(cred.credential_id.clone().into(), cred);
Ok(())
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.insert(cred.credential_id.clone().into(), cred.clone());
Ok(())
}
async fn get_info(&self) -> StoreInfo {
StoreInfo {
discoverability: DiscoverabilitySupport::ForcedDiscoverable,
}
}
}
#[async_trait::async_trait]
impl CredentialStore for Option<Passkey> {
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
id: Option<&[PublicKeyCredentialDescriptor]>,
_rp_id: &str,
_user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
if let Some(id) = id {
id.iter().find_map(|id| {
self.clone().filter(|pk| pk.credential_id == id.id)
})
} else {
self.clone() }
.map(|pk| vec![pk])
.ok_or(Ctap2Error::NoCredentials.into())
}
async fn save_credential(
&mut self,
cred: Passkey,
_user: PublicKeyCredentialUserEntity,
_rp: PublicKeyCredentialRpEntity,
_options: Options,
) -> Result<(), StatusCode> {
self.replace(cred);
Ok(())
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.replace(cred.clone());
Ok(())
}
async fn get_info(&self) -> StoreInfo {
StoreInfo {
discoverability: DiscoverabilitySupport::ForcedDiscoverable,
}
}
}
#[cfg(any(feature = "tokio", test))]
#[async_trait::async_trait]
impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
for Arc<tokio::sync::Mutex<S>>
{
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
ids: Option<&[PublicKeyCredentialDescriptor]>,
rp_id: &str,
user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
self.lock()
.await
.find_credentials(ids, rp_id, user_handle)
.await
}
async fn save_credential(
&mut self,
cred: Passkey,
user: PublicKeyCredentialUserEntity,
rp: PublicKeyCredentialRpEntity,
options: Options,
) -> Result<(), StatusCode> {
self.lock()
.await
.save_credential(cred, user, rp, options)
.await
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.lock().await.update_credential(cred).await
}
async fn get_info(&self) -> StoreInfo {
self.lock().await.get_info().await
}
}
#[cfg(any(feature = "tokio", test))]
#[async_trait::async_trait]
impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
for Arc<tokio::sync::RwLock<S>>
{
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
ids: Option<&[PublicKeyCredentialDescriptor]>,
rp_id: &str,
user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
self.read()
.await
.find_credentials(ids, rp_id, user_handle)
.await
}
async fn save_credential(
&mut self,
cred: Passkey,
user: PublicKeyCredentialUserEntity,
rp: PublicKeyCredentialRpEntity,
options: Options,
) -> Result<(), StatusCode> {
self.write()
.await
.save_credential(cred, user, rp, options)
.await
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.write().await.update_credential(cred).await
}
async fn get_info(&self) -> StoreInfo {
self.read().await.get_info().await
}
}
#[cfg(any(feature = "tokio", test))]
#[async_trait::async_trait]
impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
for tokio::sync::Mutex<S>
{
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
ids: Option<&[PublicKeyCredentialDescriptor]>,
rp_id: &str,
user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
self.lock()
.await
.find_credentials(ids, rp_id, user_handle)
.await
}
async fn save_credential(
&mut self,
cred: Passkey,
user: PublicKeyCredentialUserEntity,
rp: PublicKeyCredentialRpEntity,
options: Options,
) -> Result<(), StatusCode> {
self.lock()
.await
.save_credential(cred, user, rp, options)
.await
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.lock().await.update_credential(cred).await
}
async fn get_info(&self) -> StoreInfo {
self.lock().await.get_info().await
}
}
#[cfg(any(feature = "tokio", test))]
#[async_trait::async_trait]
impl<S: CredentialStore<PasskeyItem = Passkey> + Send + Sync> CredentialStore
for tokio::sync::RwLock<S>
{
type PasskeyItem = Passkey;
async fn find_credentials(
&self,
ids: Option<&[PublicKeyCredentialDescriptor]>,
rp_id: &str,
user_handle: Option<&[u8]>,
) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
self.read()
.await
.find_credentials(ids, rp_id, user_handle)
.await
}
async fn save_credential(
&mut self,
cred: Passkey,
user: PublicKeyCredentialUserEntity,
rp: PublicKeyCredentialRpEntity,
options: Options,
) -> Result<(), StatusCode> {
self.write()
.await
.save_credential(cred, user, rp, options)
.await
}
async fn update_credential(&mut self, cred: &Passkey) -> Result<(), StatusCode> {
self.write().await.update_credential(cred).await
}
async fn get_info(&self) -> StoreInfo {
self.read().await.get_info().await
}
}