use async_trait::async_trait; use chrono::Utc; use domain::{errors::DomainError, ports::TokenIssuer, value_objects::SystemId}; use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: String, pub role: String, pub exp: i64, } pub struct JwtTokenIssuer { encoding_key: EncodingKey, decoding_key: DecodingKey, expiry_hours: i64, } impl JwtTokenIssuer { pub fn new(secret: &str) -> Self { Self { encoding_key: EncodingKey::from_secret(secret.as_bytes()), decoding_key: DecodingKey::from_secret(secret.as_bytes()), expiry_hours: 24, } } } #[async_trait] impl TokenIssuer for JwtTokenIssuer { async fn issue(&self, user_id: &SystemId, role: &str) -> Result { let claims = Claims { sub: user_id.to_string(), role: role.to_string(), exp: (Utc::now() + chrono::Duration::hours(self.expiry_hours)).timestamp(), }; encode(&Header::default(), &claims, &self.encoding_key) .map_err(|e| DomainError::Internal(e.to_string())) } async fn verify(&self, token: &str) -> Result<(SystemId, String), DomainError> { let data = decode::(token, &self.decoding_key, &Validation::default()) .map_err(|_| DomainError::Unauthorized("Invalid or expired token".to_string()))?; let uuid = uuid::Uuid::parse_str(&data.claims.sub) .map_err(|_| DomainError::Unauthorized("Invalid token subject".to_string()))?; Ok((SystemId::from_uuid(uuid), data.claims.role)) } }