libr2fa/
lib.rs

1use std::any::Any;
2
3/// rust implementation for HTOP, TOTP and steam guard tow-factor-authentication
4///
5/// usage:
6/// ```rust
7/// use libr2fa::HOTPKey;
8/// use libr2fa::HMACType;
9/// use libr2fa::Key;
10///
11/// let mut hotp_key = HOTPKey {
12///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
13///     hmac_type: HMACType::SHA1,
14///     ..Default::default()
15/// };
16///
17/// let code = hotp_key.get_code().unwrap();
18/// ```
19use serde::{Deserialize, Serialize};
20
21mod error;
22mod hmac_type;
23mod hotp;
24mod totp;
25mod uri;
26
27pub use error::Error;
28pub use hmac_type::HMACType;
29pub use hotp::HOTPKey;
30pub use totp::TOTPKey;
31pub use uri::URI;
32
33#[cfg(feature = "steam")]
34pub mod steam;
35#[cfg(feature = "steam")]
36pub use steam::SteamKey;
37
38#[cfg(test)]
39mod test;
40
41/// KeyType is the type of the key
42/// HOTP is the counter based key
43/// TOTP is the time based key
44/// STEAM is the steam guard key (TODO not implemented yet)
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
46pub enum KeyType {
47    HOTP,
48    #[default]
49    TOTP,
50    #[cfg(feature = "steam")]
51    Steam,
52}
53
54impl std::fmt::Display for KeyType {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            KeyType::HOTP => write!(f, "hotp"),
58            KeyType::TOTP => write!(f, "totp"),
59            #[cfg(feature = "steam")]
60            KeyType::Steam => write!(f, "steam"),
61        }
62    }
63}
64
65impl From<&str> for KeyType {
66    fn from(s: &str) -> Self {
67        match s.to_ascii_lowercase().as_str() {
68            "hotp" => KeyType::HOTP,
69            "totp" => KeyType::TOTP,
70            _ => KeyType::default(),
71        }
72    }
73}
74
75impl From<String> for KeyType {
76    fn from(s: String) -> Self {
77        KeyType::from(s.as_str())
78    }
79}
80
81/// Key is the interface for the keys
82///
83/// usage:
84/// ```rust
85/// use libr2fa::HOTPKey;
86/// use libr2fa::HMACType;
87/// use libr2fa::Key;
88///
89/// let mut hotp_key = HOTPKey {
90///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
91///     hmac_type: HMACType::SHA1,
92///     ..Default::default()
93/// };
94///
95/// let code = hotp_key.get_code().unwrap();
96/// ```
97pub trait Key {
98    /// use to downcast to original type
99    ///
100    /// ```rust
101    /// use libr2fa::HOTPKey;
102    /// use libr2fa::HMACType;
103    /// use libr2fa::Key;
104    ///
105    /// let hotp_key = HOTPKey {
106    ///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
107    ///     hmac_type: HMACType::SHA1,
108    ///     ..Default::default()
109    /// };
110    ///
111    /// let key: Box<dyn Key> = Box::new(hotp_key);
112    ///
113    /// let hotp_key = key.as_any().downcast_ref::<HOTPKey>();
114    ///
115    /// assert!(hotp_key.is_some());
116    /// ```
117    fn as_any(&self) -> &dyn Any;
118
119    /// get_code returns the code for the key
120    ///
121    /// if it is HTOP key, it will increment the counter
122    fn get_code(&mut self) -> Result<String, error::Error>;
123
124    /// get the name of the key
125    ///
126    /// ```rust
127    /// use libr2fa::HOTPKey;
128    /// use libr2fa::HMACType;
129    /// use libr2fa::Key;
130    ///
131    /// let mut hotp_key = HOTPKey {
132    ///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
133    ///     hmac_type: HMACType::SHA1,
134    ///     ..Default::default()
135    /// };
136    ///
137    /// hotp_key.set_name("test");
138    ///
139    /// assert_eq!(hotp_key.get_name(), "test")
140    /// ```
141    fn get_name(&self) -> &str;
142
143    /// get the recovery codes
144    ///
145    /// ```rust
146    /// use libr2fa::HOTPKey;
147    /// use libr2fa::HMACType;
148    /// use libr2fa::Key;
149    ///
150    /// let mut hotp_key = HOTPKey {
151    ///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
152    ///     hmac_type: HMACType::SHA1,
153    ///     ..Default::default()
154    /// };
155    ///
156    /// hotp_key.set_recovery_codes(vec!["test".to_string()]);
157    ///
158    /// assert_eq!(hotp_key.get_recovery_codes(), &["test".to_string()])
159    ///
160    /// ```
161    fn get_recovery_codes(&self) -> Vec<String>;
162
163    /// get the type of the key
164    fn get_type(&self) -> KeyType;
165
166    /// set the name of the key
167    ///
168    /// ```rust
169    /// use libr2fa::HOTPKey;
170    /// use libr2fa::HMACType;
171    /// use libr2fa::Key;
172    ///
173    /// let mut hotp_key = HOTPKey {
174    ///     name: "".to_string(),
175    ///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
176    ///     hmac_type: HMACType::SHA1,
177    ///     ..Default::default()
178    /// };
179    ///
180    /// hotp_key.set_name("test");
181    ///
182    /// assert_eq!(hotp_key.get_name(), "test")
183    /// ```
184    fn set_name(&mut self, name: &str);
185
186    /// set the recovery codes
187    ///
188    /// ```rust
189    /// use libr2fa::HOTPKey;
190    /// use libr2fa::HMACType;
191    /// use libr2fa::Key;
192    ///
193    /// let mut hotp_key = HOTPKey {
194    ///     key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
195    ///     hmac_type: HMACType::SHA1,
196    ///     ..Default::default()
197    /// };
198    ///
199    /// hotp_key.set_recovery_codes(vec!["test".to_string()]);
200    ///
201    /// assert_eq!(hotp_key.get_recovery_codes(), &["test".to_string()])
202    ///
203    /// ```
204    fn set_recovery_codes(&mut self, recovery_codes: Vec<String>);
205}
206
207/// create a new key from the uri string
208///
209/// ```rust
210/// use libr2fa::otpauth_from_uri;
211/// use libr2fa::TOTPKey;
212/// use libr2fa::HMACType;
213/// use libr2fa::Key;
214///
215/// let totp_key1 = otpauth_from_uri("otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA256&digits=7&period=60");
216/// if let Err(err) = totp_key1 {
217///     panic!("{}", err);
218/// }
219/// let mut totp_key1 = totp_key1.unwrap();
220///
221/// let mut totp_key2 = TOTPKey {
222///     name: "ACME Co:john.doe@email.com".to_string(),
223///     key: "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(),
224///     digits: 7,
225///     time_step: 60,
226///     hmac_type: HMACType::SHA256,
227///     issuer: Some("ACME Co".to_string()),
228///     ..Default::default()
229///     };
230///
231/// assert_eq!(totp_key1.get_name(), totp_key2.get_name());
232/// assert_eq!(totp_key1.get_type(), totp_key2.get_type());
233/// assert_eq!(totp_key1.get_code(), totp_key2.get_code());
234/// ```
235pub fn otpauth_from_uri(uri: &str) -> Result<Box<dyn Key>, Error> {
236    let uri_struct = URI::from(uri);
237
238    match uri_struct.key_type {
239        KeyType::HOTP => HOTPKey::from_uri_struct(&uri_struct),
240        KeyType::TOTP => TOTPKey::from_uri_struct(&uri_struct),
241        #[cfg(feature = "steam")]
242        KeyType::Steam => steam::SteamKey::from_uri_struct(&uri_struct),
243    }
244}
245
246/// create a new key from the uri qrcode
247///
248/// ```rust
249/// use libr2fa::otpauth_from_uri_qrcode;
250/// use libr2fa::TOTPKey;
251/// use libr2fa::HMACType;
252/// use libr2fa::Key;
253///
254/// let totp_key1 = otpauth_from_uri_qrcode("public/uri_qrcode_test.png");
255/// if let Err(err) = totp_key1 {
256///     panic!("{}", err);
257/// }
258/// let mut totp_key1 = totp_key1.unwrap();
259///
260/// let mut totp_key2 = TOTPKey {
261///     name: "ACME Co:john.doe@email.com".to_string(),
262///     issuer: Some("ACME Co".to_string()),
263///     key: "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(),
264///     digits: 7,
265///     time_step: 60,
266///     hmac_type: HMACType::SHA256,
267///     ..Default::default()
268/// };
269///
270/// assert_eq!(totp_key1.get_name(), totp_key2.get_name());
271/// assert_eq!(totp_key1.get_type(), totp_key2.get_type());
272/// assert_eq!(totp_key1.get_code(), totp_key2.get_code());
273/// ```
274#[cfg(feature = "qrcoderead")]
275pub fn otpauth_from_uri_qrcode(path: &str) -> Result<Box<dyn Key>, Error> {
276    let uri_struct = URI::from_qr_code(path)?;
277
278    match uri_struct.key_type {
279        KeyType::HOTP => HOTPKey::from_uri_struct(&uri_struct),
280        KeyType::TOTP => TOTPKey::from_uri_struct(&uri_struct),
281        #[cfg(feature = "steam")]
282        KeyType::Steam => steam::SteamKey::from_uri_struct(&uri_struct),
283    }
284}
285
286pub trait OtpAuthKey {
287    /// to uri struct
288    fn to_uri_struct(&self) -> URI;
289
290    /// get the uri for the key
291    fn get_uri(&self) -> String {
292        self.to_uri_struct().to_string()
293    }
294
295    /// get issuer
296    fn get_issuer(&self) -> Option<&str>;
297
298    /// create the key from the uri struct
299    fn from_uri_struct(uri: &URI) -> Result<Box<dyn Key>, Error>;
300}