use crate::error::{ControlCenterError, Result}; use crate::middleware::RequestExt; use crate::models::{ ClientInfo, LoginRequest, LogoutRequest, RefreshTokenRequest, TokenResponse, }; use crate::services::AuthService; use crate::AppState; use axum::{ extract::{Request, State}, response::Json, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::info; /// Login endpoint pub async fn login( State(app_state): State>, Json(request): Json, ) -> Result>> { // Extract client info from headers (simplified - you might want more sophisticated detection) let client_info = Some(ClientInfo { user_agent: None, // Could extract from headers ip_address: None, // Could extract from connection info device_type: None, }); let token_response = app_state.auth_service.login(request, client_info).await?; Ok(Json(ApiResponse::success(token_response))) } /// Refresh token endpoint pub async fn refresh_token( State(app_state): State>, Json(request): Json, ) -> Result>> { let token_response = app_state.auth_service.refresh_token(request).await?; Ok(Json(ApiResponse::success(token_response))) } /// Logout endpoint pub async fn logout( State(app_state): State>, Json(logout_request): Json, ) -> Result>> { // For now, we'll extract user_id from the token in logout_request // This would need to be updated to work with proper authentication middleware let user_id = uuid::Uuid::new_v4(); // Placeholder - this should come from JWT validation app_state .auth_service .logout(logout_request, user_id) .await?; Ok(Json(ApiResponse::success( "Logged out successfully".to_string(), ))) } /// Verify token endpoint pub async fn verify_token( State(app_state): State>, request: Request, ) -> Result>> { let user_context = request.require_user_context()?; // Get user details let user = app_state .user_service .get_user_response_by_id(user_context.user_id) .await?; let response = TokenVerificationResponse { valid: true, user, session_id: user_context.session_id, roles: user_context.roles.clone(), }; Ok(Json(ApiResponse::success(response))) } /// Get current user sessions pub async fn get_sessions( State(app_state): State>, request: Request, ) -> Result>>> { let user_context = request.require_user_context()?; let sessions = app_state .auth_service .get_user_sessions(user_context.user_id) .await?; let session_responses = sessions .into_iter() .map(crate::models::SessionResponse::from) .collect(); Ok(Json(ApiResponse::success(session_responses))) } /// Invalidate all sessions (force logout everywhere) pub async fn invalidate_all_sessions( State(app_state): State>, request: Request, ) -> Result>> { let user_context = request.require_user_context()?; let logout_request = LogoutRequest { session_id: None, all_sessions: Some(true), }; app_state .auth_service .logout(logout_request, user_context.user_id) .await?; Ok(Json(ApiResponse::success( "All sessions invalidated successfully".to_string(), ))) } /// Health check endpoint (no auth required) pub async fn health_check() -> Json> { Json(ApiResponse::success(HealthCheckResponse { status: "healthy".to_string(), timestamp: chrono::Utc::now(), version: env!("CARGO_PKG_VERSION").to_string(), service: "control-center".to_string(), })) } /// Token verification response #[derive(Debug, Serialize)] pub struct TokenVerificationResponse { pub valid: bool, pub user: crate::models::UserResponse, pub session_id: uuid::Uuid, pub roles: Vec, } /// Health check response #[derive(Debug, Serialize)] pub struct HealthCheckResponse { pub status: String, pub timestamp: chrono::DateTime, pub version: String, pub service: String, } /// Generic API response wrapper #[derive(Debug, Serialize)] pub struct ApiResponse { pub success: bool, pub data: Option, pub message: Option, pub timestamp: chrono::DateTime, } impl ApiResponse { pub fn success(data: T) -> Self { Self { success: true, data: Some(data), message: None, timestamp: chrono::Utc::now(), } } pub fn success_with_message(data: T, message: String) -> Self { Self { success: true, data: Some(data), message: Some(message), timestamp: chrono::Utc::now(), } } pub fn error(message: String) -> ApiResponse<()> { ApiResponse { success: false, data: None, message: Some(message), timestamp: chrono::Utc::now(), } } } /// Password change request #[derive(Debug, Deserialize)] pub struct ChangePasswordRequest { pub current_password: String, pub new_password: String, } /// Change password endpoint (authenticated users only) pub async fn change_password( State(app_state): State>, Json(password_request): Json, request: Request, ) -> Result>> { let user_context = request.require_user_context()?; // Get current user let user = app_state.user_service.get_by_id(user_context.user_id).await?; // Verify current password if !app_state .auth_service .verify_password(&password_request.current_password, &user.password_hash)? { return Err(ControlCenterError::Authentication( "Current password is incorrect".to_string(), )); } // Hash new password let new_password_hash = AuthService::hash_password(&password_request.new_password)?; // Update user password let update_request = crate::models::UpdateUserRequest { password: Some(new_password_hash), ..Default::default() }; app_state .user_service .update_user(user_context.user_id, update_request) .await?; // Optionally invalidate all other sessions (force re-login) let logout_request = LogoutRequest { session_id: None, all_sessions: Some(true), }; app_state .auth_service .logout(logout_request, user_context.user_id) .await?; info!("Password changed for user: {}", user.username); Ok(Json(ApiResponse::success( "Password changed successfully. Please log in again.".to_string(), ))) } #[cfg(test)] mod tests { use super::*; #[test] fn test_api_response_creation() { let success_response = ApiResponse::success("test data".to_string()); assert!(success_response.success); assert_eq!(success_response.data, Some("test data".to_string())); assert!(success_response.message.is_none()); let success_with_message = ApiResponse::success_with_message( "test data".to_string(), "Operation completed".to_string(), ); assert!(success_with_message.success); assert_eq!(success_with_message.data, Some("test data".to_string())); assert_eq!( success_with_message.message, Some("Operation completed".to_string()) ); let error_response = ApiResponse::error("Something went wrong".to_string()); assert!(!error_response.success); assert!(error_response.data.is_none()); assert_eq!( error_response.message, Some("Something went wrong".to_string()) ); } }