geekorm_core/utils/
tfa.rs

1//! # Two Factor Authentication
2//!
3//! ```rust
4//! # #[cfg(feature = "two-factor-auth")] {
5//! use geekorm::prelude::*;
6//!
7//! let tfa = TwoFactorAuth::new();
8//!
9//! let code = tfa.generate_current().unwrap();
10//! # assert_eq!(code.len(), 6);
11//!
12//! let value: Value = tfa.into();
13//! # assert!(matches!(value, geekorm::Value::Json(_)));
14//!
15//! let totp2: TwoFactorAuth = value.into();
16//! # }
17//! ```
18
19use crate::Value;
20use std::fmt::Display;
21use totp_rs::{Algorithm, Secret, TOTP};
22
23/// Two Factor Authentication
24#[derive(Debug, Clone, serde::Serialize)]
25pub struct TwoFactorAuth {
26    totp: TOTP,
27}
28
29impl TwoFactorAuth {
30    /// Create a new TwoFactorAuth
31    ///
32    /// If `qr` feature is enabled, it will use the `KONARR_TFA_ISSUER` and `KONARR_TFA_ACCOUNT_NAME` environment variables
33    pub fn new() -> Self {
34        #[cfg(feature = "two-factor-auth-qr")]
35        let issuer = match std::env::var("GEEKORM_TFA_ISSUER") {
36            Ok(issuer) => Some(issuer),
37            Err(_) => Some(env!("CARGO_PKG_NAME").to_string()),
38        };
39        #[cfg(feature = "two-factor-auth-qr")]
40        let account_name = match std::env::var("GEEKORM_TFA_ACCOUNT_NAME") {
41            Ok(account_name) => account_name,
42            Err(_) => env!("CARGO_PKG_NAME").to_string(),
43        };
44
45        Self {
46            totp: totp_rs::TOTP {
47                secret: Secret::generate_secret().to_bytes().unwrap(),
48                algorithm: Algorithm::SHA256,
49                digits: 6,
50                skew: 1,
51                step: 30,
52                #[cfg(feature = "two-factor-auth-qr")]
53                issuer,
54                #[cfg(feature = "two-factor-auth-qr")]
55                account_name,
56            },
57        }
58    }
59
60    /// Create a new TwoFactorAuth with an issuer and account name
61    #[cfg(feature = "two-factor-auth-qr")]
62    pub fn new_with_issuer(issuer: impl Into<String>, account_name: impl Into<String>) -> Self {
63        Self {
64            totp: totp_rs::TOTP {
65                algorithm: Algorithm::SHA256,
66                digits: 6,
67                skew: 1,
68                step: 30,
69                secret: Secret::generate_secret().to_bytes().unwrap(),
70                issuer: Some(issuer.into()),
71                account_name: account_name.into(),
72            },
73        }
74    }
75
76    /// Generate a new TOTP
77    pub fn generate_current(&self) -> Result<String, crate::Error> {
78        self.totp
79            .generate_current()
80            .map_err(|e| crate::Error::TotpError(e.to_string()))
81    }
82
83    /// Check the one-time passcode is valid
84    pub fn check<'a>(&self, code: impl Into<&'a str>) -> Result<bool, crate::Error> {
85        Ok(self.totp.check_current(code.into())?)
86    }
87}
88
89impl Display for TwoFactorAuth {
90    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
91        write!(
92            f,
93            "TwoFactorAuth({}, {}, {})",
94            self.totp.algorithm, self.totp.skew, self.totp.step
95        )
96    }
97}
98
99impl From<TwoFactorAuth> for Value {
100    fn from(value: TwoFactorAuth) -> Self {
101        serde_json::to_vec(&value.totp)
102            .map(Value::Json)
103            .unwrap_or(Value::Null)
104    }
105}
106
107impl From<&TwoFactorAuth> for Value {
108    fn from(value: &TwoFactorAuth) -> Self {
109        serde_json::to_vec(&value.totp)
110            .map(Value::Json)
111            .unwrap_or(Value::Null)
112    }
113}
114
115impl From<Value> for TwoFactorAuth {
116    fn from(value: Value) -> Self {
117        match value {
118            Value::Blob(s) | Value::Json(s) => serde_json::from_slice(&s).unwrap(),
119            Value::Text(t) => serde_json::from_str(&t).unwrap(),
120            _ => {
121                panic!("Error parsing unknown type")
122            }
123        }
124    }
125}
126
127impl<'de> serde::de::Deserialize<'de> for TwoFactorAuth {
128    fn deserialize<D>(deserializer: D) -> Result<TwoFactorAuth, D::Error>
129    where
130        D: serde::de::Deserializer<'de>,
131    {
132        /// Custom vistor for TOTP
133        pub struct TFAVisitor;
134
135        impl<'de> serde::de::Visitor<'de> for TFAVisitor {
136            type Value = TwoFactorAuth;
137
138            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
139                formatter.write_str("a TwoFactorAuth struct")
140            }
141
142            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
143            where
144                E: serde::de::Error,
145            {
146                serde_json::from_str(value).map_err(serde::de::Error::custom)
147            }
148
149            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
150            where
151                A: serde::de::MapAccess<'de>,
152            {
153                let totp: totp_rs::TOTP = serde::de::Deserialize::deserialize(
154                    serde::de::value::MapAccessDeserializer::new(map),
155                )?;
156
157                Ok(TwoFactorAuth { totp })
158            }
159
160            fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
161            where
162                A: serde::de::SeqAccess<'de>,
163            {
164                let v: Vec<u8> = serde::de::Deserialize::deserialize(
165                    serde::de::value::SeqAccessDeserializer::new(seq),
166                )?;
167                self.visit_bytes(&v)
168            }
169
170            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
171            where
172                E: serde::de::Error,
173            {
174                serde_json::from_slice(value).map_err(serde::de::Error::custom)
175            }
176        }
177
178        deserializer.deserialize_struct("TwoFactorAuth", &["totp"], TFAVisitor)
179    }
180}