use argon2::{ Argon2, password_hash::{ PasswordHash, PasswordHasher as _, PasswordVerifier, SaltString, rand_core::OsRng, }, }; use async_trait::async_trait; use chrono::{Duration, Utc}; use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; use libertas_core::error::{CoreError, CoreResult}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[async_trait] pub trait PasswordHasher: Send + Sync { async fn hash_password(&self, password: &str) -> CoreResult; async fn verify_password(&self, password: &str, hash: &str) -> CoreResult<()>; } pub struct Argon2Hasher; impl Default for Argon2Hasher { fn default() -> Self { Self {} } } #[async_trait] impl PasswordHasher for Argon2Hasher { async fn hash_password(&self, password: &str) -> CoreResult { let salt = SaltString::generate(&mut OsRng); let password_bytes = password.as_bytes().to_vec(); let hash_string = tokio::task::spawn_blocking(move || { Argon2::default() .hash_password(&password_bytes, &salt) .map(|hash| hash.to_string()) .map_err(|e| CoreError::Auth(format!("Password hashing failed: {}", e))) }) .await .unwrap()?; Ok(hash_string) } async fn verify_password(&self, password: &str, hash_str: &str) -> CoreResult<()> { let hash = PasswordHash::new(hash_str) .map_err(|e| CoreError::Auth(format!("Invalid password hash format: {}", e)))?; let (password_bytes, hash_bytes) = (password.as_bytes().to_vec(), hash.to_string()); tokio::task::spawn_blocking(move || { Argon2::default() .verify_password(&password_bytes, &PasswordHash::new(&hash_bytes).unwrap()) .map_err(|_| CoreError::Auth("Invalid password".to_string())) }) .await .unwrap() } } static JWT_HEADER: Lazy
= Lazy::new(Header::default); #[derive(Debug, Serialize, Deserialize, Clone)] struct Claims { sub: Uuid, exp: usize, iat: usize, } #[async_trait] pub trait TokenGenerator: Send + Sync { fn generate_token(&self, user_id: Uuid) -> CoreResult; fn verify_token(&self, token: &str) -> CoreResult; } pub struct JwtGenerator { encoding_key: EncodingKey, decoding_key: DecodingKey, } impl JwtGenerator { pub fn new(secret: String) -> Self { Self { encoding_key: EncodingKey::from_secret(secret.as_bytes()), decoding_key: DecodingKey::from_secret(secret.as_bytes()), } } } #[async_trait] impl TokenGenerator for JwtGenerator { fn generate_token(&self, user_id: Uuid) -> CoreResult { let now = Utc::now(); let iat = now.timestamp() as usize; let exp = (now + Duration::days(7)).timestamp() as usize; let claims = Claims { sub: user_id, iat, exp, }; encode(&JWT_HEADER, &claims, &self.encoding_key) .map_err(|e| CoreError::Auth(format!("Token generation failed: {}", e))) } fn verify_token(&self, token: &str) -> CoreResult { decode::(token, &self.decoding_key, &Validation::default()) .map(|data| data.claims.sub) .map_err(|e| CoreError::Auth(format!("Token invalid: {}", e))) } }