// User preferences routes use axum::{ extract::{ConnectInfo, State}, http::{HeaderMap, StatusCode}, Json, }; use chrono::Utc; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use tracing::{error, warn}; use crate::config::UserSettingsAccess; use crate::logging::AuditLogger; use crate::AppState; // Request/Response structures matching the query route pattern #[derive(Debug, Deserialize)] pub struct PreferencesRequest { pub action: String, // "get", "set", "reset" pub user_id: Option, // For admin access to other users pub preferences: Option, } #[derive(Debug, Serialize)] pub struct PreferencesResponse { pub success: bool, pub preferences: Option, pub error: Option, } /// Extract token from Authorization header fn extract_token(headers: &HeaderMap) -> Option { headers .get("Authorization") .and_then(|header| header.to_str().ok()) .and_then(|auth_str| { if auth_str.starts_with("Bearer ") { Some(auth_str[7..].to_string()) } else { None } }) } /// POST /preferences - Handle all preference operations (get, set, reset) pub async fn handle_preferences( State(state): State, ConnectInfo(addr): ConnectInfo, headers: HeaderMap, Json(payload): Json, ) -> Result, StatusCode> { let timestamp = Utc::now(); let client_ip = addr.ip().to_string(); let request_id = AuditLogger::generate_request_id(); // Extract and validate session token let token = match extract_token(&headers) { Some(token) => token, None => { return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some( "Please stop trying to access this resource without signing in".to_string(), ), })); } }; let session = match state.session_manager.get_session(&token) { Some(session) => session, None => { return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some("Session not found".to_string()), })); } }; // Determine target user ID let target_user_id = payload.user_id.unwrap_or(session.user_id); // Get user's permission level for preferences let user_settings_permission = state .config .permissions .power_levels .get(&session.power.to_string()) .map(|p| &p.user_settings_access) .unwrap_or(&state.config.security.default_user_settings_access); // Check permissions for cross-user access if target_user_id != session.user_id { if *user_settings_permission != UserSettingsAccess::ReadWriteAll { // Log security warning super::log_warning_async( &state.logging, &request_id, &format!("User {} (power {}) attempted to access preferences of user {} without permission", session.username, session.power, target_user_id), Some("authorization"), Some(&session.username), Some(session.power), ); return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some("Insufficient permissions".to_string()), })); } } // Check write permissions for set/reset actions if payload.action == "set" || payload.action == "reset" { if target_user_id == session.user_id { // Writing own preferences - need at least ReadWriteOwn if *user_settings_permission == UserSettingsAccess::ReadOwnOnly { return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some("Insufficient permissions".to_string()), })); } } else { // Writing others' preferences - need ReadWriteAll if *user_settings_permission != UserSettingsAccess::ReadWriteAll { return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some("Insufficient permissions".to_string()), })); } } } // Log the request if let Err(e) = state .logging .log_request( &request_id, timestamp, &client_ip, Some(&session.username), Some(session.power), "/preferences", &serde_json::json!({"action": payload.action, "target_user_id": target_user_id}), ) .await { error!("[{}] Failed to log request: {}", request_id, e); } // Handle the action match payload.action.as_str() { "get" => { handle_get_preferences( state, request_id, target_user_id, session.username.clone(), session.power, ) .await } "set" => { handle_set_preferences( state, request_id, target_user_id, payload.preferences, session.username.clone(), session.power, ) .await } "reset" => { handle_reset_preferences( state, request_id, target_user_id, session.username.clone(), session.power, ) .await } _ => Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some(format!("Invalid action: {}", payload.action)), })), } } async fn handle_get_preferences( state: AppState, request_id: String, user_id: i32, username: String, power: i32, ) -> Result, StatusCode> { // Cast JSON column to CHAR to get string representation let query = "SELECT CAST(preferences AS CHAR) FROM users WHERE id = ? AND active = TRUE"; let row: Option<(Option,)> = sqlx::query_as(query) .bind(user_id) .fetch_optional(state.database.pool()) .await .map_err(|e| { let error_msg = format!( "Database error fetching preferences for user {}: {}", user_id, e ); super::log_error_async( &state.logging, &request_id, &error_msg, Some("database"), Some(&username), Some(power), ); StatusCode::INTERNAL_SERVER_ERROR })?; let preferences = match row { Some((Some(prefs_str),)) => serde_json::from_str(&prefs_str).unwrap_or_else(|e| { warn!( "[{}] Failed to parse preferences JSON for user {}: {}", request_id, user_id, e ); super::log_warning_async( &state.logging, &request_id, &format!( "Failed to parse preferences JSON for user {}: {}", user_id, e ), Some("data_integrity"), Some(&username), Some(power), ); serde_json::json!({}) }), _ => serde_json::json!({}), }; // Log user action super::log_info_async( &state.logging, &request_id, &format!( "User {} retrieved preferences for user {}", username, user_id ), Some("user_action"), Some(&username), Some(power), ); Ok(Json(PreferencesResponse { success: true, preferences: Some(preferences), error: None, })) } async fn handle_set_preferences( state: AppState, request_id: String, user_id: i32, new_preferences: Option, username: String, power: i32, ) -> Result, StatusCode> { let new_prefs = match new_preferences { Some(prefs) => prefs, None => { return Ok(Json(PreferencesResponse { success: false, preferences: None, error: Some("Missing preferences field".to_string()), })); } }; // Get current preferences for merging let query = "SELECT CAST(preferences AS CHAR) FROM users WHERE id = ? AND active = TRUE"; let row: Option<(Option,)> = sqlx::query_as(query) .bind(user_id) .fetch_optional(state.database.pool()) .await .map_err(|e| { let error_msg = format!( "Database error fetching preferences for user {}: {}", user_id, e ); super::log_error_async( &state.logging, &request_id, &error_msg, Some("database"), Some(&username), Some(power), ); StatusCode::INTERNAL_SERVER_ERROR })?; // Deep merge the preferences let mut merged_prefs = match row { Some((Some(prefs_str),)) => { serde_json::from_str(&prefs_str).unwrap_or_else(|_| serde_json::json!({})) } _ => serde_json::json!({}), }; // Merge function fn merge_json(base: &mut serde_json::Value, update: &serde_json::Value) { if let (Some(base_obj), Some(update_obj)) = (base.as_object_mut(), update.as_object()) { for (key, value) in update_obj { if let Some(base_value) = base_obj.get_mut(key) { if base_value.is_object() && value.is_object() { merge_json(base_value, value); } else { *base_value = value.clone(); } } else { base_obj.insert(key.clone(), value.clone()); } } } } merge_json(&mut merged_prefs, &new_prefs); // Save to database let prefs_str = serde_json::to_string(&merged_prefs).map_err(|e| { error!("[{}] Failed to serialize preferences: {}", request_id, e); StatusCode::INTERNAL_SERVER_ERROR })?; let update_query = "UPDATE users SET preferences = ? WHERE id = ? AND active = TRUE"; sqlx::query(update_query) .bind(&prefs_str) .bind(user_id) .execute(state.database.pool()) .await .map_err(|e| { let error_msg = format!( "Database error updating preferences for user {}: {}", user_id, e ); super::log_error_async( &state.logging, &request_id, &error_msg, Some("database"), Some(&username), Some(power), ); StatusCode::INTERNAL_SERVER_ERROR })?; // Log user action super::log_info_async( &state.logging, &request_id, &format!("User {} updated preferences for user {}", username, user_id), Some("user_action"), Some(&username), Some(power), ); Ok(Json(PreferencesResponse { success: true, preferences: Some(merged_prefs), error: None, })) } async fn handle_reset_preferences( state: AppState, request_id: String, user_id: i32, username: String, power: i32, ) -> Result, StatusCode> { let query = "UPDATE users SET preferences = NULL WHERE id = ? AND active = TRUE"; sqlx::query(query) .bind(user_id) .execute(state.database.pool()) .await .map_err(|e| { let error_msg = format!( "Database error resetting preferences for user {}: {}", user_id, e ); super::log_error_async( &state.logging, &request_id, &error_msg, Some("database"), Some(&username), Some(power), ); StatusCode::INTERNAL_SERVER_ERROR })?; // Log user action super::log_info_async( &state.logging, &request_id, &format!("User {} reset preferences for user {}", username, user_id), Some("user_action"), Some(&username), Some(power), ); Ok(Json(PreferencesResponse { success: true, preferences: Some(serde_json::json!({})), error: None, })) }