1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::Error;
use rand::{thread_rng, Rng};
use totp_rs::{Rfc6238, TOTP};

/// Generate a random string
///
/// ### Example
/// ```rust
/// use lonewolf_auth_toolkit::mfa::generate_random_string;
///
/// let random_string = generate_random_string();
/// ```
pub fn generate_random_string() -> String {
    let mut rng = thread_rng();
    let random_bytes: [u8; 32] = rng.gen();
    let hex_string: String = random_bytes.iter().map(|b| format!("{:02x}", b)).collect();

    hex_string
}

/// Generate a TOTP 6 Digit QR Code
///
/// ### Example
/// ```rust
/// use lonewolf_auth_toolkit::mfa::generate;
///
/// #[tokio::main]
/// pub async fn main() -> Result<(), anyhow::Error> {
///     let result = generate("SomeIssuer".to_string(), "SomeAccountName".to_string()).await?;
/// 
///     println!("{:?}", result.0);
///     println!("{:?}", result.1);
/// 
///     Ok(())
/// }
/// ```
pub async fn generate(issuer: String, account_name: String) -> Result<(String, String), Error> {
    let secret_string = generate_random_string();
    let mut rfc = Rfc6238::with_defaults(secret_string.clone().into_bytes().to_vec())?;

    rfc.digits(6)?;
    rfc.issuer(issuer);
    rfc.account_name(account_name);

    let totp = TOTP::from_rfc6238(rfc)?;
    let qr_code = totp.get_qr_base64();

    match qr_code {
        Ok(qr_code) => Ok((qr_code, secret_string)),
        Err(error) => Err(Error::msg(error)),
    }
}

/// Verify a TOTP 6 Digit Code
/// 
/// ### Example
/// ```rust
/// use lonewolf_auth_toolkit::mfa::verify;
/// 
/// #[tokio::main]
/// pub async fn main() -> Result<(), anyhow::Error> {
///     let verified = verify("123456".to_string(), "5BAD23B477D625825019A4C895E8C5B8D22A88D3193E6928B7FC7AEFF1CC578F2A9551A1919ADE27EC50E48DFD4A2F95D9B52636C141E5B5FADE5C24A0EC71E7".to_string()).await?;
/// 
///     Ok(())
/// }
/// ```
pub async fn verify(code: String, secret: String) -> Result<bool, Error> {
    let mut rfc = Rfc6238::with_defaults(secret.clone().into_bytes().to_vec())?;

    rfc.digits(6)?;

    let totp = TOTP::from_rfc6238(rfc)?;
    let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    let token = totp.generate(time);

    Ok(code == token)
}