Files
telecom-platform/apps/charging-engine/src/auth.rs
T
nutcas3 ccb2ab7ee8 refactor: Add comprehensive documentation with examples to charging-engine modules
- Add module-level documentation to lib.rs with usage example
- Document Claims and AuthConfig structs with examples in auth.rs
- Add ChargingEngine struct documentation with initialization example
- Document ChargingRule, SubscriberAccount, AccountStatus, UsageEvent, UsageType, and RatingPlan with examples in types.rs
- Add CircuitState, CircuitBreakerError, and CircuitBreaker documentation with examples in circuit_breaker.rs
2026-04-29 04:36:40 +03:00

166 lines
4.5 KiB
Rust

use axum::{
extract::{Request, State},
http::StatusCode,
middleware::Next,
response::Response,
};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::env;
/// JWT claims for authentication tokens
///
/// # Example
///
/// ```rust
/// use charging_engine::auth::Claims;
///
/// let claims = Claims {
/// sub: "user123".to_string(),
/// exp: 1234567890,
/// };
/// ```
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String,
pub exp: usize,
}
/// Authentication configuration for JWT and API keys
///
/// # Example
///
/// ```rust,no_run
/// use charging_engine::auth::AuthConfig;
/// use std::env;
///
/// # fn main() -> Result<(), String> {
/// env::set_var("JWT_SECRET", "a_very_secure_secret_at_least_32_chars");
/// env::set_var("API_KEYS", "key1,key2,key3");
///
/// let config = AuthConfig::from_env()?;
/// assert!(config.validate_api_key("key1"));
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct AuthConfig {
pub secret: String,
pub api_keys: Vec<String>,
}
impl AuthConfig {
/// Load authentication configuration from environment variables
///
/// Requires `JWT_SECRET` (at least 32 characters) and optionally `API_KEYS`
/// (comma-separated list of valid API keys).
///
/// # Example
///
/// ```rust,no_run
/// use charging_engine::auth::AuthConfig;
///
/// # fn main() -> Result<(), String> {
/// let config = AuthConfig::from_env()?;
/// # Ok(())
/// # }
/// ```
pub fn from_env() -> Result<Self, String> {
let secret = env::var("JWT_SECRET")
.map_err(|_| "JWT_SECRET environment variable is required".to_string())?;
if secret.len() < 32 {
return Err("JWT_SECRET must be at least 32 characters for security".to_string());
}
// Parse API keys from comma-separated environment variable
let api_keys_env = env::var("API_KEYS").unwrap_or_else(|_| String::new());
let api_keys: Vec<String> = api_keys_env
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(Self { secret, api_keys })
}
pub fn validate_api_key(&self, api_key: &str) -> bool {
self.api_keys.iter().any(|key| key == api_key)
}
}
pub fn create_token(user_id: &str, config: &AuthConfig) -> Result<String, StatusCode> {
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::hours(24))
.ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR)?
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_string(),
exp: expiration,
};
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(config.secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn validate_token(token: &str, config: &AuthConfig) -> Result<Claims, StatusCode> {
decode::<Claims>(
token,
&DecodingKey::from_secret(config.secret.as_ref()),
&Validation::default(),
)
.map(|data| data.claims)
.map_err(|_| StatusCode::UNAUTHORIZED)
}
pub async fn auth_middleware(
State(config): State<AuthConfig>,
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
// Check if authentication is enforced via environment variable
let enforce_auth = env::var("ENFORCE_AUTH")
.unwrap_or_else(|_| "true".to_string())
.to_lowercase() == "true";
if !enforce_auth {
// Allow requests without auth for development/testing
return Ok(next.run(request).await);
}
let auth_header = request
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok());
// Check for API key authentication
let api_key_header = request
.headers()
.get("X-API-Key")
.and_then(|h| h.to_str().ok());
if let Some(api_key) = api_key_header {
if config.validate_api_key(api_key) {
return Ok(next.run(request).await);
}
}
// Check for JWT authentication
if let Some(auth_header) = auth_header {
if let Some(token) = auth_header.strip_prefix("Bearer ") {
match validate_token(token, &config) {
Ok(_claims) => return Ok(next.run(request).await),
Err(_) => return Err(StatusCode::UNAUTHORIZED),
}
}
}
// No valid authentication found
Err(StatusCode::UNAUTHORIZED)
}