//! User management API endpoints (admin only) use axum::{ extract::{Path, State}, http::StatusCode, Json, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::auth::{hash_password, AdminUser}; use crate::db; use crate::AppState; use super::auth::ErrorResponse; /// User info response #[derive(Debug, Serialize)] pub struct UserInfo { pub id: String, pub username: String, pub email: Option, pub role: String, pub enabled: bool, pub created_at: String, pub last_login: Option, pub permissions: Vec, } /// Create user request #[derive(Debug, Deserialize)] pub struct CreateUserRequest { pub username: String, pub password: String, pub email: Option, pub role: String, pub permissions: Option>, } /// Update user request #[derive(Debug, Deserialize)] pub struct UpdateUserRequest { pub email: Option, pub role: String, pub enabled: bool, pub password: Option, } /// Set permissions request #[derive(Debug, Deserialize)] pub struct SetPermissionsRequest { pub permissions: Vec, } /// Set client access request #[derive(Debug, Deserialize)] pub struct SetClientAccessRequest { pub client_ids: Vec, } /// GET /api/users - List all users pub async fn list_users( State(state): State, _admin: AdminUser, ) -> Result>, (StatusCode, Json)> { let db = state.db.as_ref().ok_or_else(|| { ( StatusCode::SERVICE_UNAVAILABLE, Json(ErrorResponse { error: "Database not available".to_string(), }), ) })?; let users = db::get_all_users(db.pool()) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to fetch users".to_string(), }), ) })?; let mut result = Vec::new(); for user in users { let permissions = db::get_user_permissions(db.pool(), user.id) .await .unwrap_or_default(); result.push(UserInfo { id: user.id.to_string(), username: user.username, email: user.email, role: user.role, enabled: user.enabled, created_at: user.created_at.to_rfc3339(), last_login: user.last_login.map(|t| t.to_rfc3339()), permissions, }); } Ok(Json(result)) } /// POST /api/users - Create new user pub async fn create_user( State(state): State, _admin: AdminUser, 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(), }), ) })?; // Validate role let valid_roles = ["admin", "operator", "viewer"]; if !valid_roles.contains(&request.role.as_str()) { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: format!("Invalid role. Must be one of: {:?}", valid_roles), }), )); } // Validate password if request.password.len() < 8 { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Password must be at least 8 characters".to_string(), }), )); } // Check if username exists if db::get_user_by_username(db.pool(), &request.username) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Database error".to_string(), }), ) })? .is_some() { return Err(( StatusCode::CONFLICT, Json(ErrorResponse { error: "Username already exists".to_string(), }), )); } // Hash password let password_hash = hash_password(&request.password).map_err(|e| { tracing::error!("Password hashing error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to hash password".to_string(), }), ) })?; // Create user let user = db::create_user( db.pool(), &request.username, &password_hash, request.email.as_deref(), &request.role, ) .await .map_err(|e| { tracing::error!("Failed to create user: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to create user".to_string(), }), ) })?; // Set initial permissions if provided let permissions = if let Some(perms) = request.permissions { db::set_user_permissions(db.pool(), user.id, &perms) .await .map_err(|e| { tracing::error!("Failed to set permissions: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to set permissions".to_string(), }), ) })?; perms } else { // Default permissions based on role let default_perms = match request.role.as_str() { "admin" => vec!["view", "control", "transfer", "manage_users", "manage_clients"], "operator" => vec!["view", "control", "transfer"], "viewer" => vec!["view"], _ => vec!["view"], }; let perms: Vec = default_perms.into_iter().map(String::from).collect(); db::set_user_permissions(db.pool(), user.id, &perms) .await .ok(); perms }; tracing::info!("Created user: {} ({})", user.username, user.role); Ok(Json(UserInfo { id: user.id.to_string(), username: user.username, email: user.email, role: user.role, enabled: user.enabled, created_at: user.created_at.to_rfc3339(), last_login: None, permissions, })) } /// GET /api/users/:id - Get user details pub async fn get_user( State(state): State, _admin: AdminUser, Path(id): Path, ) -> 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::parse_str(&id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; let 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: "Database 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(), user.id) .await .unwrap_or_default(); Ok(Json(UserInfo { id: user.id.to_string(), username: user.username, email: user.email, role: user.role, enabled: user.enabled, created_at: user.created_at.to_rfc3339(), last_login: user.last_login.map(|t| t.to_rfc3339()), permissions, })) } /// PUT /api/users/:id - Update user pub async fn update_user( State(state): State, admin: AdminUser, Path(id): Path, 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(), }), ) })?; let user_id = Uuid::parse_str(&id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; // Prevent admin from disabling themselves if user_id.to_string() == admin.0.user_id && !request.enabled { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Cannot disable your own account".to_string(), }), )); } // Validate role let valid_roles = ["admin", "operator", "viewer"]; if !valid_roles.contains(&request.role.as_str()) { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: format!("Invalid role. Must be one of: {:?}", valid_roles), }), )); } // Update user let user = db::update_user( db.pool(), user_id, request.email.as_deref(), &request.role, request.enabled, ) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to update user".to_string(), }), ) })? .ok_or_else(|| { ( StatusCode::NOT_FOUND, Json(ErrorResponse { error: "User not found".to_string(), }), ) })?; // Update password if provided if let Some(password) = request.password { if password.len() < 8 { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Password must be at least 8 characters".to_string(), }), )); } let password_hash = hash_password(&password).map_err(|e| { tracing::error!("Password hashing error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to hash password".to_string(), }), ) })?; db::update_user_password(db.pool(), user_id, &password_hash) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to update password".to_string(), }), ) })?; } let permissions = db::get_user_permissions(db.pool(), user.id) .await .unwrap_or_default(); tracing::info!("Updated user: {}", user.username); Ok(Json(UserInfo { id: user.id.to_string(), username: user.username, email: user.email, role: user.role, enabled: user.enabled, created_at: user.created_at.to_rfc3339(), last_login: user.last_login.map(|t| t.to_rfc3339()), permissions, })) } /// DELETE /api/users/:id - Delete user pub async fn delete_user( State(state): State, admin: AdminUser, Path(id): Path, ) -> 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::parse_str(&id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; // Prevent admin from deleting themselves if user_id.to_string() == admin.0.user_id { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Cannot delete your own account".to_string(), }), )); } let deleted = db::delete_user(db.pool(), user_id) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to delete user".to_string(), }), ) })?; if deleted { tracing::info!("Deleted user: {}", id); Ok(StatusCode::NO_CONTENT) } else { Err(( StatusCode::NOT_FOUND, Json(ErrorResponse { error: "User not found".to_string(), }), )) } } /// PUT /api/users/:id/permissions - Set user permissions pub async fn set_permissions( State(state): State, _admin: AdminUser, Path(id): Path, 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::parse_str(&id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; // Validate permissions let valid_permissions = ["view", "control", "transfer", "manage_users", "manage_clients"]; for perm in &request.permissions { if !valid_permissions.contains(&perm.as_str()) { return Err(( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: format!("Invalid permission: {}. Valid: {:?}", perm, valid_permissions), }), )); } } db::set_user_permissions(db.pool(), user_id, &request.permissions) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to set permissions".to_string(), }), ) })?; tracing::info!("Updated permissions for user: {}", id); Ok(StatusCode::OK) } /// PUT /api/users/:id/clients - Set user client access pub async fn set_client_access( State(state): State, _admin: AdminUser, Path(id): Path, 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::parse_str(&id).map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid user ID".to_string(), }), ) })?; // Parse client IDs let client_ids: Result, _> = request .client_ids .iter() .map(|s| Uuid::parse_str(s)) .collect(); let client_ids = client_ids.map_err(|_| { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid client ID format".to_string(), }), ) })?; db::set_user_client_access(db.pool(), user_id, &client_ids) .await .map_err(|e| { tracing::error!("Database error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Failed to set client access".to_string(), }), ) })?; tracing::info!("Updated client access for user: {}", id); Ok(StatusCode::OK) }