Skip to main content

cargo_smith/auth/
auth.rs

1use std::error::Error;
2use serde::{Serialize, Deserialize};
3use sha2::{digest::generic_array::GenericArray, Digest, Sha256};
4use base64::Engine;
5use aes_gcm::{aead::{Aead, OsRng}, AeadCore, Aes256Gcm, KeyInit, Nonce};
6use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
7use chrono::{Utc, Duration};
8use bcrypt::{hash as crypt_hash, DEFAULT_COST};
9
10use crate::auth::claims::Claims;
11
12fn derive_key_from_string(key_str: &str) -> [u8; 32] {
13    let hasher = Sha256::new_with_prefix(key_str.as_bytes());
14    hasher.finalize().into()
15}
16
17#[derive(Serialize, Deserialize)]
18pub struct AuthService {
19    secret_key: String,
20    encryption_key: String,
21}
22impl AuthService {
23
24    pub fn new(secret_key: String, encryption_key: String) -> Self {
25        Self {
26            secret_key,
27            encryption_key,
28        }
29    }
30
31    pub fn hash(input: &str) -> Result<String, Box<dyn Error>> {
32        let mut hasher = Sha256::new_with_prefix(input.as_bytes());
33        hasher.update(input.as_bytes());
34        let result = hasher.finalize();
35        Ok(hex::encode(result))
36    }
37
38    pub fn hash_password(input: &str) -> Result<String, bcrypt::BcryptError> {
39        crypt_hash(input, DEFAULT_COST)
40    }
41
42    pub fn verify_password(password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
43        bcrypt::verify(password, hash)
44    }
45
46    pub fn generate_token<T: Serialize>(&self, email: String, data: T, minutes: i64) -> String {
47
48        let expiration = Utc::now()
49            .checked_add_signed(Duration::minutes(minutes))
50            .expect("valid timestamp")
51            .timestamp() as usize;
52
53        let claims = Claims {
54            sub: email,
55            exp: expiration,
56            iat: Utc::now().timestamp() as usize,
57            data: data,
58        };
59
60        encode(
61            &Header::default(),
62            &claims,
63            &EncodingKey::from_secret(self.secret_key.as_bytes()),
64        ).unwrap()
65    }
66
67    pub fn verify_token<T>(&self, token: &str) -> bool
68    where
69        T: for<'de> Deserialize<'de> + Clone, 
70    {
71        let validation = Validation::default();
72        let result = decode::<Claims<T>>(
73            token,
74            &DecodingKey::from_secret(self.secret_key.as_bytes()),
75            &validation,
76        );
77        result.is_ok()
78    }
79
80    pub fn is_token_expired<T>(&self, token: &str) -> bool 
81    where
82        T: for<'de> Deserialize<'de> + Clone,
83    {
84        let validation = Validation::default();
85        if let Ok(data) = decode::<Claims<T>>(
86            token,
87            &DecodingKey::from_secret(self.secret_key.as_bytes()),
88            &validation,
89        ) {
90            let now = Utc::now().timestamp() as usize;
91            data.claims.exp < now
92        } else {
93            true // Treat invalid token as expired
94        }
95    }
96
97    pub fn encrypt(&self, input: &str) -> Result<String, Box<dyn Error>> {
98
99        let key_bytes = derive_key_from_string(&self.encryption_key);
100        let key = GenericArray::from_slice(&key_bytes);
101        let cipher = Aes256Gcm::new(key);
102
103        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
104
105        let cipher_text = cipher.encrypt(&nonce, input.as_bytes())
106            .map_err(|e| format!("Encryption failed: {}", e))?;
107
108        let mut encrypted_data = nonce.to_vec();
109        encrypted_data.extend_from_slice(&cipher_text);
110
111        Ok(base64::engine::general_purpose::STANDARD.encode(encrypted_data))
112    }
113
114    pub fn decrypt(&self, input: &str) -> Result<String, Box<dyn Error>> {
115
116        let key_bytes = derive_key_from_string(&self.encryption_key);
117        let key = GenericArray::from_slice(&key_bytes);
118        let cipher = Aes256Gcm::new(key);
119
120        let encrypted_data = base64::engine::general_purpose::STANDARD.decode(input)
121            .map_err(|e| format!("Base64 decode failed: {}", e))?;
122
123        if encrypted_data.len() < 12 {
124            return Err("Invalid encrypted data: too short".into());
125        }
126        
127        let (nonce_bytes, cipher_text) = encrypted_data.split_at(12);
128        let nonce = Nonce::from_slice(nonce_bytes);
129        
130        let plaintext = cipher.decrypt(nonce, cipher_text)
131            .map_err(|e| format!("Decryption failed: {}", e))?;
132        
133        String::from_utf8(plaintext)
134            .map_err(|e| format!("Invalid UTF-8: {}", e).into())
135    }
136}