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}