use std::path::PathBuf; use crate::api::model::AppState; use crate::model::{ApiProxyConfig, ProxyUserCredentials, TargetUser}; use crate::repository::user_repository::store_api_user; use axum::response::IntoResponse; use axum::Router; use serde_json::json; use shared::model::{ApiProxyConfigDto, ProxyUserCredentialsDto}; use std::sync::Arc; use shared::utils::mask_credentials; async fn save_config_api_proxy_user( method: axum::http::Method, axum::extract::State(app_state): axum::extract::State>, axum::extract::Path(target_name): axum::extract::Path, axum::extract::Json(mut credential): axum::extract::Json, ) -> impl axum::response::IntoResponse + Send { let virtual_file = PathBuf::from("api_proxy"); let _lock = app_state.app_config.file_locks.write_lock(&virtual_file).await; credential.prepare(); if let Err(err) = credential.validate() { return ( axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": err.to_string()})) ).into_response(); } let is_update = method == axum::http::Method::PUT; let mut api_proxy = if let Some(old) = app_state.app_config.api_proxy.load().clone() { (*old).clone() } else { ApiProxyConfig::default() }; // ---------- Search for existing Target and existing User ---------- let mut existing_target_index: Option = None; // index of target (target_name), falls vorhanden let mut existing_user_target_index: Option = None; // index of existing users target let mut existing_user_index: Option = None; // index of the user in the targets credentials list for (t_idx, target_user) in api_proxy.user.iter().enumerate() { if target_user.target == target_name { existing_target_index = Some(t_idx); } for (u_idx, user) in target_user.credentials.iter().enumerate() { if let (Some(u), Some(c)) = (&user.token, &credential.token) { if u == c && user.username != credential.username { return ( axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": format!("Duplicate token {}", mask_credentials(c))})) ).into_response(); } } if user.username == credential.username { // if not an update und username exists -> Error (duplicate username) if !is_update { return ( axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": format!("Duplicate username {}", &credential.username)})) ).into_response(); } // mark position of user (for update / move) existing_user_target_index = Some(t_idx); existing_user_index = Some(u_idx); } } } // ---------- if update but no user found -> Error ---------- if is_update && existing_user_index.is_none() { return ( axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": format!("User {} not found", credential.username)})) ).into_response(); } // ---------- create target if new target does not exist ---------- let target_idx = if let Some(idx) = existing_target_index { idx } else { api_proxy.user.push(TargetUser { target: target_name.clone(), credentials: vec![], }); api_proxy.user.len() - 1 }; if is_update { let mut remove_empty_target = false; // existing_user_index and existing_user_target_index exists at this point let user_idx = existing_user_index.unwrap(); let user_target_idx = existing_user_target_index.unwrap(); if user_target_idx == target_idx { // Update api_proxy.user[user_target_idx].credentials[user_idx] = ProxyUserCredentials::from(&credential); } else { // Move: remove from old target and insert into new target api_proxy.user[user_target_idx].credentials.remove(user_idx); api_proxy.user[target_idx].credentials.push(ProxyUserCredentials::from(&credential)); remove_empty_target = api_proxy.user[user_target_idx].credentials.is_empty(); } if remove_empty_target { api_proxy.user.retain(|t| !t.credentials.is_empty()); } } else { // new user api_proxy.user[target_idx].credentials.push(ProxyUserCredentials::from(&credential)); } let new_api_proxy = Arc::new(api_proxy); if new_api_proxy.use_user_db { if let Err(err) = store_api_user(&app_state.app_config, &new_api_proxy.user) { return ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(json!({"error": err.to_string()})) ).into_response(); } } else { let config = app_state.app_config.config.load(); let backup_dir = config.get_backup_dir(); let paths = app_state.app_config.paths.load(); if let Some(err) = crate::api::endpoints::v1_api_config::intern_save_config_api_proxy( backup_dir.as_ref(), &ApiProxyConfigDto::from(&*new_api_proxy), paths.api_proxy_file_path.as_str(), ) { return ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(json!({"error": err.to_string()})) ).into_response(); } } // Update state after successful save app_state.app_config.api_proxy.store(Some(Arc::clone(&new_api_proxy))); axum::http::StatusCode::OK.into_response() } async fn delete_config_api_proxy_user( axum::extract::State(app_state): axum::extract::State>, axum::extract::Path((target_name, username)): axum::extract::Path<(String, String)>, ) -> impl axum::response::IntoResponse + Send { if let Some(old_api_proxy) = app_state.app_config.api_proxy.load().clone() { let mut api_proxy = (*old_api_proxy).clone(); let mut modified = false; for target_user in &mut api_proxy.user { if target_user.target == target_name { let count = target_user.credentials.len(); target_user.credentials.retain(|user| user.username != username); modified = count != target_user.credentials.len(); break; } } if modified { let new_api_proxy = Arc::new(api_proxy); if new_api_proxy.use_user_db { if let Err(err) = store_api_user(&app_state.app_config, &new_api_proxy.user) { return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(json!({"error": err.to_string()}))).into_response(); } } else { let config = app_state.app_config.config.load(); let backup_dir = config.get_backup_dir(); let paths = app_state.app_config.paths.load(); if let Some(err) = crate::api::endpoints::v1_api_config::intern_save_config_api_proxy(backup_dir.as_ref(), &ApiProxyConfigDto::from(&*new_api_proxy), paths.api_proxy_file_path.as_str()) { return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(json!({"error": err.to_string()}))).into_response(); } } app_state.app_config.api_proxy.store(Some(Arc::clone(&new_api_proxy))); } else { return (axum::http::StatusCode::BAD_REQUEST, axum::Json(json!({"error": format!("User not found {username} in target {target_name}")}))).into_response(); } } axum::http::StatusCode::OK.into_response() } pub fn v1_api_user_register(router: Router>) -> axum::Router> { router .route("/user/{target}", axum::routing::post(save_config_api_proxy_user)) .route("/user/{target}", axum::routing::put(save_config_api_proxy_user)) .route("/user/{target}/{username}", axum::routing::delete(delete_config_api_proxy_user)) }