secretumvault/src/api/handlers.rs

229 lines
8.5 KiB
Rust
Raw Normal View History

use std::sync::Arc;
2025-12-22 21:34:01 +00:00
#[cfg(feature = "server")]
use axum::{
extract::{Extension, Path},
2025-12-22 21:34:01 +00:00
http::StatusCode,
response::IntoResponse,
Json,
};
use serde_json::{json, Value};
use super::ApiResponse;
use crate::core::VaultCore;
/// Helper: Try reading with fallback path reconstruction
#[cfg(feature = "server")]
async fn try_fallback_read(
vault: &Arc<VaultCore>,
full_path: &str,
) -> Option<axum::response::Response> {
for (mount_path, _) in vault.engines.iter() {
let slash = if full_path.starts_with('/') { "" } else { "/" };
let reconstructed = format!("{}{}{}", mount_path, slash, full_path);
let (_, relative_path) = vault.split_path(&reconstructed)?;
let engine = vault.route_to_engine(&reconstructed)?;
let engine_path = relative_path.trim_start_matches('/');
if let Ok(Some(data)) = engine.read(engine_path).await {
let response = ApiResponse::success(data);
return Some((StatusCode::OK, Json(response)).into_response());
}
}
None
}
2025-12-22 21:34:01 +00:00
/// GET /v1/* - Read a secret from any mounted engine
#[cfg(feature = "server")]
pub async fn read_secret(
Extension(vault): Extension<Arc<VaultCore>>,
2025-12-22 21:34:01 +00:00
Path(path): Path<String>,
) -> impl IntoResponse {
let full_path = path;
if let Some((_mount_path, relative_path)) = vault.split_path(&full_path) {
if let Some(engine) = vault.route_to_engine(&full_path) {
return match engine.read(&relative_path).await {
2025-12-22 21:34:01 +00:00
Ok(Some(data)) => {
let response = ApiResponse::success(data);
(StatusCode::OK, Json(response)).into_response()
}
Ok(None) => {
let response = ApiResponse::<Value>::error("Secret not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
Err(e) => {
let response = ApiResponse::<Value>::error(format!("Failed to read: {}", e));
(StatusCode::INTERNAL_SERVER_ERROR, Json(response)).into_response()
}
};
2025-12-22 21:34:01 +00:00
}
let response = ApiResponse::<Value>::error("No engine mounted at this path");
return (StatusCode::NOT_FOUND, Json(response)).into_response();
}
// Try fallback path reconstruction
if let Some(response) = try_fallback_read(&vault, &full_path).await {
return response;
2025-12-22 21:34:01 +00:00
}
let response = ApiResponse::<Value>::error("Path not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
/// Helper: Try writing with fallback path reconstruction
#[cfg(feature = "server")]
async fn try_fallback_write(
vault: &Arc<VaultCore>,
full_path: &str,
payload: &Value,
) -> Option<axum::response::Response> {
for (mount_path, _) in vault.engines.iter() {
let slash = if full_path.starts_with('/') { "" } else { "/" };
let reconstructed = format!("{}{}{}", mount_path, slash, full_path);
let (_, relative_path) = vault.split_path(&reconstructed)?;
let engine = vault.route_to_engine(&reconstructed)?;
let engine_path = relative_path.trim_start_matches('/');
if engine.write(engine_path, payload).await.is_ok() {
let response = ApiResponse::success(json!({"path": full_path}));
return Some((StatusCode::OK, Json(response)).into_response());
}
}
None
2025-12-22 21:34:01 +00:00
}
/// POST /v1/* - Write a secret to any mounted engine
#[cfg(feature = "server")]
pub async fn write_secret(
Extension(vault): Extension<Arc<VaultCore>>,
2025-12-22 21:34:01 +00:00
Path(path): Path<String>,
Json(payload): Json<Value>,
) -> impl IntoResponse {
let full_path = path;
if let Some((_mount_path, relative_path)) = vault.split_path(&full_path) {
if let Some(engine) = vault.route_to_engine(&full_path) {
return match engine.write(&relative_path, &payload).await {
2025-12-22 21:34:01 +00:00
Ok(()) => {
let response = ApiResponse::success(json!({"path": full_path}));
(StatusCode::OK, Json(response)).into_response()
}
Err(e) => {
let response = ApiResponse::<Value>::error(format!("Failed to write: {}", e));
(StatusCode::BAD_REQUEST, Json(response)).into_response()
}
};
2025-12-22 21:34:01 +00:00
}
let response = ApiResponse::<Value>::error("No engine mounted at this path");
return (StatusCode::NOT_FOUND, Json(response)).into_response();
2025-12-22 21:34:01 +00:00
}
// Try fallback path reconstruction
if let Some(response) = try_fallback_write(&vault, &full_path, &payload).await {
return response;
}
let response = ApiResponse::<Value>::error("Path not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
2025-12-22 21:34:01 +00:00
}
/// PUT /v1/* - Update a secret in any mounted engine
#[cfg(feature = "server")]
pub async fn update_secret(
Extension(vault): Extension<Arc<VaultCore>>,
2025-12-22 21:34:01 +00:00
Path(path): Path<String>,
Json(payload): Json<Value>,
) -> impl IntoResponse {
let full_path = path;
match vault.split_path(&full_path) {
Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) {
Some(engine) => match engine.write(&relative_path, &payload).await {
Ok(()) => {
let response = ApiResponse::success(json!({"path": full_path}));
(StatusCode::OK, Json(response)).into_response()
}
Err(e) => {
let response = ApiResponse::<Value>::error(format!("Failed to update: {}", e));
(StatusCode::BAD_REQUEST, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("No engine mounted at this path");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("Path not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
}
}
/// DELETE /v1/* - Delete a secret from any mounted engine
#[cfg(feature = "server")]
pub async fn delete_secret(
Extension(vault): Extension<Arc<VaultCore>>,
2025-12-22 21:34:01 +00:00
Path(path): Path<String>,
) -> impl IntoResponse {
let full_path = path;
match vault.split_path(&full_path) {
Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) {
Some(engine) => match engine.delete(&relative_path).await {
Ok(()) => {
let response: ApiResponse<Value> = ApiResponse::success(json!({}));
(StatusCode::NO_CONTENT, Json(response)).into_response()
}
Err(e) => {
let response = ApiResponse::<Value>::error(format!("Failed to delete: {}", e));
(StatusCode::BAD_REQUEST, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("No engine mounted at this path");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("Path not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
}
}
/// LIST /v1/* - List secrets at a path prefix
#[cfg(feature = "server")]
pub async fn list_secrets(
Extension(vault): Extension<Arc<VaultCore>>,
2025-12-22 21:34:01 +00:00
Path(path): Path<String>,
) -> impl IntoResponse {
let full_path = path;
match vault.split_path(&full_path) {
Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) {
Some(engine) => match engine.list(&relative_path).await {
Ok(items) => {
let response = ApiResponse::success(json!({"keys": items}));
(StatusCode::OK, Json(response)).into_response()
}
Err(e) => {
let response = ApiResponse::<Value>::error(format!("Failed to list: {}", e));
(StatusCode::BAD_REQUEST, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("No engine mounted at this path");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
},
None => {
let response = ApiResponse::<Value>::error("Path not found");
(StatusCode::NOT_FOUND, Json(response)).into_response()
}
}
}