//! Authentication API endpoints use axum::{ extract::{State, Request}, http::StatusCode, Json, }; use serde::{Deserialize, Serialize}; use crate::auth::{ verify_password, AuthenticatedUser, JwtConfig, }; use crate::db; use crate::AppState; /// Login request #[derive(Debug, Deserialize)] pub struct LoginRequest { pub username: String, pub password: String, } /// Login response #[derive(Debug, Serialize)] pub struct LoginResponse { pub token: String, pub user: UserResponse, } /// User info in response #[derive(Debug, Serialize)] pub struct UserResponse { pub id: String, pub username: String, pub email: Option, pub role: String, pub permissions: Vec, } /// Error response #[derive(Debug, Serialize)] pub struct ErrorResponse { pub error: String, } /// POST /api/auth/login pub async fn login( State(state): State, Json(request): Json, ) -> Result, (StatusCode, Json)> { let db = state.db.as_ref().ok_or_else(|| { ( StatusCode::SERVICE_UNAVAILABLE, Json(ErrorResponse { error: "Database not available".to_string(), }), ) })?; // Get user by username let user = db::get_user_by_username(db.pool(), &request.username) .await .map_err(|e| { tracing::error!("Database error during login: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Internal server error".to_string(), }), ) })? .ok_or_else(|| { ( StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "Invalid username or password".to_string(), }), ) })?; // Check if user is enabled if !user.enabled { return Err(( StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "Account is disabled".to_string(), }), )); } // Verify password let password_valid = verify_password(&request.password, &user.password_hash) .map_err(|e| { tracing::error!("Password verification error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Internal server error".to_string(), }), ) })?; if !password_valid { return Err(( StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "Invalid username or password".to_string(), }), )); } // Get user permissions let permissions = db::get_user_permissions(db.pool(), user.id) .await .unwrap_or_default(); // Update last login let _ = db::update_last_login(db.pool(), user.id).await; // Create JWT token let token = state.jwt_config.create_token( user.id, &user.username, &user.role, permissions.clone(), ) .map_err(|e| { tracing::error!("Token creation error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to create token".to_string(), }), ) })?; tracing::info!("User {} logged in successfully", user.username); Ok(Json(LoginResponse { token, user: UserResponse { id: user.id.to_string(), username: user.username, email: user.email, role: user.role, permissions, }, })) } /// GET /api/auth/me - Get current user info pub async fn get_me( State(state): State, user: AuthenticatedUser, ) -> Result, (StatusCode, Json)> { let db = state.db.as_ref().ok_or_else(|| { ( StatusCode::SERVICE_UNAVAILABLE, Json(ErrorResponse { error: "Database not available".to_string(), }), ) })?; let user_id = uuid::Uuid::parse_str(&user.user_id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; let db_user = db::get_user_by_id(db.pool(), user_id) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Internal server error".to_string(), }), ) })? .ok_or_else(|| { ( StatusCode::NOT_FOUND, Json(ErrorResponse { error: "User not found".to_string(), }), ) })?; let permissions = db::get_user_permissions(db.pool(), db_user.id) .await .unwrap_or_default(); Ok(Json(UserResponse { id: db_user.id.to_string(), username: db_user.username, email: db_user.email, role: db_user.role, permissions, })) } /// Change password request #[derive(Debug, Deserialize)] pub struct ChangePasswordRequest { pub current_password: String, pub new_password: String, } /// POST /api/auth/change-password pub async fn change_password( State(state): State, user: AuthenticatedUser, Json(request): Json, ) -> Result)> { let db = state.db.as_ref().ok_or_else(|| { ( StatusCode::SERVICE_UNAVAILABLE, Json(ErrorResponse { error: "Database not available".to_string(), }), ) })?; let user_id = uuid::Uuid::parse_str(&user.user_id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; // Get current user let db_user = db::get_user_by_id(db.pool(), user_id) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Internal server error".to_string(), }), ) })? .ok_or_else(|| { ( StatusCode::NOT_FOUND, Json(ErrorResponse { error: "User not found".to_string(), }), ) })?; // Verify current password let password_valid = verify_password(&request.current_password, &db_user.password_hash) .map_err(|e| { tracing::error!("Password verification error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Internal server error".to_string(), }), ) })?; if !password_valid { return Err(( StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "Current password is incorrect".to_string(), }), )); } // Validate new password if request.new_password.len() < 8 { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Password must be at least 8 characters".to_string(), }), )); } // Hash new password let new_hash = crate::auth::hash_password(&request.new_password) .map_err(|e| { tracing::error!("Password hashing error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to hash password".to_string(), }), ) })?; // Update password db::update_user_password(db.pool(), user_id, &new_hash) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to update password".to_string(), }), ) })?; tracing::info!("User {} changed their password", user.username); Ok(StatusCode::OK) }