#[cfg(feature = "cedar")]
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
#[cfg(feature = "cedar")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "cedar")]
use crate::{
auth::{user::User, Authenticated},
middleware::cedar::CedarAuthz,
};
#[cfg(feature = "cedar")]
#[derive(Debug, Serialize, Deserialize)]
pub struct ReloadPolicyResponse {
pub success: bool,
pub message: String,
pub timestamp: String,
}
#[cfg(feature = "cedar")]
#[derive(Debug, Serialize, Deserialize)]
pub struct PolicyStatusResponse {
pub enabled: bool,
pub policy_path: String,
pub hot_reload: bool,
pub failure_mode: String,
pub cache_enabled: bool,
}
#[cfg(feature = "cedar")]
pub async fn reload_policies(
State(cedar): State<CedarAuthz>,
Authenticated(user): Authenticated<User>,
) -> Result<Response, StatusCode> {
if !user.roles.contains(&"admin".to_string()) {
tracing::warn!(
user_id = user.id,
email = %user.email,
"Non-admin user attempted to reload Cedar policies"
);
return Err(StatusCode::FORBIDDEN);
}
match cedar.reload_policies().await {
Ok(()) => {
let response = ReloadPolicyResponse {
success: true,
message: "Cedar policies reloaded successfully".to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
};
tracing::info!(
user_id = user.id,
email = %user.email,
"Cedar policies reloaded by admin"
);
Ok((StatusCode::OK, Json(response)).into_response())
}
Err(e) => {
tracing::error!(
error = ?e,
user_id = user.id,
"Failed to reload Cedar policies"
);
let response = ReloadPolicyResponse {
success: false,
message: format!("Failed to reload policies: {e}"),
timestamp: chrono::Utc::now().to_rfc3339(),
};
Ok((StatusCode::INTERNAL_SERVER_ERROR, Json(response)).into_response())
}
}
}
#[cfg(feature = "cedar")]
pub async fn policy_status(
State(cedar): State<CedarAuthz>,
Authenticated(user): Authenticated<User>,
) -> Result<Response, StatusCode> {
if !user.roles.contains(&"admin".to_string()) {
tracing::warn!(
user_id = user.id,
email = %user.email,
"Non-admin user attempted to view Cedar policy status"
);
return Err(StatusCode::FORBIDDEN);
}
let config = cedar.config();
let response = PolicyStatusResponse {
enabled: config.enabled,
policy_path: config.policy_path.display().to_string(),
hot_reload: config.hot_reload,
failure_mode: format!("{:?}", config.failure_mode).to_lowercase(),
cache_enabled: config.cache_enabled,
};
tracing::debug!(
user_id = user.id,
email = %user.email,
"Cedar policy status requested by admin"
);
Ok((StatusCode::OK, Json(response)).into_response())
}
#[cfg(test)]
#[cfg(feature = "cedar")]
mod tests {
use super::*;
#[test]
fn test_reload_response_serialization() {
let response = ReloadPolicyResponse {
success: true,
message: "Policies reloaded".to_string(),
timestamp: "2025-11-22T10:30:00Z".to_string(),
};
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"success\":true"));
assert!(json.contains("Policies reloaded"));
}
#[test]
fn test_status_response_serialization() {
let response = PolicyStatusResponse {
enabled: true,
policy_path: "policies/app.cedar".to_string(),
hot_reload: false,
failure_mode: "closed".to_string(),
cache_enabled: true,
};
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"enabled\":true"));
assert!(json.contains("policies/app.cedar"));
}
}