#[cfg(feature = "auth")]
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use crate::auth::types::{AuthError, Claims, TokenValidation, User};
use crate::config::JwtConfig;
use crate::error::{FusekiError, FusekiResult};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
pub struct JwtManager {
#[cfg(feature = "auth")]
encoding_key: EncodingKey,
#[cfg(feature = "auth")]
decoding_key: DecodingKey,
#[cfg(feature = "auth")]
algorithm: Algorithm,
issuer: String,
audience: String,
expiration_secs: u64,
}
impl JwtManager {
pub fn new(config: &JwtConfig) -> FusekiResult<Self> {
#[cfg(feature = "auth")]
{
let encoding_key = EncodingKey::from_secret(config.secret.as_bytes());
let decoding_key = DecodingKey::from_secret(config.secret.as_bytes());
let algorithm = Algorithm::HS256;
Ok(Self {
encoding_key,
decoding_key,
algorithm,
issuer: config.issuer.clone(),
audience: config.audience.clone(),
expiration_secs: config.expiration_secs,
})
}
#[cfg(not(feature = "auth"))]
{
Ok(Self {
issuer: config.issuer.clone(),
audience: config.audience.clone(),
expiration_secs: config.expiration_secs,
})
}
}
#[cfg(feature = "auth")]
pub fn generate_token(&self, user: &User) -> FusekiResult<String> {
let now = Utc::now();
let expiration = now + Duration::seconds(self.expiration_secs as i64);
let claims = Claims {
sub: user.username.clone(),
exp: expiration.timestamp(),
iat: now.timestamp(),
nbf: now.timestamp(),
iss: self.issuer.clone(),
aud: self.audience.clone(),
roles: user.roles.clone(),
permissions: user.permissions.clone(),
};
encode(&Header::new(self.algorithm), &claims, &self.encoding_key).map_err(|e| {
FusekiError::authentication(format!("Failed to generate JWT token: {}", e))
})
}
#[cfg(feature = "auth")]
pub fn validate_token(&self, token: &str) -> FusekiResult<TokenValidation> {
let mut validation = Validation::new(self.algorithm);
validation.set_issuer(std::slice::from_ref(&self.issuer));
validation.set_audience(std::slice::from_ref(&self.audience));
let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
.map_err(|e| FusekiError::authentication(format!("Invalid JWT token: {}", e)))?;
let claims = token_data.claims;
let exp_time = DateTime::from_timestamp(claims.exp as i64, 0).ok_or_else(|| {
FusekiError::authentication("Invalid expiration time in token".to_string())
})?;
if Utc::now() > exp_time {
return Err(FusekiError::authentication("Token has expired".to_string()));
}
let user = User {
username: claims.sub,
roles: claims.roles,
email: None, full_name: None, last_login: None,
permissions: claims.permissions,
};
Ok(TokenValidation {
user,
expires_at: exp_time,
})
}
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
auth_header.strip_prefix("Bearer ")
}
#[cfg(feature = "auth")]
pub fn generate_refresh_token(&self, user: &User) -> FusekiResult<String> {
let now = Utc::now();
let expiration = now + Duration::days(30);
let claims = Claims {
sub: user.username.clone(),
exp: expiration.timestamp(),
iat: now.timestamp(),
nbf: now.timestamp(),
iss: self.issuer.clone(),
aud: format!("{}-refresh", self.audience),
roles: user.roles.clone(),
permissions: user.permissions.clone(),
};
encode(&Header::new(self.algorithm), &claims, &self.encoding_key).map_err(|e| {
FusekiError::authentication(format!("Failed to generate refresh token: {}", e))
})
}
#[cfg(feature = "auth")]
pub fn validate_refresh_token(&self, token: &str) -> FusekiResult<TokenValidation> {
let mut validation = Validation::new(self.algorithm);
validation.set_issuer(std::slice::from_ref(&self.issuer));
let audience = format!("{}-refresh", self.audience);
validation.set_audience(std::slice::from_ref(&audience));
let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
.map_err(|e| FusekiError::authentication(format!("Invalid refresh token: {}", e)))?;
let claims = token_data.claims;
let exp_time = DateTime::from_timestamp(claims.exp as i64, 0).ok_or_else(|| {
FusekiError::authentication("Invalid expiration time in token".to_string())
})?;
if Utc::now() > exp_time {
return Err(FusekiError::authentication(
"Refresh token has expired".to_string(),
));
}
let user = User {
username: claims.sub,
roles: claims.roles,
email: None,
full_name: None,
last_login: None,
permissions: claims.permissions,
};
Ok(TokenValidation {
user,
expires_at: exp_time,
})
}
#[cfg(feature = "auth")]
pub fn get_token_expiration(&self, token: &str) -> FusekiResult<DateTime<Utc>> {
let validation = Validation::new(self.algorithm);
let token_data = decode::<Claims>(token, &self.decoding_key, &validation)
.map_err(|e| FusekiError::authentication(format!("Invalid token: {}", e)))?;
DateTime::from_timestamp(token_data.claims.exp as i64, 0).ok_or_else(|| {
FusekiError::authentication("Invalid expiration time in token".to_string())
})
}
#[cfg(feature = "auth")]
pub fn is_token_close_to_expiration(&self, token: &str) -> FusekiResult<bool> {
let expiration = self.get_token_expiration(token)?;
let one_hour_from_now = Utc::now() + Duration::hours(1);
Ok(expiration <= one_hour_from_now)
}
#[cfg(not(feature = "auth"))]
pub fn generate_token(&self, _user: &User) -> FusekiResult<String> {
Err(FusekiError::configuration(
"JWT authentication is disabled. Enable the 'auth' feature to use JWT tokens."
.to_string(),
))
}
#[cfg(not(feature = "auth"))]
pub fn validate_token(&self, _token: &str) -> FusekiResult<TokenValidation> {
Err(FusekiError::configuration(
"JWT authentication is disabled. Enable the 'auth' feature to use JWT tokens."
.to_string(),
))
}
#[cfg(not(feature = "auth"))]
pub fn generate_refresh_token(&self, _user: &User) -> FusekiResult<String> {
Err(FusekiError::configuration(
"JWT authentication is disabled. Enable the 'auth' feature to use JWT tokens."
.to_string(),
))
}
#[cfg(not(feature = "auth"))]
pub fn validate_refresh_token(&self, _token: &str) -> FusekiResult<TokenValidation> {
Err(FusekiError::configuration(
"JWT authentication is disabled. Enable the 'auth' feature to use JWT tokens."
.to_string(),
))
}
}