1use std::sync::Arc;
6
7use axum::{
8 Json, Router,
9 extract::{Path, State},
10 http::StatusCode,
11 response::IntoResponse,
12 routing::{delete, get, post},
13};
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct RoleDto {
19 pub id: String,
21 pub name: String,
23 pub description: Option<String>,
25 pub permissions: Vec<String>,
27 pub tenant_id: Option<String>,
29 pub created_at: String,
31 pub updated_at: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct PermissionDto {
38 pub id: String,
40 pub resource: String,
42 pub action: String,
43 pub description: Option<String>,
45 pub created_at: String,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct UserRoleDto {
52 pub user_id: String,
54 pub role_id: String,
56 pub tenant_id: Option<String>,
58 pub assigned_at: String,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct CreateRoleRequest {
65 pub name: String,
67 pub description: Option<String>,
69 pub permissions: Vec<String>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CreatePermissionRequest {
76 pub resource: String,
78 pub action: String,
80 pub description: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct AssignRoleRequest {
87 pub user_id: String,
89 pub role_id: String,
91}
92
93#[derive(Clone)]
95pub struct RbacManagementState {
96 pub db: Arc<db_backend::RbacDbBackend>,
98}
99
100pub fn rbac_management_router(state: RbacManagementState) -> Router {
117 Router::new()
118 .route("/api/roles", post(create_role).get(list_roles))
120 .route("/api/roles/:role_id", get(get_role).put(update_role).delete(delete_role))
121 .route("/api/permissions", post(create_permission).get(list_permissions))
123 .route("/api/permissions/:permission_id", get(get_permission).delete(delete_permission))
124 .route("/api/user-roles", post(assign_role).get(list_user_roles))
126 .route("/api/user-roles/:user_id/:role_id", delete(revoke_role))
127 .route("/api/audit/permissions", get(query_permission_audit))
129 .with_state(Arc::new(state))
130}
131
132async fn create_role(
139 State(state): State<Arc<RbacManagementState>>,
140 Json(payload): Json<CreateRoleRequest>,
141) -> impl IntoResponse {
142 match state
145 .db
146 .create_role(
147 &payload.name,
148 payload.description.as_deref(),
149 payload.permissions,
150 None, )
152 .await
153 {
154 Ok(role) => (StatusCode::CREATED, Json(serde_json::to_value(role).unwrap_or_default()))
155 .into_response(),
156 Err(_) => (StatusCode::CONFLICT, Json(serde_json::json!({"error": "role_duplicate"})))
157 .into_response(),
158 }
159}
160
161async fn list_roles(State(state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
164 match state.db.list_roles(None, 100, 0).await {
167 Ok(roles) => Json(roles),
168 Err(_) => Json(Vec::<RoleDto>::new()),
169 }
170}
171
172async fn get_role(
175 State(state): State<Arc<RbacManagementState>>,
176 Path(role_id): Path<String>,
177) -> impl IntoResponse {
178 match state.db.get_role(&role_id).await {
180 Ok(role) => {
181 (StatusCode::OK, Json(serde_json::to_value(role).unwrap_or_default())).into_response()
182 },
183 Err(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "role_not_found"})))
184 .into_response(),
185 }
186}
187
188async fn update_role(
191 State(_state): State<Arc<RbacManagementState>>,
192 Path(_role_id): Path<String>,
193 Json(_payload): Json<CreateRoleRequest>,
194) -> impl IntoResponse {
195 Json(serde_json::json!({"updated": true}))
198}
199
200async fn delete_role(
203 State(state): State<Arc<RbacManagementState>>,
204 Path(role_id): Path<String>,
205) -> impl IntoResponse {
206 match state.db.delete_role(&role_id).await {
208 Ok(_) => StatusCode::NO_CONTENT,
209 Err(_) => StatusCode::CONFLICT,
210 }
211}
212
213async fn create_permission(
220 State(state): State<Arc<RbacManagementState>>,
221 Json(payload): Json<CreatePermissionRequest>,
222) -> impl IntoResponse {
223 match state
225 .db
226 .create_permission(&payload.resource, &payload.action, payload.description.as_deref())
227 .await
228 {
229 Ok(perm) => (StatusCode::CREATED, Json(serde_json::to_value(perm).unwrap_or_default()))
230 .into_response(),
231 Err(_) => {
232 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "permission_duplicate"})))
233 .into_response()
234 },
235 }
236}
237
238async fn list_permissions(State(_state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
241 Json(Vec::<PermissionDto>::new())
244}
245
246async fn get_permission(
249 State(_state): State<Arc<RbacManagementState>>,
250 Path(_permission_id): Path<String>,
251) -> impl IntoResponse {
252 (
254 StatusCode::NOT_FOUND,
255 Json(serde_json::json!({"error": "permission_not_found"})),
256 )
257}
258
259async fn delete_permission(
262 State(_state): State<Arc<RbacManagementState>>,
263 Path(_permission_id): Path<String>,
264) -> impl IntoResponse {
265 StatusCode::NO_CONTENT
267}
268
269async fn assign_role(
276 State(state): State<Arc<RbacManagementState>>,
277 Json(payload): Json<AssignRoleRequest>,
278) -> impl IntoResponse {
279 match state.db.assign_role_to_user(&payload.user_id, &payload.role_id, None).await {
281 Ok(assignment) => {
282 (StatusCode::CREATED, Json(serde_json::to_value(assignment).unwrap_or_default()))
283 .into_response()
284 },
285 Err(_) => {
286 (StatusCode::CONFLICT, Json(serde_json::json!({"error": "assignment_duplicate"})))
287 .into_response()
288 },
289 }
290}
291
292async fn list_user_roles(State(_state): State<Arc<RbacManagementState>>) -> impl IntoResponse {
295 Json(Vec::<UserRoleDto>::new())
298}
299
300async fn revoke_role(
303 State(state): State<Arc<RbacManagementState>>,
304 Path((user_id, role_id)): Path<(String, String)>,
305) -> impl IntoResponse {
306 match state.db.revoke_role_from_user(&user_id, &role_id).await {
308 Ok(_) => StatusCode::NO_CONTENT,
309 Err(_) => StatusCode::NOT_FOUND,
310 }
311}
312
313async fn query_permission_audit(
320 State(_state): State<Arc<RbacManagementState>>,
321) -> impl IntoResponse {
322 Json(Vec::<serde_json::Value>::new())
324}
325
326pub mod db_backend;
328
329#[cfg(test)]
330mod tests;
331
332#[cfg(test)]
333mod db_backend_tests;
334
335#[cfg(test)]
336mod integration_tests;
337
338#[cfg(test)]
339mod schema_tests;