ares/auth/
jwt.rs

1use crate::types::{AppError, Claims, Result, TokenResponse};
2use argon2::{
3    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
4    Argon2,
5};
6use chrono::{Duration, Utc};
7use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
8
9pub struct AuthService {
10    jwt_secret: String,
11    access_expiry: i64,
12    refresh_expiry: i64,
13}
14
15impl AuthService {
16    pub fn new(jwt_secret: String, access_expiry: i64, refresh_expiry: i64) -> Self {
17        Self {
18            jwt_secret,
19            access_expiry,
20            refresh_expiry,
21        }
22    }
23
24    pub fn hash_password(&self, password: &str) -> Result<String> {
25        let salt = SaltString::generate(&mut OsRng);
26        let argon2 = Argon2::default();
27
28        argon2
29            .hash_password(password.as_bytes(), &salt)
30            .map(|hash| hash.to_string())
31            .map_err(|e| AppError::Auth(format!("Failed to hash password: {}", e)))
32    }
33
34    pub fn verify_password(&self, password: &str, hash: &str) -> Result<bool> {
35        let parsed_hash = PasswordHash::new(hash)
36            .map_err(|e| AppError::Auth(format!("Invalid password hash: {}", e)))?;
37
38        Ok(Argon2::default()
39            .verify_password(password.as_bytes(), &parsed_hash)
40            .is_ok())
41    }
42
43    pub fn generate_tokens(&self, user_id: &str, email: &str) -> Result<TokenResponse> {
44        let access_token = self.generate_access_token(user_id, email)?;
45        let refresh_token = self.generate_refresh_token(user_id, email)?;
46
47        Ok(TokenResponse {
48            access_token,
49            refresh_token,
50            expires_in: self.access_expiry,
51        })
52    }
53
54    fn generate_access_token(&self, user_id: &str, email: &str) -> Result<String> {
55        let claims = Claims {
56            sub: user_id.to_string(),
57            email: email.to_string(),
58            exp: (Utc::now() + Duration::seconds(self.access_expiry)).timestamp() as usize,
59            iat: Utc::now().timestamp() as usize,
60        };
61
62        encode(
63            &Header::new(Algorithm::HS256),
64            &claims,
65            &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
66        )
67        .map_err(|e| AppError::Auth(format!("Failed to generate token: {}", e)))
68    }
69
70    fn generate_refresh_token(&self, user_id: &str, email: &str) -> Result<String> {
71        let claims = Claims {
72            sub: user_id.to_string(),
73            email: email.to_string(),
74            exp: (Utc::now() + Duration::seconds(self.refresh_expiry)).timestamp() as usize,
75            iat: Utc::now().timestamp() as usize,
76        };
77
78        encode(
79            &Header::new(Algorithm::HS256),
80            &claims,
81            &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
82        )
83        .map_err(|e| AppError::Auth(format!("Failed to generate refresh token: {}", e)))
84    }
85
86    pub fn verify_token(&self, token: &str) -> Result<Claims> {
87        let validation = Validation::new(Algorithm::HS256);
88
89        decode::<Claims>(
90            token,
91            &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
92            &validation,
93        )
94        .map(|data| data.claims)
95        .map_err(|e| AppError::Auth(format!("Invalid token: {}", e)))
96    }
97
98    pub fn hash_token(&self, token: &str) -> String {
99        use sha2::{Digest, Sha256};
100        let mut hasher = Sha256::new();
101        hasher.update(token.as_bytes());
102        let result = hasher.finalize();
103        result
104            .iter()
105            .map(|b| format!("{:02x}", b))
106            .collect::<String>()
107    }
108}