50 lines
1.7 KiB
Rust
50 lines
1.7 KiB
Rust
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<String, DomainError> {
|
|
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::<Claims>(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))
|
|
}
|
|
}
|