Skip to main content

alibi_plugins/two_factor/
otp_storage.rs

1use crate::token_crypto;
2use alibi_core::AuthResult;
3use async_trait::async_trait;
4use std::sync::Arc;
5
6/// Application-owned irreversible OTP representation.
7#[async_trait]
8pub trait TwoFactorOtpHasher: Send + Sync {
9    async fn hash(&self, otp: &str) -> AuthResult<String>;
10}
11
12/// Application-owned reversible OTP representation.
13#[async_trait]
14pub trait TwoFactorOtpCipher: Send + Sync {
15    async fn encrypt(&self, otp: &str) -> AuthResult<String>;
16    async fn decrypt(&self, stored: &str) -> AuthResult<String>;
17}
18
19/// Persistence format for delivered two-factor OTPs.
20#[derive(Clone, Default)]
21pub enum TwoFactorOtpStorage {
22    #[default]
23    Plain,
24    Hashed,
25    Encrypted,
26    CustomHash(Arc<dyn TwoFactorOtpHasher>),
27    CustomCipher(Arc<dyn TwoFactorOtpCipher>),
28}
29
30impl std::fmt::Debug for TwoFactorOtpStorage {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.write_str(match self {
33            Self::Plain => "Plain",
34            Self::Hashed => "Hashed",
35            Self::Encrypted => "Encrypted",
36            Self::CustomHash(_) => "CustomHash",
37            Self::CustomCipher(_) => "CustomCipher",
38        })
39    }
40}
41
42impl TwoFactorOtpStorage {
43    pub(super) async fn store(
44        &self,
45        otp: &str,
46        secret: &alibi_core::AuthConfig,
47    ) -> AuthResult<String> {
48        match self {
49            Self::Plain => Ok(otp.to_owned()),
50            Self::Hashed => Ok(token_crypto::hash_token(otp)),
51            Self::Encrypted => token_crypto::encrypt_with_config(otp, secret),
52            Self::CustomHash(callback) => callback.hash(otp).await,
53            Self::CustomCipher(callback) => callback.encrypt(otp).await,
54        }
55    }
56
57    pub(super) async fn verify(
58        &self,
59        stored: &str,
60        input: &str,
61        secret: &alibi_core::AuthConfig,
62    ) -> AuthResult<bool> {
63        let (left, right) = match self {
64            Self::Plain => (stored.to_owned(), input.to_owned()),
65            Self::Hashed => (stored.to_owned(), token_crypto::hash_token(input)),
66            Self::Encrypted => (
67                token_crypto::decrypt_with_config(stored, secret)?,
68                input.to_owned(),
69            ),
70            Self::CustomHash(callback) => (stored.to_owned(), callback.hash(input).await?),
71            Self::CustomCipher(callback) => (callback.decrypt(stored).await?, input.to_owned()),
72        };
73        Ok(left.len() == right.len()
74            && left
75                .bytes()
76                .zip(right.bytes())
77                .fold(0u8, |difference, (a, b)| difference | (a ^ b))
78                == 0)
79    }
80}