auth_framework/api/
admin.rs

1//! Administrative API Endpoints
2//!
3//! Handles user management, system configuration, and admin operations
4
5use crate::api::{
6    ApiResponse, ApiState, extract_bearer_token, responses::Pagination, validate_api_token,
7};
8use axum::{
9    Json,
10    extract::{Path, Query, State},
11    http::HeaderMap,
12};
13use serde::{Deserialize, Serialize};
14
15/// User list item
16#[derive(Debug, Serialize)]
17pub struct UserListItem {
18    pub id: String,
19    pub username: String,
20    pub email: String,
21    pub roles: Vec<String>,
22    pub active: bool,
23    pub created_at: String,
24    pub last_login: Option<String>,
25}
26
27/// User list response
28#[derive(Debug, Serialize)]
29pub struct UserListResponse {
30    pub users: Vec<UserListItem>,
31    pub pagination: Pagination,
32}
33
34/// User list query parameters
35#[derive(Debug, Deserialize)]
36pub struct UserListQuery {
37    #[serde(default = "default_page")]
38    pub page: u32,
39    #[serde(default = "default_limit")]
40    pub limit: u32,
41    #[serde(default)]
42    pub search: Option<String>,
43    #[serde(default)]
44    pub role: Option<String>,
45    #[serde(default)]
46    pub active: Option<bool>,
47}
48
49fn default_page() -> u32 {
50    1
51}
52fn default_limit() -> u32 {
53    20
54}
55
56/// Create user request
57#[derive(Debug, Deserialize)]
58pub struct CreateUserRequest {
59    pub username: String,
60    pub password: String,
61    pub email: String,
62    #[serde(default)]
63    pub first_name: Option<String>,
64    #[serde(default)]
65    pub last_name: Option<String>,
66    #[serde(default)]
67    pub roles: Vec<String>,
68    #[serde(default = "default_active")]
69    pub active: bool,
70}
71
72fn default_active() -> bool {
73    true
74}
75
76/// Update user roles request
77#[derive(Debug, Deserialize)]
78pub struct UpdateUserRolesRequest {
79    pub roles: Vec<String>,
80}
81
82/// System stats response
83#[derive(Debug, Serialize)]
84pub struct SystemStats {
85    pub total_users: u64,
86    pub active_sessions: u64,
87    pub total_tokens: u64,
88    pub failed_logins_24h: u64,
89    pub system_uptime: String,
90    pub memory_usage: String,
91    pub cpu_usage: String,
92}
93
94/// GET /admin/users
95/// List all users (admin only)
96pub async fn list_users(
97    State(state): State<ApiState>,
98    headers: HeaderMap,
99    Query(query): Query<UserListQuery>,
100) -> ApiResponse<UserListResponse> {
101    match extract_bearer_token(&headers) {
102        Some(token) => {
103            match validate_api_token(&state.auth_framework, &token).await {
104                Ok(auth_token) => {
105                    // Check admin permissions
106                    if !auth_token.roles.contains(&"admin".to_string()) {
107                        return ApiResponse::<UserListResponse>::forbidden_typed();
108                    }
109
110                    // In a real implementation, fetch users from storage with pagination
111                    let total_users = 150u64; // This would come from a count query in real implementation
112                    let _offset = (query.page - 1) * query.limit;
113
114                    // Validate pagination parameters
115                    let page = if query.page == 0 { 1 } else { query.page };
116                    let limit = if query.limit == 0 {
117                        20
118                    } else {
119                        query.limit.min(100)
120                    }; // Cap at 100 items per page
121
122                    // Calculate total pages
123                    let total_pages = ((total_users as f64) / (limit as f64)).ceil() as u32;
124                    let total_pages = if total_pages == 0 { 1 } else { total_pages };
125
126                    let users = vec![
127                        UserListItem {
128                            id: "user_1".to_string(),
129                            username: "admin@example.com".to_string(),
130                            email: "admin@example.com".to_string(),
131                            roles: vec!["admin".to_string()],
132                            active: true,
133                            created_at: "2024-01-01T00:00:00Z".to_string(),
134                            last_login: Some("2024-08-17T10:30:00Z".to_string()),
135                        },
136                        UserListItem {
137                            id: "user_2".to_string(),
138                            username: "user@example.com".to_string(),
139                            email: "user@example.com".to_string(),
140                            roles: vec!["user".to_string()],
141                            active: true,
142                            created_at: "2024-01-02T00:00:00Z".to_string(),
143                            last_login: Some("2024-08-16T15:45:00Z".to_string()),
144                        },
145                    ];
146
147                    let pagination = Pagination {
148                        page,
149                        limit,
150                        total: total_users,
151                        pages: total_pages,
152                    };
153
154                    let response = UserListResponse { users, pagination };
155
156                    ApiResponse::success(response)
157                }
158                Err(e) => {
159                    // Convert AuthError to typed response
160                    let error_response = ApiResponse::<()>::from(e);
161                    ApiResponse::<UserListResponse> {
162                        success: error_response.success,
163                        data: None,
164                        error: error_response.error,
165                        message: error_response.message,
166                    }
167                }
168            }
169        }
170        None => ApiResponse::<UserListResponse>::unauthorized_typed(),
171    }
172}
173
174/// POST /admin/users
175/// Create new user (admin only)
176pub async fn create_user(
177    State(state): State<ApiState>,
178    headers: HeaderMap,
179    Json(req): Json<CreateUserRequest>,
180) -> ApiResponse<UserListItem> {
181    // Validate input
182    if req.username.is_empty() || req.password.is_empty() || req.email.is_empty() {
183        return ApiResponse::<UserListItem>::validation_error_typed(
184            "Username, password, and email are required",
185        );
186    }
187
188    if req.password.len() < 8 {
189        return ApiResponse::<UserListItem>::validation_error_typed(
190            "Password must be at least 8 characters long",
191        );
192    }
193
194    match extract_bearer_token(&headers) {
195        Some(token) => {
196            match validate_api_token(&state.auth_framework, &token).await {
197                Ok(auth_token) => {
198                    // Check admin permissions
199                    if !auth_token.roles.contains(&"admin".to_string()) {
200                        return ApiResponse::forbidden_typed();
201                    }
202
203                    // In a real implementation:
204                    // 1. Check if username/email already exists
205                    // 2. Hash password
206                    // 3. Create user in storage
207                    // 4. Send welcome email
208
209                    let new_user = UserListItem {
210                        id: format!("user_{}", chrono::Utc::now().timestamp()),
211                        username: req.username,
212                        email: req.email,
213                        roles: req.roles,
214                        active: req.active,
215                        created_at: chrono::Utc::now().to_rfc3339(),
216                        last_login: None,
217                    };
218
219                    tracing::info!("New user created: {}", new_user.id);
220                    ApiResponse::success(new_user)
221                }
222                Err(e) => {
223                    // Convert AuthError to typed response
224                    let error_response = ApiResponse::<()>::from(e);
225                    ApiResponse::<UserListItem> {
226                        success: error_response.success,
227                        data: None,
228                        error: error_response.error,
229                        message: error_response.message,
230                    }
231                }
232            }
233        }
234        None => ApiResponse::<UserListItem>::unauthorized_typed(),
235    }
236}
237
238/// PUT /admin/users/{user_id}/roles
239/// Update user roles (admin only)
240pub async fn update_user_roles(
241    State(state): State<ApiState>,
242    headers: HeaderMap,
243    Path(user_id): Path<String>,
244    Json(req): Json<UpdateUserRolesRequest>,
245) -> ApiResponse<()> {
246    match extract_bearer_token(&headers) {
247        Some(token) => {
248            match validate_api_token(&state.auth_framework, &token).await {
249                Ok(auth_token) => {
250                    // Check admin permissions
251                    if !auth_token.roles.contains(&"admin".to_string()) {
252                        return ApiResponse::forbidden();
253                    }
254
255                    // In a real implementation:
256                    // 1. Validate user exists
257                    // 2. Validate roles are valid
258                    // 3. Update user roles in storage
259                    // 4. Invalidate user's existing sessions if needed
260
261                    tracing::info!("Updated roles for user {}: {:?}", user_id, req.roles);
262                    ApiResponse::<()>::ok_with_message("User roles updated successfully")
263                }
264                Err(e) => e.into(),
265            }
266        }
267        None => ApiResponse::unauthorized(),
268    }
269}
270
271/// DELETE /admin/users/{user_id}
272/// Delete user (admin only)
273pub async fn delete_user(
274    State(state): State<ApiState>,
275    headers: HeaderMap,
276    Path(user_id): Path<String>,
277) -> ApiResponse<()> {
278    match extract_bearer_token(&headers) {
279        Some(token) => {
280            match validate_api_token(&state.auth_framework, &token).await {
281                Ok(auth_token) => {
282                    // Check admin permissions
283                    if !auth_token.roles.contains(&"admin".to_string()) {
284                        return ApiResponse::forbidden();
285                    }
286
287                    // Prevent self-deletion
288                    if auth_token.user_id == user_id {
289                        return ApiResponse::validation_error("Cannot delete your own account");
290                    }
291
292                    // In a real implementation:
293                    // 1. Validate user exists
294                    // 2. Soft delete or hard delete based on policy
295                    // 3. Invalidate all user sessions
296                    // 4. Archive user data if required
297
298                    tracing::info!("User deleted: {}", user_id);
299                    ApiResponse::<()>::ok_with_message("User deleted successfully")
300                }
301                Err(e) => e.into(),
302            }
303        }
304        None => ApiResponse::unauthorized(),
305    }
306}
307
308/// PUT /admin/users/{user_id}/activate
309/// Activate/deactivate user (admin only)
310#[derive(Debug, Deserialize)]
311pub struct ActivateUserRequest {
312    pub active: bool,
313}
314
315pub async fn activate_user(
316    State(state): State<ApiState>,
317    headers: HeaderMap,
318    Path(user_id): Path<String>,
319    Json(req): Json<ActivateUserRequest>,
320) -> ApiResponse<()> {
321    match extract_bearer_token(&headers) {
322        Some(token) => {
323            match validate_api_token(&state.auth_framework, &token).await {
324                Ok(auth_token) => {
325                    // Check admin permissions
326                    if !auth_token.roles.contains(&"admin".to_string()) {
327                        return ApiResponse::forbidden();
328                    }
329
330                    // In a real implementation:
331                    // 1. Validate user exists
332                    // 2. Update user active status
333                    // 3. If deactivating, invalidate all user sessions
334
335                    let action = if req.active {
336                        "activated"
337                    } else {
338                        "deactivated"
339                    };
340                    tracing::info!("User {} {}", user_id, action);
341                    ApiResponse::<()>::ok_with_message(format!("User {} successfully", action))
342                }
343                Err(e) => e.into(),
344            }
345        }
346        None => ApiResponse::unauthorized(),
347    }
348}
349
350/// GET /admin/stats
351/// Get system statistics (admin only)
352pub async fn get_system_stats(
353    State(state): State<ApiState>,
354    headers: HeaderMap,
355) -> ApiResponse<SystemStats> {
356    match extract_bearer_token(&headers) {
357        Some(token) => {
358            match validate_api_token(&state.auth_framework, &token).await {
359                Ok(auth_token) => {
360                    // Check admin permissions
361                    if !auth_token.roles.contains(&"admin".to_string()) {
362                        return ApiResponse::forbidden_typed();
363                    }
364
365                    // In a real implementation, collect actual system statistics
366                    let stats = SystemStats {
367                        total_users: 1250,
368                        active_sessions: 45,
369                        total_tokens: 892,
370                        failed_logins_24h: 12,
371                        system_uptime: "15 days, 4 hours".to_string(),
372                        memory_usage: "256 MB / 1 GB".to_string(),
373                        cpu_usage: "12%".to_string(),
374                    };
375
376                    ApiResponse::success(stats)
377                }
378                Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
379            }
380        }
381        None => ApiResponse::unauthorized_typed(),
382    }
383}
384
385/// GET /admin/audit-logs
386/// Get audit logs (admin only)
387#[derive(Debug, Serialize)]
388pub struct AuditLogEntry {
389    pub id: String,
390    pub timestamp: String,
391    pub user_id: String,
392    pub action: String,
393    pub resource: String,
394    pub ip_address: String,
395    pub user_agent: String,
396    pub result: String,
397}
398
399#[derive(Debug, Serialize)]
400pub struct AuditLogResponse {
401    pub logs: Vec<AuditLogEntry>,
402    pub pagination: Pagination,
403}
404
405#[derive(Debug, Deserialize)]
406pub struct AuditLogQuery {
407    #[serde(default = "default_page")]
408    pub page: u32,
409    #[serde(default = "default_limit")]
410    pub limit: u32,
411    #[serde(default)]
412    pub user_id: Option<String>,
413    #[serde(default)]
414    pub action: Option<String>,
415    #[serde(default)]
416    pub start_date: Option<String>,
417    #[serde(default)]
418    pub end_date: Option<String>,
419}
420
421pub async fn get_audit_logs(
422    State(state): State<ApiState>,
423    headers: HeaderMap,
424    Query(query): Query<AuditLogQuery>,
425) -> ApiResponse<AuditLogResponse> {
426    match extract_bearer_token(&headers) {
427        Some(token) => {
428            match validate_api_token(&state.auth_framework, &token).await {
429                Ok(auth_token) => {
430                    // Check admin permissions
431                    if !auth_token.roles.contains(&"admin".to_string()) {
432                        return ApiResponse::forbidden_typed();
433                    }
434
435                    // In a real implementation, fetch audit logs from storage with pagination
436                    let total_logs = 1500u64; // This would come from a count query in real implementation
437                    let _offset = (query.page - 1) * query.limit;
438
439                    // Validate pagination parameters
440                    let page = if query.page == 0 { 1 } else { query.page };
441                    let limit = if query.limit == 0 {
442                        20
443                    } else {
444                        query.limit.min(100)
445                    }; // Cap at 100 items per page
446
447                    // Calculate total pages
448                    let total_pages = ((total_logs as f64) / (limit as f64)).ceil() as u32;
449                    let total_pages = if total_pages == 0 { 1 } else { total_pages };
450
451                    let logs = vec![
452                        AuditLogEntry {
453                            id: "audit_1".to_string(),
454                            timestamp: "2024-08-17T10:30:00Z".to_string(),
455                            user_id: "user_123".to_string(),
456                            action: "login".to_string(),
457                            resource: "/auth/login".to_string(),
458                            ip_address: "192.168.1.100".to_string(),
459                            user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)".to_string(),
460                            result: "success".to_string(),
461                        },
462                        AuditLogEntry {
463                            id: "audit_2".to_string(),
464                            timestamp: "2024-08-17T10:25:00Z".to_string(),
465                            user_id: "user_456".to_string(),
466                            action: "password_change".to_string(),
467                            resource: "/users/change-password".to_string(),
468                            ip_address: "192.168.1.101".to_string(),
469                            user_agent: "Mozilla/5.0 (macOS; Intel Mac OS X 10_15_7)".to_string(),
470                            result: "success".to_string(),
471                        },
472                    ];
473
474                    let pagination = Pagination {
475                        page,
476                        limit,
477                        total: total_logs,
478                        pages: total_pages,
479                    };
480
481                    let response = AuditLogResponse { logs, pagination };
482
483                    ApiResponse::success(response)
484                }
485                Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
486            }
487        }
488        None => ApiResponse::unauthorized_typed(),
489    }
490}
491
492