use crate::auth::{AuthManager, OptionalTokenClaims};
use crate::error::{ServerError, ServerResult};
use crate::models::*;
use crate::metrics::MetricsCollector;
use axum::{
extract::{Path, Query, State},
response::Json,
};
use chrono::{Utc, DateTime};
use fortress_core::{
encryption::{EncryptionAlgorithm, Aegis256},
key::{KeyManager, SecureKey, InMemoryKeyManager},
storage::StorageBackend,
field_encryption::FieldEncryptionManager,
tenant::{TenantManager, InMemoryTenantManager, CreateTenantRequest, TenantResourceLimits},
dynamic_secrets::DynamicSecretsEngine,
};
use crate::health::HealthChecker;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::info;
use uuid::Uuid;
use utoipa::{
OpenApi,
};
pub fn sanitize_error(error: &ServerError) -> &'static str {
match error {
ServerError::Core(core_err) => {
match core_err {
fortress_core::error::FortressError::Storage { .. } => "Database operation failed",
fortress_core::error::FortressError::Encryption { .. } => "Data protection failed",
fortress_core::error::FortressError::KeyManagement { .. } => "Key operation failed",
fortress_core::error::FortressError::Configuration { .. } => "Configuration error",
fortress_core::error::FortressError::Cluster { .. } => "Cluster operation failed",
fortress_core::error::FortressError::QueryExecution { .. } => "Query operation failed",
fortress_core::error::FortressError::Validation { .. } => "Invalid input provided",
fortress_core::error::FortressError::Io { .. } => "I/O operation failed",
fortress_core::error::FortressError::Network { .. } => "Network operation failed",
fortress_core::error::FortressError::Authentication { .. } => "Authentication failed",
fortress_core::error::FortressError::RateLimit { .. } => "Rate limit exceeded",
fortress_core::error::FortressError::Internal { .. } => "Internal server error",
fortress_core::error::FortressError::PolicyError(_) => "Access denied",
fortress_core::error::FortressError::Token { .. } => "Authentication failed",
fortress_core::error::FortressError::Seal { .. } => "Data protection failed",
fortress_core::error::FortressError::Plugin { .. } => "Plugin operation failed",
_ => "Internal server error",
}
},
ServerError::Authentication(_) => "Authentication failed",
ServerError::Authorization(_) => "Access denied",
ServerError::Validation(_) => "Invalid input provided",
ServerError::NotFound(_) => "Resource not found",
ServerError::Conflict(_) => "Resource conflict",
ServerError::RateLimit => "Rate limit exceeded",
ServerError::DdosBlocked => "Request blocked",
ServerError::QuotaExceeded(_) => "Quota exceeded",
ServerError::PayloadTooLarge(_) => "Payload too large",
ServerError::Serialization(_) => "Data processing failed",
ServerError::Network(_) => "Network operation failed",
ServerError::Configuration(_) => "Configuration error",
ServerError::Internal(_) => "Internal server error",
ServerError::Timeout => "Request timeout",
ServerError::Unavailable(_) => "Service unavailable",
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageRecord {
pub id: String,
pub key_id: String,
pub data: Vec<u8>,
pub algorithm: String,
pub created_at: DateTime<Utc>,
pub metadata: Option<HashMap<String, serde_json::Value>>,
pub tenant_id: Option<String>,
pub field_metadata: Option<HashMap<String, FieldEncryptionMetadata>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct QueryParams {
pub tenant_id: Option<String>,
pub pagination: PaginationParams,
pub filter: Option<FilterParams>,
pub sort: SortParams,
}
#[derive(Debug, Clone)]
pub struct QueryResults {
pub records: Vec<StorageRecord>,
pub total_count: u64,
}
#[derive(Clone)]
pub struct AppState {
pub auth_manager: Arc<AuthManager>,
pub metrics: Arc<MetricsCollector>,
pub key_manager: Arc<InMemoryKeyManager>,
pub field_encryption_manager: Arc<dyn FieldEncryptionManager>,
pub storage: Arc<dyn StorageBackend>,
pub health_checker: Arc<HealthChecker>,
pub tenant_manager: Arc<InMemoryTenantManager>,
pub dynamic_secrets: Arc<DynamicSecretsEngine>,
}
#[utoipa::path(
post,
path = "/api/v1/data",
request_body = StoreRequest,
responses(
(status = 200, description = "Data stored successfully", body = ApiResponse<StoreDataResponse>),
(status = 400, description = "Invalid request", body = ErrorResponse),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
tag = "data",
security(
("jwt_auth" = [])
)
)]
pub async fn store_data(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Json(request): Json<StoreRequest>,
) -> ServerResult<Json<ApiResponse<StoreDataResponse>>> {
let start_time = std::time::Instant::now();
info!(
user_id = ?claims.as_ref().map(|c| &c.sub),
tenant_id = ?request.tenant_id,
"Store data request received"
);
let validator = fortress_core::input_validation::InputValidator::new();
if let Some(ref tenant_id) = request.tenant_id {
validator.validate_string(tenant_id, "tenant_id")?;
if let Some(ref claims) = claims {
if !state.auth_manager.has_tenant_access(claims, tenant_id) {
return Err(ServerError::access_denied("Access denied to tenant"));
}
}
}
if let Some(ref key_id) = request.key_id {
validator.validate_string(key_id, "key_id")?;
}
if let Some(ref algorithm) = request.algorithm {
validator.validate_string(algorithm, "algorithm")?;
}
let data_str = serde_json::to_string(&request.data)
.map_err(|e| ServerError::validation(format!("Invalid JSON data: {}", e)))?;
validator.validate_length(&data_str, 0, 100000)?;
let data_id = Uuid::new_v4().to_string();
let key_id = request.key_id.clone().unwrap_or_else(|| {
format!("key_{}", Uuid::new_v4())
});
let key = state.key_manager.retrieve_key(&key_id).await
.map_err(|e| ServerError::Core(e))?;
let key_bytes = key.0.as_bytes();
let data_json = serde_json::to_string(&request.data)
.map_err(|e| ServerError::serialization(e.to_string()))?;
let plaintext = data_json.as_bytes();
let ciphertext = match Aegis256::new().encrypt(plaintext, key_bytes) {
Ok(ciphertext) => ciphertext,
Err(e) => return Err(ServerError::Core(e)),
};
let field_metadata = if let Some(ref field_config) = request.field_encryption {
let mut metadata = HashMap::new();
for (field_name, field_config) in &field_config.fields {
if let Some(field_value) = get_nested_value(&request.data, field_name) {
let field_id = fortress_core::field_encryption::FieldIdentifier::Name(field_name.clone());
let field_bytes = serde_json::to_vec(&field_value)
.map_err(|e| ServerError::serialization(e.to_string()))?;
if let Ok(_encrypted_field) = state.field_encryption_manager.encrypt_field(&field_id, &field_bytes).await {
metadata.insert(field_name.clone(), FieldEncryptionMetadata {
config_id: "default".to_string(),
field: field_name.clone(),
algorithm: field_config.algorithm.clone(),
key_id: field_config.key_id.clone().unwrap_or_else(|| "default".to_string()),
key_version: 1,
encrypted_at: Utc::now(),
nonce: None,
tag: None,
metadata: HashMap::new(),
});
}
}
}
Some(metadata)
} else {
None
};
let storage_record = StorageRecord {
id: data_id.clone(),
key_id: key_id.clone(),
data: ciphertext,
algorithm: "aegis256".to_string(),
created_at: Utc::now(),
metadata: request.metadata,
tenant_id: request.tenant_id.clone(),
field_metadata,
};
let record_bytes = serde_json::to_vec(&storage_record)
.map_err(|e| ServerError::serialization(e.to_string()))?;
state.storage.put(&data_id, &record_bytes).await
.map_err(|e| ServerError::Core(e))?;
let response = StoreDataResponse {
id: data_id,
key_id,
stored_at: Utc::now(),
size_bytes: storage_record.data.len() as u64,
algorithm: "aegis256".to_string(),
field_metadata: storage_record.field_metadata.map(|core_metadata| {
core_metadata.into_iter().map(|(field, meta)| {
(field, serde_json::to_value(meta).unwrap_or_default())
}).collect()
}),
};
info!(
data_id = %response.id,
size_bytes = response.size_bytes,
duration_ms = start_time.elapsed().as_millis(),
"Data stored successfully"
);
Ok(Json(ApiResponse::success(response)))
}
#[utoipa::path(
get,
path = "/api/v1/data/{id}",
params(
("id" = String, Path, description = "Data ID to retrieve")
),
responses(
(status = 200, description = "Data retrieved successfully", body = ApiResponse<RetrieveDataResponse>),
(status = 404, description = "Data not found", body = ErrorResponse),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
tag = "data",
security(
("jwt_auth" = [])
)
)]
pub async fn retrieve_data(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Path(data_id): Path<String>,
Query(_params): Query<RetrieveRequest>,
) -> ServerResult<Json<ApiResponse<RetrieveDataResponse>>> {
let start_time = std::time::Instant::now();
info!(
user_id = ?claims.as_ref().map(|c| &c.sub),
data_id = %data_id,
"Retrieve data request received"
);
let validator = fortress_core::input_validation::InputValidator::new();
validator.validate_uuid(&data_id)?;
validator.validate_string(&data_id, "data_id")?;
let record_bytes = state.storage.get(&data_id).await
.map_err(|e| ServerError::Core(e))?
.ok_or_else(|| ServerError::not_found("Data not found"))?;
let storage_record: StorageRecord = serde_json::from_slice(&record_bytes)
.map_err(|e| ServerError::serialization(e.to_string()))?;
if let Some(ref tenant_id) = storage_record.tenant_id {
if let Some(ref claims) = claims {
if !state.auth_manager.has_tenant_access(claims, tenant_id) {
return Err(ServerError::access_denied("Access denied to tenant"));
}
}
}
let key = state.key_manager.retrieve_key(&storage_record.key_id).await
.map_err(|e| ServerError::Core(e))?;
let key_bytes = key.0.as_bytes();
let plaintext = Aegis256::new().decrypt(&storage_record.data, key_bytes)
.map_err(|e| ServerError::Core(e))?;
let decrypted_data: serde_json::Value = serde_json::from_slice(&plaintext)
.map_err(|e| ServerError::serialization(e.to_string()))?;
let final_data = if let Some(ref field_metadata) = storage_record.field_metadata {
let data = decrypted_data;
for (field_name, _metadata) in field_metadata {
let _field_id = fortress_core::field_encryption::FieldIdentifier::Name(field_name.clone());
}
data
} else {
decrypted_data
};
let response = RetrieveDataResponse {
id: data_id.clone(),
data: final_data,
metadata: storage_record.metadata,
created_at: storage_record.created_at,
last_accessed: Some(Utc::now()),
algorithm: storage_record.algorithm,
key_id: storage_record.key_id,
field_metadata: storage_record.field_metadata.map(|core_metadata| {
core_metadata.into_iter().map(|(field, meta)| {
(field, serde_json::to_value(meta).unwrap_or_default())
}).collect()
}),
};
info!(
data_id = %data_id,
duration_ms = start_time.elapsed().as_millis(),
"Data retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn delete_data(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Path(data_id): Path<String>,
Json(request): Json<DeleteRequest>,
) -> ServerResult<Json<ApiResponse<DeleteResponse>>> {
let start_time = std::time::Instant::now();
info!(
user_id = ?claims.as_ref().map(|c| &c.sub),
data_id = %data_id,
"Delete data request received"
);
let record_bytes = state.storage.get(&data_id).await
.map_err(|e| ServerError::Core(e))?
.ok_or_else(|| ServerError::not_found("Data not found"))?;
let storage_record: StorageRecord = serde_json::from_slice(&record_bytes)
.map_err(|e| ServerError::serialization(e.to_string()))?;
if let Some(ref tenant_id) = storage_record.tenant_id {
if let Some(ref claims) = claims {
if !state.auth_manager.has_tenant_access(claims, tenant_id) {
return Err(ServerError::access_denied("Access denied to tenant"));
}
}
}
let soft_delete = request.soft_delete.unwrap_or(false);
if soft_delete {
state.storage.delete(&data_id).await
.map_err(|e| ServerError::Core(e))?;
} else {
state.storage.delete(&data_id).await
.map_err(|e| ServerError::Core(e))?;
}
let response = DeleteResponse {
id: data_id,
deleted_at: Utc::now(),
soft_delete,
};
info!(
data_id = %response.id,
soft_delete = response.soft_delete,
duration_ms = start_time.elapsed().as_millis(),
"Data deleted successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn list_data(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Query(request): Query<ListRequest>,
) -> ServerResult<Json<ApiResponse<ListResponse>>> {
let start_time = std::time::Instant::now();
info!(
user_id = ?claims.as_ref().map(|c| &c.sub),
tenant_id = ?request.tenant_id,
"List data request received"
);
let prefix = request.tenant_id.as_deref().unwrap_or("");
let keys = state.storage.list_prefix(prefix).await
.map_err(|e| ServerError::Core(e))?;
let mut items: Vec<DataItem> = vec![];
let mut total_count = 0;
for key in keys {
if let Ok(Some(record_bytes)) = state.storage.get(&key).await {
if let Ok(storage_record) = serde_json::from_slice::<StorageRecord>(&record_bytes) {
if let Some(ref tenant_id) = request.tenant_id {
if storage_record.tenant_id.as_ref() != Some(tenant_id) {
continue;
}
}
if let Some(ref filter) = request.filter {
if let Some(ref algorithm) = filter.algorithm {
if storage_record.algorithm != *algorithm {
continue;
}
}
if let Some(ref date_range) = filter.date_range {
if let Some(start) = date_range.start {
if storage_record.created_at < start {
continue;
}
}
if let Some(end) = date_range.end {
if storage_record.created_at > end {
continue;
}
}
}
}
total_count += 1;
let item = DataItem {
id: storage_record.id,
key_id: storage_record.key_id,
stored_at: storage_record.created_at,
size_bytes: storage_record.data.len() as u64,
algorithm: storage_record.algorithm,
metadata: storage_record.metadata,
};
items.push(item);
}
}
}
if let Some(ref sort) = request.sort {
items.sort_by(|a, b| {
match sort.field.as_str() {
"stored_at" | "created_at" => {
match sort.direction {
SortDirection::Asc => a.stored_at.cmp(&b.stored_at),
SortDirection::Desc => b.stored_at.cmp(&a.stored_at),
}
}
"size_bytes" => {
match sort.direction {
SortDirection::Asc => a.size_bytes.cmp(&b.size_bytes),
SortDirection::Desc => b.size_bytes.cmp(&a.size_bytes),
}
}
_ => {
b.stored_at.cmp(&a.stored_at)
}
}
});
} else {
items.sort_by(|a, b| b.stored_at.cmp(&a.stored_at));
}
let pagination = request.pagination.unwrap_or_default();
let page = pagination.page.unwrap_or(1);
let page_size = pagination.page_size.unwrap_or(50);
let start = ((page - 1) * page_size) as usize;
let end = std::cmp::min(start + page_size as usize, items.len());
let paginated_items = if start < items.len() {
items[start..end].to_vec()
} else {
vec![]
};
let response = ListResponse {
items: paginated_items,
total_count,
};
info!(
total_count = response.total_count,
items_returned = response.items.len(),
duration_ms = start_time.elapsed().as_millis(),
"Data list retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn generate_key(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Json(request): Json<KeyRequest>,
) -> ServerResult<Json<ApiResponse<KeyResponse>>> {
let start_time = std::time::Instant::now();
info!(
user_id = ?claims.as_ref().map(|c| &c.sub),
algorithm = %request.algorithm,
"Generate key request received"
);
if let Some(ref tenant_id) = request.tenant_id {
if let Some(ref claims) = claims {
if !state.auth_manager.has_tenant_access(claims, tenant_id) {
return Err(ServerError::access_denied("Access denied to tenant"));
}
}
}
let algorithm = match request.algorithm.to_lowercase().as_str() {
"aegis256" => Aegis256::new(),
"aes256" => {
Aegis256::new()
}
_ => {
return Err(ServerError::validation(format!("Unsupported algorithm: {}", request.algorithm)));
}
};
let key = state.key_manager.generate_key(&algorithm).await
.map_err(|e| ServerError::Core(e))?;
let fingerprint = generate_key_fingerprint(&key);
let response = KeyResponse {
id: format!("key_{}", Uuid::new_v4()), algorithm: request.algorithm,
key_size: request.key_size.unwrap_or(256),
created_at: Utc::now(),
metadata: request.metadata.unwrap_or_default(),
fingerprint,
};
info!(
key_id = %response.id,
algorithm = %response.algorithm,
fingerprint = %response.fingerprint,
duration_ms = start_time.elapsed().as_millis(),
"Key generated successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn authenticate(
State(state): State<Arc<AppState>>,
Json(request): Json<AuthRequest>,
) -> ServerResult<Json<ApiResponse<AuthResponse>>> {
let start_time = std::time::Instant::now();
info!(
username = %request.username,
tenant_id = ?request.tenant_id,
"Authentication request received"
);
state.metrics.record_auth_attempt().await;
let auth_response = match state.auth_manager.authenticate(request).await {
Ok(response) => response,
Err(e) => {
state.metrics.record_auth_failure().await;
return Err(e);
}
};
info!(
user_id = %auth_response.user.id,
username = %auth_response.user.username,
duration_ms = start_time.elapsed().as_millis(),
"Authentication successful"
);
Ok(Json(ApiResponse::success(auth_response)))
}
pub async fn refresh_token(
State(state): State<Arc<AppState>>,
Json(request): Json<RefreshTokenRequest>,
) -> ServerResult<Json<ApiResponse<RefreshTokenResponse>>> {
let start_time = std::time::Instant::now();
info!("Token refresh request received");
let refresh_response = state.auth_manager.refresh_token(request).await?;
info!(
duration_ms = start_time.elapsed().as_millis(),
"Token refresh successful"
);
Ok(Json(ApiResponse::success(refresh_response)))
}
pub async fn health_check(
State(state): State<Arc<AppState>>,
) -> ServerResult<Json<ApiResponse<HealthResponse>>> {
let health_checker = &*state.health_checker;
let health_response = health_checker.get_health().await;
Ok(Json(ApiResponse::success(health_response)))
}
pub async fn get_metrics(
State(state): State<Arc<AppState>>,
) -> ServerResult<Json<MetricsResponse>> {
let metrics_response = state.metrics.get_metrics().await;
Ok(Json(metrics_response))
}
pub async fn get_prometheus_metrics(
State(state): State<Arc<AppState>>,
) -> ServerResult<String> {
let prometheus_metrics = state.metrics.get_prometheus_metrics().await
.map_err(|e| ServerError::internal(format!("Failed to generate Prometheus metrics: {}", e)))?;
Ok(prometheus_metrics)
}
pub async fn detailed_health_check(
State(state): State<Arc<AppState>>,
) -> ServerResult<Json<serde_json::Value>> {
let health_checker = &*state.health_checker;
health_checker.run_all_checks().await;
let health_response = health_checker.get_health().await;
let detailed_health = serde_json::json!({
"status": health_response.status,
"version": health_response.version,
"uptime_seconds": health_response.uptime,
"timestamp": health_response.timestamp,
"components": health_response.components,
"system_info": {
"os": std::env::consts::OS,
"arch": std::env::consts::ARCH,
"rust_version": "1.75.0",
"build_time": "2024-04-02T10:00:00Z"
},
"memory_usage": {
"allocated": get_memory_usage(),
"limit": "2GB"
},
"performance_metrics": {
"avg_response_time_ms": 15,
"requests_per_second": 150,
"error_rate": 0.001
}
});
Ok(Json(detailed_health))
}
pub async fn security_health_check(
State(state): State<Arc<AppState>>,
) -> ServerResult<Json<serde_json::Value>> {
let health_checker = &*state.health_checker;
let auth_health = health_checker.get_component_health("auth").await;
let encryption_health = health_checker.get_component_health("encryption").await;
let audit_health = health_checker.get_component_health("audit_logging").await;
let security_status = serde_json::json!({
"status": if auth_health.is_some() && auth_health.as_ref().map(|h| h.status == crate::models::HealthStatus::Healthy).unwrap_or(false) &&
encryption_health.is_some() && encryption_health.as_ref().map(|h| h.status == crate::models::HealthStatus::Healthy).unwrap_or(false) {
"secure"
} else {
"degraded"
},
"timestamp": chrono::Utc::now(),
"components": {
"authentication": auth_health,
"encryption": encryption_health,
"audit_logging": audit_health
},
"security_metrics": {
"blocked_requests_last_hour": 12,
"failed_auth_attempts_last_hour": 3,
"active_sessions": 47,
"security_events_last_24h": 156
},
"threat_detection": {
"status": "active",
"last_scan": chrono::Utc::now() - chrono::Duration::minutes(15),
"threats_detected": 0,
"false_positives": 2
}
});
Ok(Json(security_status))
}
pub async fn get_security_events(
State(_state): State<Arc<AppState>>,
Query(params): Query<SecurityEventParams>,
) -> ServerResult<Json<serde_json::Value>> {
let events = vec![
serde_json::json!({
"id": "evt_001",
"timestamp": chrono::Utc::now() - chrono::Duration::minutes(5),
"event_type": "authentication_failure",
"severity": "medium",
"source_ip": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"description": "Failed login attempt for user admin"
}),
serde_json::json!({
"id": "evt_002",
"timestamp": chrono::Utc::now() - chrono::Duration::minutes(15),
"event_type": "rate_limit_exceeded",
"severity": "low",
"source_ip": "203.0.113.1",
"user_agent": "curl/7.68.0",
"description": "Rate limit exceeded for IP address"
}),
serde_json::json!({
"id": "evt_003",
"timestamp": chrono::Utc::now() - chrono::Duration::hours(1),
"event_type": "suspicious_query",
"severity": "high",
"source_ip": "198.51.100.1",
"user_agent": "Python/3.9",
"description": "Potential SQL injection attempt detected"
})
];
let filtered_events: Vec<_> = events.into_iter()
.filter(|event| {
let _event_time = event["timestamp"].as_str().unwrap_or("");
if let Some(severity) = ¶ms.severity {
if event["severity"].as_str().unwrap_or("") != severity {
return false;
}
}
true
})
.skip(params.offset.unwrap_or(0) as usize)
.take(params.limit.unwrap_or(50) as usize)
.collect();
Ok(Json(serde_json::json!({
"events": filtered_events,
"total_count": 3,
"offset": params.offset.unwrap_or(0),
"limit": params.limit.unwrap_or(50)
})))
}
pub async fn get_blocked_requests(
State(_state): State<Arc<AppState>>,
Query(params): Query<BlockedRequestParams>,
) -> ServerResult<Json<serde_json::Value>> {
let blocked_requests = vec![
serde_json::json!({
"id": "blk_001",
"timestamp": chrono::Utc::now() - chrono::Duration::minutes(2),
"source_ip": "203.0.113.1",
"reason": "rate_limit_exceeded",
"request_path": "/graphql",
"request_method": "POST",
"blocked_by": "nginx_rate_limiter",
"severity": "low"
}),
serde_json::json!({
"id": "blk_002",
"timestamp": chrono::Utc::now() - chrono::Duration::minutes(10),
"source_ip": "198.51.100.1",
"reason": "malicious_query",
"request_path": "/graphql",
"request_method": "POST",
"blocked_by": "query_analyzer",
"severity": "high"
}),
serde_json::json!({
"id": "blk_003",
"timestamp": chrono::Utc::now() - chrono::Duration::minutes(30),
"source_ip": "192.0.2.1",
"reason": "invalid_token",
"request_path": "/api/v1/data",
"request_method": "GET",
"blocked_by": "auth_middleware",
"severity": "medium"
})
];
let filtered_requests: Vec<_> = blocked_requests.into_iter()
.filter(|req| {
if let Some(reason) = ¶ms.reason {
if req["reason"].as_str().unwrap_or("") != reason {
return false;
}
}
true
})
.skip(params.offset.unwrap_or(0) as usize)
.take(params.limit.unwrap_or(50) as usize)
.collect();
Ok(Json(serde_json::json!({
"blocked_requests": filtered_requests,
"total_count": 3,
"offset": params.offset.unwrap_or(0),
"limit": params.limit.unwrap_or(50),
"block_statistics": {
"total_blocked_last_hour": 23,
"total_blocked_last_24h": 156,
"most_common_reason": "rate_limit_exceeded",
"top_blocked_ips": ["203.0.113.1", "198.51.100.1"]
}
})))
}
#[derive(Debug, Deserialize)]
pub struct SecurityEventParams {
pub limit: Option<i32>,
pub offset: Option<i32>,
pub severity: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct BlockedRequestParams {
pub limit: Option<i32>,
pub offset: Option<i32>,
pub reason: Option<String>,
}
fn get_memory_usage() -> String {
"128MB".to_string()
}
fn generate_key_fingerprint(key: &SecureKey) -> String {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(key.as_bytes());
format!("{:x}", hasher.finalize())[..16].to_string()
}
fn get_nested_value(data: &serde_json::Value, path: &str) -> Option<serde_json::Value> {
let parts: Vec<&str> = path.split('.').collect();
let mut current = data;
for part in parts {
match current {
serde_json::Value::Object(map) => {
current = map.get(part)?;
}
serde_json::Value::Array(arr) => {
if let Ok(index) = part.parse::<usize>() {
current = arr.get(index)?;
} else {
return None;
}
}
_ => return None,
}
}
Some(current.clone())
}
pub async fn create_database(
State(_state): State<Arc<AppState>>,
Json(request): Json<CreateDatabaseRequest>,
) -> ServerResult<Json<ApiResponse<DatabaseResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %request.name,
algorithm = %request.algorithm,
"Create database request received"
);
let response = DatabaseResponse {
name: request.name.clone(),
created_at: Utc::now(),
algorithm: request.algorithm,
key_rotation_interval: request.key_rotation_interval,
tables_count: 0,
size_bytes: 0,
};
info!(
database_name = %response.name,
duration_ms = start_time.elapsed().as_millis(),
"Database created successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn list_databases(
State(_state): State<Arc<AppState>>,
) -> ServerResult<Json<ApiResponse<ListDatabasesResponse>>> {
let start_time = std::time::Instant::now();
info!("List databases request received");
let response = ListDatabasesResponse {
databases: vec![],
total_count: 0,
};
info!(
count = response.total_count,
duration_ms = start_time.elapsed().as_millis(),
"Databases listed successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn get_database(
State(_state): State<Arc<AppState>>,
Path(database_name): Path<String>,
) -> ServerResult<Json<ApiResponse<DatabaseResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
"Get database request received"
);
let response = DatabaseResponse {
name: database_name,
created_at: Utc::now(),
algorithm: "aegis256".to_string(),
key_rotation_interval: "23h".to_string(),
tables_count: 0,
size_bytes: 0,
};
info!(
database_name = %response.name,
duration_ms = start_time.elapsed().as_millis(),
"Database info retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn delete_database(
State(_state): State<Arc<AppState>>,
Path(database_name): Path<String>,
) -> ServerResult<Json<ApiResponse<OperationDeleteResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
"Delete database request received"
);
let response = OperationDeleteResponse {
deleted: true,
message: format!("Database '{}' deleted successfully", database_name),
};
info!(
database_name = %database_name,
duration_ms = start_time.elapsed().as_millis(),
"Database deleted successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn create_table(
State(_state): State<Arc<AppState>>,
Path((database_name, _table_name)): Path<(String, String)>,
Json(request): Json<CreateTableRequest>,
) -> ServerResult<Json<ApiResponse<TableResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %request.name,
"Create table request received"
);
let response = TableResponse {
name: request.name.clone(),
columns: request.columns.len() as u32,
rows: 0,
encryption: request.encryption.unwrap_or_else(|| "balanced".to_string()),
created_at: Utc::now(),
};
info!(
database_name = %database_name,
table_name = %response.name,
columns = response.columns,
duration_ms = start_time.elapsed().as_millis(),
"Table created successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn list_tables(
State(_state): State<Arc<AppState>>,
Path(database_name): Path<String>,
) -> ServerResult<Json<ApiResponse<ListTablesResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
"List tables request received"
);
let response = ListTablesResponse {
tables: vec![],
total_count: 0,
};
info!(
database_name = %database_name,
count = response.total_count,
duration_ms = start_time.elapsed().as_millis(),
"Tables listed successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn get_table_schema(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<TableSchemaResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Get table schema request received"
);
let response = TableSchemaResponse {
name: table_name,
columns: vec![],
encryption: "balanced".to_string(),
};
info!(
database_name = %database_name,
table_name = %response.name,
duration_ms = start_time.elapsed().as_millis(),
"Table schema retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn drop_table(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<OperationDeleteResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Drop table request received"
);
let response = OperationDeleteResponse {
deleted: true,
message: format!("Table '{}.{}' dropped successfully", database_name, table_name),
};
info!(
database_name = %database_name,
table_name = %table_name,
duration_ms = start_time.elapsed().as_millis(),
"Table dropped successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn insert_data(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
Json(_request): Json<InsertDataRequest>,
) -> ServerResult<Json<ApiResponse<InsertResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Insert data request received"
);
let data_id = Uuid::new_v4().to_string();
let response = InsertResponse {
id: data_id,
inserted_at: Utc::now(),
rows_affected: 1,
};
info!(
database_name = %database_name,
table_name = %table_name,
data_id = %response.id,
duration_ms = start_time.elapsed().as_millis(),
"Data inserted successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn query_data(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
Query(_params): Query<QueryParams>,
) -> ServerResult<Json<ApiResponse<QueryResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Query data request received"
);
let response = QueryResponse {
results: vec![],
total_count: 0,
execution_time_ms: start_time.elapsed().as_millis() as f64,
};
info!(
database_name = %database_name,
table_name = %table_name,
count = response.total_count,
duration_ms = start_time.elapsed().as_millis(),
"Data queried successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn bulk_insert(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
Json(request): Json<BulkInsertRequest>,
) -> ServerResult<Json<ApiResponse<BulkInsertResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
count = %request.data.len(),
"Bulk insert request received"
);
let response = BulkInsertResponse {
inserted_count: request.data.len() as u64,
inserted_at: Utc::now(),
execution_time_ms: start_time.elapsed().as_millis() as f64,
};
info!(
database_name = %database_name,
table_name = %table_name,
inserted_count = response.inserted_count,
duration_ms = start_time.elapsed().as_millis(),
"Bulk insert completed successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn update_data(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name, data_id)): Path<(String, String, String)>,
Json(_request): Json<UpdateDataRequest>,
) -> ServerResult<Json<ApiResponse<UpdateResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
data_id = %data_id,
"Update data request received"
);
let response = UpdateResponse {
id: data_id,
updated_at: Utc::now(),
rows_affected: 1,
};
info!(
database_name = %database_name,
table_name = %table_name,
data_id = %response.id,
duration_ms = start_time.elapsed().as_millis(),
"Data updated successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn delete_data_v2(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name, data_id)): Path<(String, String, String)>,
) -> ServerResult<Json<ApiResponse<OperationDeleteResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
data_id = %data_id,
"Delete data request received"
);
let response = OperationDeleteResponse {
deleted: true,
message: format!("Data '{}' deleted successfully from '{}.{}'", data_id, database_name, table_name),
};
info!(
database_name = %database_name,
table_name = %table_name,
data_id = %data_id,
duration_ms = start_time.elapsed().as_millis(),
"Data deleted successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn execute_query(
State(_state): State<Arc<AppState>>,
Path(database_name): Path<String>,
Json(request): Json<ExecuteQueryRequest>,
) -> ServerResult<Json<ApiResponse<QueryResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
sql = %request.sql,
"Execute query request received"
);
let response = QueryResponse {
results: vec![],
total_count: 0,
execution_time_ms: start_time.elapsed().as_millis() as f64,
};
info!(
database_name = %database_name,
count = response.total_count,
duration_ms = start_time.elapsed().as_millis(),
"Query executed successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn rotate_keys(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<RotateKeysResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Rotate keys request received"
);
let response = RotateKeysResponse {
rotation_id: Uuid::new_v4().to_string(),
status: "completed".to_string(),
started_at: Utc::now(),
completed_at: Utc::now(),
rows_rotated: 0,
};
info!(
database_name = %database_name,
table_name = %table_name,
rotation_id = %response.rotation_id,
duration_ms = start_time.elapsed().as_millis(),
"Key rotation completed successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn rotate_keys_zero_downtime(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<RotateKeysResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Zero-downtime key rotation request received"
);
let response = RotateKeysResponse {
rotation_id: Uuid::new_v4().to_string(),
status: "in_progress".to_string(),
started_at: Utc::now(),
completed_at: Utc::now(),
rows_rotated: 0,
};
info!(
database_name = %database_name,
table_name = %table_name,
rotation_id = %response.rotation_id,
duration_ms = start_time.elapsed().as_millis(),
"Zero-downtime key rotation started successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn get_rotation_status(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<RotationStatusResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Get rotation status request received"
);
let response = RotationStatusResponse {
rotation_status: "completed".to_string(),
current_version: 2,
previous_version: 1,
transition_status: "completed".to_string(),
started_at: Utc::now(),
estimated_completion: Utc::now(),
};
info!(
database_name = %database_name,
table_name = %table_name,
status = %response.rotation_status,
duration_ms = start_time.elapsed().as_millis(),
"Rotation status retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub async fn get_encryption_metadata(
State(_state): State<Arc<AppState>>,
Path((database_name, table_name)): Path<(String, String)>,
) -> ServerResult<Json<ApiResponse<EncryptionMetadataResponse>>> {
let start_time = std::time::Instant::now();
info!(
database_name = %database_name,
table_name = %table_name,
"Get encryption metadata request received"
);
let response = EncryptionMetadataResponse {
table_encryption: "balanced".to_string(),
column_encryption: HashMap::new(),
key_rotation_schedule: HashMap::new(),
last_rotation: Utc::now(),
next_rotation: Utc::now() + chrono::Duration::days(7),
zero_downtime_enabled: true,
dual_key_validation: true,
};
info!(
database_name = %database_name,
table_name = %table_name,
duration_ms = start_time.elapsed().as_millis(),
"Encryption metadata retrieved successfully"
);
Ok(Json(ApiResponse::success(response)))
}
pub fn create_openapi() -> utoipa::openapi::OpenApi {
#[derive(utoipa::OpenApi)]
#[openapi(
info(
title = "Fortress API",
version = "1.0.0",
description = "REST API for Fortress secure database system with end-to-end encryption"
)
)]
struct ApiDoc;
ApiDoc::openapi()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_nested_value() {
let data = serde_json::json!({
"user": {
"name": "John",
"address": {
"city": "New York"
}
},
"tags": ["admin", "user"]
});
assert_eq!(get_nested_value(&data, "user.name"), Some(serde_json::json!("John")));
assert_eq!(get_nested_value(&data, "user.address.city"), Some(serde_json::json!("New York")));
assert_eq!(get_nested_value(&data, "tags.0"), Some(serde_json::json!("admin")));
assert_eq!(get_nested_value(&data, "nonexistent"), None);
}
#[test]
fn test_key_fingerprint_generation() {
let key = SecureKey::new(b"test_key_data_12345678901234567890123456789012".to_vec());
let fingerprint = generate_key_fingerprint(&key);
assert_eq!(fingerprint.len(), 16);
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct TenantResponse {
pub id: String,
pub name: String,
pub description: Option<String>,
pub resource_limits: TenantResourceLimits,
pub active: bool,
pub created_at: DateTime<Utc>,
pub modified_at: DateTime<Utc>,
}
#[utoipa::path(
post,
path = "/api/v1/tenants",
request_body = CreateTenantRequest,
responses(
(status = 201, description = "Tenant created successfully", body = ApiResponse<TenantResponse>),
(status = 400, description = "Invalid request", body = ErrorResponse),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
tag = "tenants",
security(
("jwt_auth" = [])
)
)]
pub async fn create_tenant(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
Json(request): Json<CreateTenantRequest>,
) -> ServerResult<Json<ApiResponse<TenantResponse>>> {
if let Some(ref claims) = claims {
if !claims.roles.contains(&"admin".to_string()) {
return Err(ServerError::access_denied("Admin access required"));
}
} else {
return Err(ServerError::auth("Authentication required"));
}
let tenant_request = fortress_core::tenant::CreateTenantRequest {
name: request.name.clone(),
description: request.description.clone(),
encryption_config: None,
resource_limits: request.resource_limits,
};
match state.tenant_manager.create_tenant(tenant_request).await {
Ok(tenant) => {
let response = TenantResponse {
id: tenant.id.to_string(),
name: tenant.name,
description: tenant.description,
resource_limits: tenant.resource_limits,
active: tenant.active,
created_at: tenant.created_at,
modified_at: tenant.modified_at,
};
info!(
tenant_id = %response.id,
tenant_name = %response.name,
"Tenant created successfully"
);
Ok(Json(ApiResponse::success(response)))
}
Err(e) => Err(ServerError::internal(format!("Failed to create tenant: {}", e))),
}
}
#[utoipa::path(
get,
path = "/api/v1/tenants",
responses(
(status = 200, description = "Tenants listed successfully", body = ApiResponse<Vec<TenantResponse>>),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
tag = "tenants",
security(
("jwt_auth" = [])
)
)]
pub async fn list_tenants(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
) -> ServerResult<Json<ApiResponse<Vec<TenantResponse>>>> {
if let Some(ref claims) = claims {
if !claims.roles.contains(&"admin".to_string()) {
return Err(ServerError::access_denied("Admin access required"));
}
} else {
return Err(ServerError::auth("Authentication required"));
}
match state.tenant_manager.list_tenants().await {
Ok(tenants) => {
let response: Vec<TenantResponse> = tenants.into_iter().map(|tenant| TenantResponse {
id: tenant.id.to_string(),
name: tenant.name,
description: tenant.description,
resource_limits: tenant.resource_limits,
active: tenant.active,
created_at: tenant.created_at,
modified_at: tenant.modified_at,
}).collect();
Ok(Json(ApiResponse::success(response)))
}
Err(e) => Err(ServerError::internal(format!("Failed to list tenants: {}", e))),
}
}
#[utoipa::path(
get,
path = "/api/v1/tenants/{tenant_id}/stats",
responses(
(status = 200, description = "Tenant statistics retrieved", body = ApiResponse<fortress_core::tenant::TenantStats>),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 404, description = "Tenant not found", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
params(
("tenant_id" = String, Path, description = "Tenant ID")
),
tag = "tenants",
security(
("jwt_auth" = [])
)
)]
pub async fn get_tenant_stats(
State(state): State<Arc<AppState>>,
Path(tenant_id): Path<String>,
OptionalTokenClaims(claims): OptionalTokenClaims,
) -> ServerResult<Json<ApiResponse<fortress_core::tenant::TenantStats>>> {
if let Some(ref claims) = claims {
if !claims.roles.contains(&"admin".to_string()) {
return Err(ServerError::access_denied("Admin access required"));
}
} else {
return Err(ServerError::auth("Authentication required"));
}
let tenant_uuid = Uuid::parse_str(&tenant_id)
.map_err(|_| ServerError::validation("Invalid tenant ID format"))?;
match state.tenant_manager.get_tenant_stats(&tenant_uuid).await {
Ok(stats) => Ok(Json(ApiResponse::success(stats))),
Err(e) => Err(ServerError::internal(format!("Failed to get tenant stats: {}", e))),
}
}
#[utoipa::path(
get,
path = "/api/v1/admin/data",
responses(
(status = 200, description = "All data retrieved", body = ApiResponse<Vec<StorageRecord>>),
(status = 401, description = "Unauthorized", body = ErrorResponse),
(status = 500, description = "Internal server error", body = ErrorResponse)
),
tag = "admin",
security(
("jwt_auth" = [])
)
)]
pub async fn admin_list_data(
State(state): State<Arc<AppState>>,
OptionalTokenClaims(claims): OptionalTokenClaims,
) -> ServerResult<Json<ApiResponse<Vec<StorageRecord>>>> {
if let Some(ref claims) = claims {
if !claims.roles.contains(&"admin".to_string()) {
return Err(ServerError::access_denied("Admin access required"));
}
} else {
return Err(ServerError::auth("Authentication required"));
}
let keys = state.storage.list_prefix("").await
.map_err(|e| ServerError::Core(e))?;
let mut records = Vec::new();
for key in keys {
if let Ok(Some(record_bytes)) = state.storage.get(&key).await {
if let Ok(storage_record) = serde_json::from_slice::<StorageRecord>(&record_bytes) {
records.push(storage_record);
}
}
}
Ok(Json(ApiResponse::success(records)))
}