use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::mfa::MfaVerifier;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PasskeyCredential {
pub credential_id: String,
pub state: serde_json::Value,
pub created_at: u64,
}
#[async_trait]
pub trait WebAuthnBackend: Send + Sync + 'static {
async fn start_registration(&self, user_id: &str) -> Result<serde_json::Value>;
async fn finish_registration(
&self,
user_id: &str,
response: serde_json::Value,
) -> Result<PasskeyCredential>;
async fn start_assertion(&self, user_id: &str) -> Result<serde_json::Value>;
async fn finish_assertion(&self, user_id: &str, response: serde_json::Value) -> Result<bool>;
}
#[async_trait]
pub trait PasskeyStore: Send + Sync + 'static {
async fn add(&self, user_id: &str, credential: PasskeyCredential) -> Result<()>;
async fn list(&self, user_id: &str) -> Result<Vec<PasskeyCredential>>;
async fn remove(&self, user_id: &str, credential_id: &str) -> Result<()>;
}
pub struct PasskeyService {
backend: std::sync::Arc<dyn WebAuthnBackend>,
store: std::sync::Arc<dyn PasskeyStore>,
}
impl PasskeyService {
pub fn new(
backend: std::sync::Arc<dyn WebAuthnBackend>,
store: std::sync::Arc<dyn PasskeyStore>,
) -> Self {
Self { backend, store }
}
pub async fn start_registration(&self, user_id: &str) -> Result<serde_json::Value> {
self.backend.start_registration(user_id).await
}
pub async fn finish_registration(
&self,
user_id: &str,
response: serde_json::Value,
) -> Result<()> {
let cred = self.backend.finish_registration(user_id, response).await?;
self.store.add(user_id, cred).await
}
pub async fn start_assertion(&self, user_id: &str) -> Result<serde_json::Value> {
self.backend.start_assertion(user_id).await
}
pub async fn list(&self, user_id: &str) -> Result<Vec<PasskeyCredential>> {
self.store.list(user_id).await
}
pub async fn remove(&self, user_id: &str, credential_id: &str) -> Result<()> {
self.store.remove(user_id, credential_id).await
}
}
#[async_trait]
impl MfaVerifier for PasskeyService {
async fn verify(&self, user_id: &str, method: &str, code: &str) -> Result<bool> {
if method != "passkey" {
return Ok(false);
}
let response: serde_json::Value = match serde_json::from_str(code) {
Ok(v) => v,
Err(_) => return Ok(false),
};
self.backend.finish_assertion(user_id, response).await
}
}