use std::sync::Arc;
use axum::{
Json, Router,
extract::{Path, State},
http::StatusCode,
response::IntoResponse,
routing::{delete, get, post},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleDto {
pub id: String,
pub name: String,
pub description: Option<String>,
pub permissions: Vec<String>,
pub tenant_id: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionDto {
pub id: String,
pub resource: String,
pub action: String,
pub description: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserRoleDto {
pub user_id: String,
pub role_id: String,
pub tenant_id: Option<String>,
pub assigned_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateRoleRequest {
pub name: String,
pub description: Option<String>,
pub permissions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatePermissionRequest {
pub resource: String,
pub action: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssignRoleRequest {
pub user_id: String,
pub role_id: String,
}
#[derive(Clone)]
pub struct RbacManagementState {
pub db: Arc<db_backend::RbacDbBackend>,
}
pub fn rbac_management_router(state: RbacManagementState) -> Router {
Router::new()
.route("/api/roles", post(create_role).get(list_roles))
.route("/api/roles/:role_id", get(get_role).put(update_role).delete(delete_role))
.route("/api/permissions", post(create_permission).get(list_permissions))
.route("/api/permissions/:permission_id", get(get_permission).delete(delete_permission))
.route("/api/user-roles", post(assign_role).get(list_user_roles))
.route("/api/user-roles/:user_id/:role_id", delete(revoke_role))
.route("/api/audit/permissions", get(query_permission_audit))
.with_state(Arc::new(state))
}
async fn create_role(
State(state): State<Arc<RbacManagementState>>,
Json(payload): Json<CreateRoleRequest>,
) -> impl IntoResponse {
match state
.db
.create_role(
&payload.name,
payload.description.as_deref(),
payload.permissions,
None, )
.await
{
Ok(role) => (StatusCode::CREATED, Json(serde_json::to_value(role).unwrap_or_default()))
.into_response(),
Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "role_duplicate"})))
.into_response(),
}
}
async fn list_roles(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
match state.db.list_roles(None, 100, 0).await {
Ok(roles) => Json(roles),
Err(_) => Json(Vec::<RoleDto>::new()),
}
}
async fn get_role(
State(state): State<Arc<RbacManagementState>>,
Path(role_id): Path<String>,
) -> impl IntoResponse {
match state.db.get_role(&role_id).await {
Ok(role) => {
(StatusCode::OK, Json(serde_json::to_value(role).unwrap_or_default())).into_response()
},
Err(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
.into_response(),
}
}
async fn update_role(
State(_state): State<Arc<RbacManagementState>>,
Path(_role_id): Path<String>,
Json(_payload): Json<CreateRoleRequest>,
) -> impl IntoResponse {
Json(serde_json::json!({"updated": true}))
}
async fn delete_role(
State(state): State<Arc<RbacManagementState>>,
Path(role_id): Path<String>,
) -> impl IntoResponse {
match state.db.delete_role(&role_id).await {
Ok(_) => StatusCode::NO_CONTENT,
Err(_) => StatusCode::CONFLICT,
}
}
async fn create_permission(
State(state): State<Arc<RbacManagementState>>,
Json(payload): Json<CreatePermissionRequest>,
) -> impl IntoResponse {
match state
.db
.create_permission(&payload.resource, &payload.action, payload.description.as_deref())
.await
{
Ok(perm) => (StatusCode::CREATED, Json(serde_json::to_value(perm).unwrap_or_default()))
.into_response(),
Err(_) => {
(StatusCode::CONFLICT, Json(serde_json::json!({"error": "permission_duplicate"})))
.into_response()
},
}
}
async fn list_permissions(State(_state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
Json(Vec::<PermissionDto>::new())
}
async fn get_permission(
State(_state): State<Arc<RbacManagementState>>,
Path(_permission_id): Path<String>,
) -> impl IntoResponse {
(
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": "permission_not_found"})),
)
}
async fn delete_permission(
State(_state): State<Arc<RbacManagementState>>,
Path(_permission_id): Path<String>,
) -> impl IntoResponse {
StatusCode::NO_CONTENT
}
async fn assign_role(
State(state): State<Arc<RbacManagementState>>,
Json(payload): Json<AssignRoleRequest>,
) -> impl IntoResponse {
match state.db.assign_role_to_user(&payload.user_id, &payload.role_id, None).await {
Ok(assignment) => {
(StatusCode::CREATED, Json(serde_json::to_value(assignment).unwrap_or_default()))
.into_response()
},
Err(_) => {
(StatusCode::CONFLICT, Json(serde_json::json!({"error": "assignment_duplicate"})))
.into_response()
},
}
}
async fn list_user_roles(State(_state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
Json(Vec::<UserRoleDto>::new())
}
async fn revoke_role(
State(state): State<Arc<RbacManagementState>>,
Path((user_id, role_id)): Path<(String, String)>,
) -> impl IntoResponse {
match state.db.revoke_role_from_user(&user_id, &role_id).await {
Ok(_) => StatusCode::NO_CONTENT,
Err(_) => StatusCode::NOT_FOUND,
}
}
async fn query_permission_audit(
State(_state): State<Arc<RbacManagementState>>,
) -> impl IntoResponse {
Json(Vec::<serde_json::Value>::new())
}
pub mod db_backend;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod db_backend_tests;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
mod schema_tests;