botserver/src/core/shared/admin_handlers.rs.bak
Rodrigo Rodriguez e143968179 feat: Add JWT secret rotation and health verification
SEC-02: Implement credential rotation security improvements

- Add JWT secret rotation to rotate-secret command
- Generate 64-character HS512-compatible secrets
- Automatic .env backup with timestamp
- Atomic file updates via temp+rename pattern
- Add health verification for rotated credentials
- Route rotate-secret, rotate-secrets, vault commands in CLI
- Add verification attempts for database and JWT endpoints

Security improvements:
- JWT_SECRET now rotatable (previously impossible)
- Automatic rollback via backup files
- Health checks catch configuration errors
- Clear warnings about token invalidation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-19 19:42:41 +00:00

270 lines
7.5 KiB
Rust

use super::admin_types::*;
use crate::core::shared::state::AppState;
use crate::core::urls::ApiUrls;
use axum::{
extract::{Path, State},
http::StatusCode,
response::{IntoResponse, Json},
routing::{get, post},
};
use diesel::prelude::*;
use diesel::sql_types::{Text, Nullable};
use log::{error, info};
use std::sync::Arc;
use uuid::Uuid;
/// Get admin dashboard data
pub async fn get_admin_dashboard(
State(state): State<Arc<AppState>>,
Path(bot_id): Path<Uuid>,
) -> impl IntoResponse {
let bot_id = bot_id.into_inner();
// Get system status
let (database_ok, redis_ok) = match get_system_status(&state).await {
Ok(status) => (true, status.is_healthy()),
Err(e) => {
error!("Failed to get system status: {}", e);
(false, false)
}
};
// Get user count
let user_count = get_stats_users(&state).await.unwrap_or(0);
let group_count = get_stats_groups(&state).await.unwrap_or(0);
let bot_count = get_stats_bots(&state).await.unwrap_or(0);
// Get storage stats
let storage_stats = get_stats_storage(&state).await.unwrap_or_else(|| StorageStat {
total_gb: 0,
used_gb: 0,
percent: 0.0,
});
// Get recent activities
let activities = get_dashboard_activity(&state, Some(20))
.await
.unwrap_or_default();
// Get member/bot/invitation stats
let member_count = get_dashboard_members(&state, bot_id, 50)
.await
.unwrap_or(0);
let bot_list = get_dashboard_bots(&state, bot_id, 50)
.await
.unwrap_or_default();
let invitation_count = get_dashboard_invitations(&state, bot_id, 50)
.await
.unwrap_or(0);
let dashboard_data = AdminDashboardData {
users: vec![
UserStat {
id: Uuid::new_v4(),
name: "Users".to_string(),
count: user_count as i64,
},
GroupStat {
id: Uuid::new_v4(),
name: "Groups".to_string(),
count: group_count as i64,
},
BotStat {
id: Uuid::new_v4(),
name: "Bots".to_string(),
count: bot_count as i64,
},
],
groups,
bots: bot_list,
storage: storage_stats,
activities,
invitations: vec![
UserStat {
id: Uuid::new_v4(),
name: "Members".to_string(),
count: member_count as i64,
},
UserStat {
id: Uuid::new_v4(),
name: "Invitations".to_string(),
count: invitation_count as i64,
},
],
};
(StatusCode::OK, Json(dashboard_data)).into_response()
}
/// Get system health status
pub async fn get_system_status(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
let (database_ok, redis_ok) = match get_system_status(&state).await {
Ok(status) => (true, status.is_healthy()),
Err(e) => {
error!("Failed to get system status: {}", e);
(false, false)
}
};
let response = SystemHealth {
database: database_ok,
redis: redis_ok,
services: vec![],
};
(StatusCode::OK, Json(response)).into_response()
}
/// Get system metrics
pub async fn get_system_metrics(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
// Get CPU usage
let cpu_usage = sys_info::get_system_cpu_usage();
let cpu_usage_percent = if cpu_usage > 0.0 {
(cpu_usage / sys_info::get_system_cpu_count() as f64) * 100.0
} else {
0.0
};
// Get memory usage
let mem_total = sys_info::get_total_memory_mb();
let mem_used = sys_info::get_used_memory_mb();
let mem_percent = if mem_total > 0 {
((mem_total - mem_used) as f64 / mem_total as f64) * 100.0
} else {
0.0
};
// Get disk usage
let disk_total = sys_info::get_total_disk_space_gb();
let disk_used = sys_info::get_used_disk_space_gb();
let disk_percent = if disk_total > 0.0 {
((disk_total - disk_used) as f64 / disk_total as f64) * 100.0
} else {
0.0
};
let services = vec![
ServiceStatus {
name: "database".to_string(),
status: if database_ok { "running" } else { "stopped" }.to_string(),
uptime_seconds: 0,
},
ServiceStatus {
name: "redis".to_string(),
status: if redis_ok { "running" } else { "stopped" }.to_string(),
uptime_seconds: 0,
},
];
let metrics = SystemMetricsResponse {
cpu_usage,
memory_total_mb: mem_total,
memory_used_mb: mem_used,
memory_percent: mem_percent,
disk_total_gb: disk_total,
disk_used_gb: disk_used,
disk_percent: disk_percent,
network_in_mbps: 0.0,
network_out_mbps: 0.0,
active_connections: 0,
request_rate_per_minute: 0,
error_rate_percent: 0.0,
};
(StatusCode::OK, Json(metrics)).into_response()
}
/// Get user statistics
pub async fn get_stats_users(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
use crate::core::shared::models::schema::users;
let count = users::table
.count()
.get_result(&state.conn)
.map_err(|e| format!("Failed to get user count: {}", e))?;
let response = vec![
UserStat {
id: Uuid::new_v4(),
name: "Total Users".to_string(),
count: count as i64,
},
];
(StatusCode::OK, Json(response)).into_response()
}
/// Get group statistics
pub async fn get_stats_groups(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
use crate::core::shared::models::schema::bot_groups;
let count = bot_groups::table
.count()
.get_result(&state.conn)
.map_err(|e| format!("Failed to get group count: {}", e))?;
let response = vec![
UserStat {
id: Uuid::new_v4(),
name: "Total Groups".to_string(),
count: count as i64,
},
];
(StatusCode::OK, Json(response)).into_response()
}
/// Get bot statistics
pub async fn get_stats_bots(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
use crate::core::shared::models::schema::bots;
let count = bots::table
.count()
.get_result(&state.conn)
.map_err(|e| format!("Failed to get bot count: {}", e))?;
let response = vec![
UserStat {
id: Uuid::new_v4(),
name: "Total Bots".to_string(),
count: count as i64,
},
];
(StatusCode::OK, Json(response)).into_response()
}
/// Get storage statistics
pub async fn get_stats_storage(
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
use crate::core::shared::models::schema::storage_usage;
let usage = storage_usage::table
.limit(100)
.order_by(crate::core::shared::models::schema::storage_usage::timestamp.desc())
.load(&state.conn)
.map_err(|e| format!("Failed to get storage stats: {}", e))?;
let total_gb = usage.iter().map(|u| u.total_gb.unwrap_or(0.0)).sum::<f64>();
let used_gb = usage.iter().map(|u| u.used_gb.unwrap_or(0.0)).sum::<f64>();
let percent = if total_gb > 0.0 { (used_gb / total_gb * 100.0) } else { 0.0 };
let response = StorageStat {
total_gb: total_gb.round(),
used_gb: used_gb.round(),
percent: (percent * 100.0).round(),
};
(StatusCode::OK, Json(response)).into_response()
}