use crate::AppState;
use axum::{
extract::State,
http::{header, HeaderName, StatusCode},
response::{IntoResponse, Response},
};
use chrono::Utc;
use tracing::info;
pub async fn get_ohttp_keys(State(state): State<AppState>) -> Result<Response, StatusCode> {
state.metrics.key_requests_total.inc();
match state.key_manager.get_encoded_config().await {
Ok(config_bytes) => {
info!("Serving {} bytes of key configurations", config_bytes.len());
let max_age = calculate_cache_max_age(&state);
Ok((
StatusCode::OK,
[
(header::CONTENT_TYPE, "application/ohttp-keys"),
(header::CACHE_CONTROL, &format!("public, max-age={max_age}")),
(HeaderName::from_static("x-content-type-options"), "nosniff"),
],
config_bytes,
)
.into_response())
}
Err(e) => {
tracing::error!("Failed to encode key config: {e}");
Err(StatusCode::INTERNAL_SERVER_ERROR)
}
}
}
pub async fn get_legacy_ohttp_configs(
State(state): State<AppState>,
) -> Result<Response, StatusCode> {
get_ohttp_keys(State(state)).await
}
fn calculate_cache_max_age(state: &AppState) -> u64 {
let ten_percent = state.config.key_rotation_interval.as_secs() / 10;
let one_hour = 3600;
let twenty_four_hours = 86400;
ten_percent.max(one_hour).min(twenty_four_hours)
}
pub async fn key_health_check(State(state): State<AppState>) -> impl IntoResponse {
let stats = state.key_manager.get_stats().await;
let health_status = if stats.active_keys > 0 && stats.expired_keys == 0 {
"healthy"
} else if stats.active_keys > 0 {
"degraded"
} else {
"unhealthy"
};
axum::Json(serde_json::json!({
"status": health_status,
"timestamp": Utc::now().to_rfc3339(),
"key_stats": {
"active_key_id": stats.active_key_id,
"total_keys": stats.total_keys,
"active_keys": stats.active_keys,
"expired_keys": stats.expired_keys,
"rotation_enabled": stats.auto_rotation_enabled,
"rotation_interval_hours": stats.rotation_interval.as_secs() / 3600,
}
}))
}