#![allow(dead_code)]
pub mod api_key;
pub mod jwt;
pub mod rbac;
pub use crate::core::models::{ApiKey, User, UserRole, UserSession};
use crate::config::AuthConfig;
use crate::core::models::RequestContext;
use crate::storage::StorageLayer;
use crate::utils::error::{GatewayError, Result};
use std::sync::Arc;
use tracing::{debug, info};
use uuid::Uuid;
#[derive(Clone)]
pub struct AuthSystem {
config: Arc<AuthConfig>,
storage: Arc<StorageLayer>,
jwt: Arc<jwt::JwtHandler>,
api_key: Arc<api_key::ApiKeyHandler>,
rbac: Arc<rbac::RbacSystem>,
}
#[derive(Debug, Clone)]
pub struct AuthResult {
pub success: bool,
pub user: Option<User>,
pub api_key: Option<ApiKey>,
pub session: Option<UserSession>,
pub error: Option<String>,
pub context: RequestContext,
}
#[derive(Debug, Clone)]
pub struct AuthzResult {
pub allowed: bool,
pub required_permissions: Vec<String>,
pub user_permissions: Vec<String>,
pub reason: Option<String>,
}
#[derive(Debug, Clone)]
pub enum AuthMethod {
Jwt(String),
ApiKey(String),
Session(String),
None,
}
impl AuthSystem {
pub async fn new(config: &AuthConfig, storage: Arc<StorageLayer>) -> Result<Self> {
info!("Initializing authentication system");
let config = Arc::new(config.clone());
let jwt = Arc::new(jwt::JwtHandler::new(&config).await?);
let api_key = Arc::new(api_key::ApiKeyHandler::new(storage.clone()).await?);
let rbac = Arc::new(rbac::RbacSystem::new(&config.rbac).await?);
info!("Authentication system initialized successfully");
Ok(Self {
config,
storage,
jwt,
api_key,
rbac,
})
}
pub async fn authenticate(
&self,
auth_method: AuthMethod,
context: RequestContext,
) -> Result<AuthResult> {
debug!("Authenticating request: {:?}", auth_method);
match auth_method {
AuthMethod::Jwt(token) => self.authenticate_jwt(&token, context).await,
AuthMethod::ApiKey(key) => self.authenticate_api_key(&key, context).await,
AuthMethod::Session(session_id) => {
self.authenticate_session(&session_id, context).await
}
AuthMethod::None => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some("No authentication provided".to_string()),
context,
}),
}
}
async fn authenticate_jwt(
&self,
token: &str,
mut context: RequestContext,
) -> Result<AuthResult> {
match self.jwt.verify_token(token).await {
Ok(claims) => {
if let Ok(Some(user)) = self.storage.db().find_user_by_id(claims.sub).await {
if user.is_active() {
context.user_id = Some(user.id());
context.team_id = user.team_ids.first().copied();
Ok(AuthResult {
success: true,
user: Some(user),
api_key: None,
session: None,
error: None,
context,
})
} else {
Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some("User account is not active".to_string()),
context,
})
}
} else {
Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some("User not found".to_string()),
context,
})
}
}
Err(e) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some(format!("Invalid JWT token: {}", e)),
context,
}),
}
}
async fn authenticate_api_key(
&self,
key: &str,
mut context: RequestContext,
) -> Result<AuthResult> {
match self.api_key.verify_key(key).await {
Ok(Some((api_key, user))) => {
context.api_key_id = Some(api_key.metadata.id);
context.user_id = api_key.user_id;
context.team_id = api_key.team_id;
Ok(AuthResult {
success: true,
user,
api_key: Some(api_key),
session: None,
error: None,
context,
})
}
Ok(None) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some("Invalid API key".to_string()),
context,
}),
Err(e) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some(format!("API key verification failed: {}", e)),
context,
}),
}
}
async fn authenticate_session(
&self,
session_id: &str,
mut context: RequestContext,
) -> Result<AuthResult> {
match self.jwt.verify_token(session_id).await {
Ok(claims) => {
match self.storage.db().find_user_by_id(claims.sub).await {
Ok(Some(user)) => {
context.user_id = Some(user.id());
context.team_id = user.team_ids.first().copied();
Ok(AuthResult {
success: true,
user: Some(user),
api_key: None,
session: None, error: None,
context,
})
}
Ok(None) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some("User not found".to_string()),
context,
}),
Err(e) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some(format!("User lookup failed: {}", e)),
context,
}),
}
}
Err(e) => Ok(AuthResult {
success: false,
user: None,
api_key: None,
session: None,
error: Some(format!("Session verification failed: {}", e)),
context,
}),
}
}
pub async fn authorize(&self, user: &User, permissions: &[String]) -> Result<AuthzResult> {
debug!(
"Authorizing user {} for permissions: {:?}",
user.username, permissions
);
let user_permissions = self.rbac.get_user_permissions(user).await?;
let allowed = self.rbac.check_permissions(&user_permissions, permissions);
Ok(AuthzResult {
allowed,
required_permissions: permissions.to_vec(),
user_permissions,
reason: if !allowed {
Some("Insufficient permissions".to_string())
} else {
None
},
})
}
pub async fn create_user(
&self,
username: String,
email: String,
password: String,
) -> Result<User> {
info!("Creating new user: {}", username);
let password_hash = crate::utils::crypto::hash_password(&password)?;
let user = User::new(username, email, password_hash);
self.storage.db().create_user(&user).await
}
pub async fn login(&self, username: &str, password: &str) -> Result<(User, String)> {
info!("User login attempt: {}", username);
let user = self
.storage
.db()
.find_user_by_username(username)
.await?
.ok_or_else(|| GatewayError::auth("Invalid username or password"))?;
if !crate::utils::crypto::verify_password(password, &user.password_hash)? {
return Err(GatewayError::auth("Invalid username or password"));
}
if !user.is_active() {
return Err(GatewayError::auth("Account is not active"));
}
let session_id = uuid::Uuid::new_v4();
let permissions = self.get_user_permissions(&user).await?;
let session_token = self
.jwt
.create_access_token(
user.id(),
format!("{:?}", user.role),
permissions,
user.team_ids.first().copied(),
Some(session_id),
)
.await?;
self.storage.db().update_user_last_login(user.id()).await?;
info!("User logged in successfully: {}", username);
Ok((user, session_token))
}
async fn get_user_permissions(&self, user: &User) -> Result<Vec<String>> {
let permissions = match user.role {
UserRole::Admin => vec![
"read:all".to_string(),
"write:all".to_string(),
"delete:all".to_string(),
"manage:users".to_string(),
"manage:api_keys".to_string(),
"manage:teams".to_string(),
],
UserRole::SuperAdmin => vec![
"read:all".to_string(),
"write:all".to_string(),
"delete:all".to_string(),
"manage:users".to_string(),
"manage:api_keys".to_string(),
"manage:teams".to_string(),
"manage:system".to_string(),
],
UserRole::Manager => vec![
"read:team".to_string(),
"write:team".to_string(),
"manage:team_users".to_string(),
"manage:team_api_keys".to_string(),
],
UserRole::User => vec![
"read:own".to_string(),
"write:own".to_string(),
"use:api".to_string(),
],
UserRole::ApiUser => vec!["use:api".to_string()],
UserRole::Viewer => vec!["read:own".to_string()],
};
Ok(permissions)
}
pub async fn logout(&self, session_token: &str) -> Result<()> {
info!("User logout");
if let Ok(claims) = self.jwt.verify_token(session_token).await {
if let Some(session_id) = claims.session_id {
info!("Invalidated session: {}", session_id);
}
}
Ok(())
}
pub async fn create_api_key(
&self,
user_id: Uuid,
name: String,
permissions: Vec<String>,
) -> Result<(ApiKey, String)> {
info!("Creating API key for user: {}", user_id);
self.api_key
.create_key(Some(user_id), None, name, permissions)
.await
}
pub async fn revoke_api_key(&self, key_id: Uuid) -> Result<()> {
info!("Revoking API key: {}", key_id);
self.api_key.revoke_key(key_id).await
}
pub async fn change_password(
&self,
user_id: Uuid,
old_password: &str,
new_password: &str,
) -> Result<()> {
info!("Changing password for user: {}", user_id);
let user = self
.storage
.db()
.find_user_by_id(user_id)
.await?
.ok_or_else(|| GatewayError::not_found("User not found"))?;
if !crate::utils::crypto::verify_password(old_password, &user.password_hash)? {
return Err(GatewayError::auth("Invalid current password"));
}
let new_password_hash = crate::utils::crypto::hash_password(new_password)?;
self.storage
.db()
.update_user_password(user_id, &new_password_hash)
.await?;
info!("Password changed successfully for user: {}", user_id);
Ok(())
}
pub async fn request_password_reset(&self, email: &str) -> Result<String> {
info!("Password reset requested for email: {}", email);
let user = self
.storage
.db()
.find_user_by_email(email)
.await?
.ok_or_else(|| GatewayError::not_found("User not found"))?;
let reset_token = crate::utils::crypto::generate_token(32);
let expires_at = chrono::Utc::now() + chrono::Duration::hours(1);
self.storage
.db()
.store_password_reset_token(user.id(), &reset_token, expires_at)
.await?;
info!("Password reset token generated for user: {}", user.id());
Ok(reset_token)
}
pub async fn reset_password(&self, token: &str, new_password: &str) -> Result<()> {
info!("Resetting password with token");
let user_id = self
.storage
.db()
.verify_password_reset_token(token)
.await?
.ok_or_else(|| GatewayError::auth("Invalid or expired reset token"))?;
let password_hash = crate::utils::crypto::hash_password(new_password)?;
self.storage
.db()
.update_user_password(user_id, &password_hash)
.await?;
self.storage
.db()
.invalidate_password_reset_token(token)
.await?;
info!("Password reset successfully for user: {}", user_id);
Ok(())
}
pub fn config(&self) -> &AuthConfig {
&self.config
}
pub fn jwt(&self) -> &jwt::JwtHandler {
&self.jwt
}
pub fn api_key(&self) -> &api_key::ApiKeyHandler {
&self.api_key
}
pub fn rbac(&self) -> &rbac::RbacSystem {
&self.rbac
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_result_creation() {
let context = RequestContext::new();
let result = AuthResult {
success: true,
user: None,
api_key: None,
session: None,
error: None,
context,
};
assert!(result.success);
assert!(result.error.is_none());
}
#[test]
fn test_authz_result_creation() {
let result = AuthzResult {
allowed: true,
required_permissions: vec!["read".to_string()],
user_permissions: vec!["read".to_string(), "write".to_string()],
reason: None,
};
assert!(result.allowed);
assert_eq!(result.required_permissions.len(), 1);
assert_eq!(result.user_permissions.len(), 2);
}
#[test]
fn test_auth_method_variants() {
let jwt_method = AuthMethod::Jwt("token".to_string());
let api_key_method = AuthMethod::ApiKey("key".to_string());
let session_method = AuthMethod::Session("session".to_string());
let none_method = AuthMethod::None;
assert!(matches!(jwt_method, AuthMethod::Jwt(_)));
assert!(matches!(api_key_method, AuthMethod::ApiKey(_)));
assert!(matches!(session_method, AuthMethod::Session(_)));
assert!(matches!(none_method, AuthMethod::None));
}
}