Skip to main content

arcly_http_identity/mfa/
mod.rs

1//! Multi-factor authentication: the [`MfaVerifier`] hook the login flow calls,
2//! plus concrete factors (TOTP today, passkeys behind the `passkey` feature).
3
4use async_trait::async_trait;
5
6use crate::error::Result;
7
8#[cfg(feature = "totp")]
9pub mod totp;
10
11#[cfg(feature = "passkey")]
12pub mod passkey;
13
14/// The seam between [`IdentityService`](crate::identity::IdentityService) and the
15/// concrete second factors. An implementation resolves the enrolled secret for
16/// `user_id` and checks `code` for the given `method`.
17///
18/// A [`MfaRouter`] is provided to fan out to multiple factors (e.g. TOTP +
19/// recovery codes) by `method` string.
20#[async_trait]
21pub trait MfaVerifier: Send + Sync + 'static {
22    /// Verify a factor response. `method` is `"totp"`, `"recovery"`, `"passkey"`,
23    /// etc. Returns `Ok(true)` on success, `Ok(false)` on a wrong-but-well-formed
24    /// code, and `Err` only on backend failure.
25    async fn verify(&self, user_id: &str, method: &str, code: &str) -> Result<bool>;
26}
27
28/// Dispatches an MFA verification to the first registered factor that claims the
29/// `method`. Lets an app enrol several factors behind one `MfaVerifier`.
30#[derive(Default)]
31pub struct MfaRouter {
32    factors: Vec<(String, std::sync::Arc<dyn MfaVerifier>)>,
33}
34
35impl MfaRouter {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Register `verifier` as the handler for `method`.
41    pub fn register(mut self, method: &str, verifier: std::sync::Arc<dyn MfaVerifier>) -> Self {
42        self.factors.push((method.to_owned(), verifier));
43        self
44    }
45}
46
47#[async_trait]
48impl MfaVerifier for MfaRouter {
49    async fn verify(&self, user_id: &str, method: &str, code: &str) -> Result<bool> {
50        for (m, v) in &self.factors {
51            if m == method {
52                return v.verify(user_id, method, code).await;
53            }
54        }
55        // Unknown method → not verified (never an error the caller can probe).
56        Ok(false)
57    }
58}