arcly_http_identity/mfa/
passkey.rs1use async_trait::async_trait;
10use serde::{Deserialize, Serialize};
11
12use crate::error::Result;
13use crate::mfa::MfaVerifier;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct PasskeyCredential {
19 pub credential_id: String,
20 pub state: serde_json::Value,
22 pub created_at: u64,
23}
24
25#[async_trait]
29pub trait WebAuthnBackend: Send + Sync + 'static {
30 async fn start_registration(&self, user_id: &str) -> Result<serde_json::Value>;
33 async fn finish_registration(
36 &self,
37 user_id: &str,
38 response: serde_json::Value,
39 ) -> Result<PasskeyCredential>;
40 async fn start_assertion(&self, user_id: &str) -> Result<serde_json::Value>;
42 async fn finish_assertion(&self, user_id: &str, response: serde_json::Value) -> Result<bool>;
44}
45
46#[async_trait]
48pub trait PasskeyStore: Send + Sync + 'static {
49 async fn add(&self, user_id: &str, credential: PasskeyCredential) -> Result<()>;
50 async fn list(&self, user_id: &str) -> Result<Vec<PasskeyCredential>>;
51 async fn remove(&self, user_id: &str, credential_id: &str) -> Result<()>;
52}
53
54pub struct PasskeyService {
59 backend: std::sync::Arc<dyn WebAuthnBackend>,
60 store: std::sync::Arc<dyn PasskeyStore>,
61}
62
63impl PasskeyService {
64 pub fn new(
65 backend: std::sync::Arc<dyn WebAuthnBackend>,
66 store: std::sync::Arc<dyn PasskeyStore>,
67 ) -> Self {
68 Self { backend, store }
69 }
70
71 pub async fn start_registration(&self, user_id: &str) -> Result<serde_json::Value> {
72 self.backend.start_registration(user_id).await
73 }
74
75 pub async fn finish_registration(
76 &self,
77 user_id: &str,
78 response: serde_json::Value,
79 ) -> Result<()> {
80 let cred = self.backend.finish_registration(user_id, response).await?;
81 self.store.add(user_id, cred).await
82 }
83
84 pub async fn start_assertion(&self, user_id: &str) -> Result<serde_json::Value> {
85 self.backend.start_assertion(user_id).await
86 }
87
88 pub async fn list(&self, user_id: &str) -> Result<Vec<PasskeyCredential>> {
89 self.store.list(user_id).await
90 }
91
92 pub async fn remove(&self, user_id: &str, credential_id: &str) -> Result<()> {
93 self.store.remove(user_id, credential_id).await
94 }
95}
96
97#[async_trait]
98impl MfaVerifier for PasskeyService {
99 async fn verify(&self, user_id: &str, method: &str, code: &str) -> Result<bool> {
100 if method != "passkey" {
101 return Ok(false);
102 }
103 let response: serde_json::Value = match serde_json::from_str(code) {
104 Ok(v) => v,
105 Err(_) => return Ok(false),
106 };
107 self.backend.finish_assertion(user_id, response).await
108 }
109}