token_management.rs 3.24 KB
Newer Older
tec's avatar
tec committed
1
use base64;
tec's avatar
tec committed
2
use chrono::{prelude::Utc, DateTime};
tec's avatar
tec committed
3
use openssl::symm::{decrypt, encrypt, Cipher};
tec's avatar
tec committed
4
5
6
7
8
use rand::Rng;
use serenity::model::user::User;
use std::str;

lazy_static! {
tec's avatar
tec committed
9
    static ref KEY: [u8; 32] = rand::thread_rng().gen::<[u8; 32]>();
tec's avatar
tec committed
10
    static ref CIPHER: Cipher = Cipher::aes_256_cbc();
tec's avatar
tec committed
11
12
}

tec's avatar
tec committed
13
14
15
16
17
fn text_encrypt(plaintext: &str) -> String {
    let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    let encrypted_vec =
        encrypt(*CIPHER, &*KEY, Some(iv), plaintext.as_bytes()).expect("encryption failed");
    return base64::encode(encrypted_vec.as_slice());
tec's avatar
tec committed
18
}
tec's avatar
tec committed
19
fn text_decrypt(ciphertext: &str) -> Option<String> {
tec's avatar
tec committed
20
    let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
tec's avatar
tec committed
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    if let Ok(cipher_vec) = base64::decode(ciphertext) {
        if let Ok(decrypted_vec) = decrypt(*CIPHER, &*KEY, Some(iv), &cipher_vec) {
            if let Ok(decrypted_token) = str::from_utf8(decrypted_vec.as_slice()) {
                return Some(decrypted_token.to_owned());
            } else {
                warn!("Invalid utf8 in text");
            }
        } else {
            warn!("Text decryption failed");
        }
    } else {
        warn!("Unable to decode base64 text");
    }
    return None;
tec's avatar
tec committed
35
36
37
38
39
40
41
42
43
44
45
46
}

pub fn generate_token<'a>(discord_user: &User, username: &str) -> String {
    // if username doesn't exist : throw error
    let timestamp = Utc::now().to_rfc3339();
    let payload = format!(
        "{},{},{}",
        timestamp,
        discord_user.id.0.to_string(),
        username
    );
    info!("Token generated for {}: {}", discord_user.name, &payload);
tec's avatar
tec committed
47
    text_encrypt(&payload).to_string()
tec's avatar
tec committed
48
49
50
51
52
53
}

#[derive(Debug)]
pub enum TokenError {
    DiscordIdMismatch,
    TokenExpired,
tec's avatar
tec committed
54
    TokenInvalid,
tec's avatar
tec committed
55
56
57
58
59
60
61
}
impl std::fmt::Display for TokenError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

tec's avatar
tec committed
62
pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, TokenError> {
tec's avatar
tec committed
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    if let Some(token) = text_decrypt(encrypted_token) {
        let token_components: Vec<_> = token.splitn(3, ',').collect();
        info!(
            "Verification attempt from '{}'(uid: {}) for account '{}' with token from {}",
            discord_user.name, token_components[1], token_components[2], token_components[0]
        );
        let token_timestamp =
            DateTime::parse_from_rfc3339(token_components[0]).expect("Invalid date format");
        let token_discord_user = token_components[1];
        let token_username = token_components[2];
        if token_discord_user != discord_user.id.0.to_string() {
            warn!("... attempt failed : DiscordID mismatch");
            return Err(TokenError::DiscordIdMismatch);
        }
        let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp();
        if time_delta_seconds > 5 * 60 {
            warn!(
                "... attempt failed : token expired ({} seconds old)",
                time_delta_seconds
            );
            return Err(TokenError::TokenExpired);
        }
        info!(
            "... verification successful (token {} seconds old)",
tec's avatar
tec committed
87
88
            time_delta_seconds
        );
tec's avatar
tec committed
89
90
91
        return Ok(token_username.to_owned());
    } else {
        return Err(TokenError::TokenInvalid);
tec's avatar
tec committed
92
93
    }
}