token_management.rs 3.13 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
use rand::Rng;
use serenity::model::user::User;
use std::str;

8 9
pub static TOKEN_LIFETIME: i64 = 300; // 5 minutes

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

tec's avatar
tec committed
15 16 17 18
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");
Timothy du Heaume's avatar
Timothy du Heaume committed
19
    base64::encode(encrypted_vec.as_slice())
tec's avatar
tec committed
20
}
tec's avatar
tec committed
21
fn text_decrypt(ciphertext: &str) -> Option<String> {
tec's avatar
tec committed
22
    let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
23
    guard!(let Ok(cipher_vec) = base64::decode(ciphertext) else {
tec's avatar
tec committed
24
        warn!("Unable to decode base64 text");
25 26 27 28 29 30 31 32 33 34 35
        return None
    });
    guard!(let Ok(decrypted_vec) = decrypt(*CIPHER, &*KEY, Some(iv), &cipher_vec) else {
        warn!("Text decryption failed");
        return None
    });
    guard!(let Ok(decrypted_token) = str::from_utf8(decrypted_vec.as_slice()) else {
        warn!("Invalid utf8 in text");
        return None
    });
    Some(decrypted_token.to_owned())
tec's avatar
tec committed
36 37
}

Timothy du Heaume's avatar
Timothy du Heaume committed
38
pub fn generate_token(discord_user: &User, username: &str) -> String {
tec's avatar
tec committed
39 40 41 42 43 44 45 46 47
    // 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);
Timothy du Heaume's avatar
Timothy du Heaume committed
48
    text_encrypt(&payload)
tec's avatar
tec committed
49 50 51 52 53 54
}

#[derive(Debug)]
pub enum TokenError {
    DiscordIdMismatch,
    TokenExpired,
tec's avatar
tec committed
55
    TokenInvalid,
tec's avatar
tec committed
56 57 58 59 60 61 62
}
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
63
pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, TokenError> {
64 65 66 67 68 69 70 71 72 73 74 75 76 77
    guard!(let Some(token) = text_decrypt(encrypted_token) else {
        return Err(TokenError::TokenInvalid)
    });
    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");
Ash's avatar
cleanup  
Ash committed
78
        return Err(TokenError::DiscordIdMismatch)
79 80
    }
    let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp();
81
    if time_delta_seconds > TOKEN_LIFETIME {
82 83
        warn!(
            "... attempt failed : token expired ({} seconds old)",
tec's avatar
tec committed
84 85
            time_delta_seconds
        );
Ash's avatar
cleanup  
Ash committed
86
        return Err(TokenError::TokenExpired)
tec's avatar
tec committed
87
    }
88 89 90 91 92
    info!(
        "... verification successful (token {} seconds old)",
        time_delta_seconds
    );
    Ok(token_username.to_owned())
tec's avatar
tec committed
93
}