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            "UNAUTHORIZED",
172            "Authentication required",
173        ),
174    }
175}
176
177/// POST /admin/users
178/// Create new user (admin only)
179pub async fn create_user(
180    State(state): State<ApiState>,
181    headers: HeaderMap,
182    Json(req): Json<CreateUserRequest>,
183) -> ApiResponse<UserListItem> {
184    // Validate input
185    if req.username.is_empty() || req.password.is_empty() || req.email.is_empty() {
186        return ApiResponse::<UserListItem>::validation_error_typed(
187            "Username, password, and email are required",
188        );
189    }
190
191    // Enhanced password validation using SecurityConfig
192    let security_config = &state.auth_framework.config().security;
193
194    // Create password policy from security config
195    let password_policy = crate::utils::validation::PasswordPolicy {
196        min_length: security_config.min_password_length,
197        max_length: 128, // Set reasonable maximum
198        require_lowercase: security_config.require_lowercase,
199        require_uppercase: security_config.require_uppercase,
200        require_digit: security_config.require_digit,
201        require_special: security_config.require_special,
202        banned_passwords: std::collections::HashSet::new(),
203        min_entropy: 3.0,
204    };
205
206    if let Err(e) =
207        crate::utils::validation::validate_password_enhanced(&req.password, &password_policy)
208    {
209        return ApiResponse::<UserListItem>::validation_error_typed(e.to_string());
210    }
211
212    // Enhanced email validation
213    if let Err(e) = crate::utils::validation::validate_email(&req.email) {
214        return ApiResponse::<UserListItem>::validation_error_typed(e.to_string());
215    }
216
217    match extract_bearer_token(&headers) {
218        Some(token) => {
219            match validate_api_token(&state.auth_framework, &token).await {
220                Ok(auth_token) => {
221                    // Check admin permissions
222                    if !auth_token.roles.contains(&"admin".to_string()) {
223                        return ApiResponse::forbidden_typed();
224                    }
225
226                    // In a real implementation:
227                    // 1. Check if username/email already exists
228                    // 2. Hash password
229                    // 3. Create user in storage
230                    // 4. Send welcome email
231
232                    let new_user = UserListItem {
233                        id: format!("user_{}", chrono::Utc::now().timestamp()),
234                        username: req.username,
235                        email: req.email,
236                        roles: req.roles,
237                        active: req.active,
238                        created_at: chrono::Utc::now().to_rfc3339(),
239                        last_login: None,
240                    };
241
242                    tracing::info!("New user created: {}", new_user.id);
243                    ApiResponse::success(new_user)
244                }
245                Err(e) => {
246                    // Convert AuthError to typed response
247                    let error_response = ApiResponse::<()>::from(e);
248                    ApiResponse::<UserListItem> {
249                        success: error_response.success,
250                        data: None,
251                        error: error_response.error,
252                        message: error_response.message,
253                    }
254                }
255            }
256        }
257        None => ApiResponse::<UserListItem>::unauthorized_typed(
258            "UNAUTHORIZED",
259            "Authentication required",
260        ),
261    }
262}
263
264/// PUT /admin/users/{user_id}/roles
265/// Update user roles (admin only)
266pub async fn update_user_roles(
267    State(state): State<ApiState>,
268    headers: HeaderMap,
269    Path(user_id): Path<String>,
270    Json(req): Json<UpdateUserRolesRequest>,
271) -> ApiResponse<()> {
272    match extract_bearer_token(&headers) {
273        Some(token) => {
274            match validate_api_token(&state.auth_framework, &token).await {
275                Ok(auth_token) => {
276                    // Check admin permissions
277                    if !auth_token.roles.contains(&"admin".to_string()) {
278                        return ApiResponse::forbidden();
279                    }
280
281                    // In a real implementation:
282                    // 1. Validate user exists
283                    // 2. Validate roles are valid
284                    // 3. Update user roles in storage
285                    // 4. Invalidate user's existing sessions if needed
286
287                    tracing::info!("Updated roles for user {}: {:?}", user_id, req.roles);
288                    ApiResponse::<()>::ok_with_message("User roles updated successfully")
289                }
290                Err(e) => e.into(),
291            }
292        }
293        None => ApiResponse::unauthorized(),
294    }
295}
296
297/// DELETE /admin/users/{user_id}
298/// Delete user (admin only)
299pub async fn delete_user(
300    State(state): State<ApiState>,
301    headers: HeaderMap,
302    Path(user_id): Path<String>,
303) -> ApiResponse<()> {
304    match extract_bearer_token(&headers) {
305        Some(token) => {
306            match validate_api_token(&state.auth_framework, &token).await {
307                Ok(auth_token) => {
308                    // Check admin permissions
309                    if !auth_token.roles.contains(&"admin".to_string()) {
310                        return ApiResponse::forbidden();
311                    }
312
313                    // Prevent self-deletion
314                    if auth_token.user_id == user_id {
315                        return ApiResponse::validation_error("Cannot delete your own account");
316                    }
317
318                    // In a real implementation:
319                    // 1. Validate user exists
320                    // 2. Soft delete or hard delete based on policy
321                    // 3. Invalidate all user sessions
322                    // 4. Archive user data if required
323
324                    tracing::info!("User deleted: {}", user_id);
325                    ApiResponse::<()>::ok_with_message("User deleted successfully")
326                }
327                Err(e) => e.into(),
328            }
329        }
330        None => ApiResponse::unauthorized(),
331    }
332}
333
334/// PUT /admin/users/{user_id}/activate
335/// Activate/deactivate user (admin only)
336#[derive(Debug, Deserialize)]
337pub struct ActivateUserRequest {
338    pub active: bool,
339}
340
341pub async fn activate_user(
342    State(state): State<ApiState>,
343    headers: HeaderMap,
344    Path(user_id): Path<String>,
345    Json(req): Json<ActivateUserRequest>,
346) -> ApiResponse<()> {
347    match extract_bearer_token(&headers) {
348        Some(token) => {
349            match validate_api_token(&state.auth_framework, &token).await {
350                Ok(auth_token) => {
351                    // Check admin permissions
352                    if !auth_token.roles.contains(&"admin".to_string()) {
353                        return ApiResponse::forbidden();
354                    }
355
356                    // In a real implementation:
357                    // 1. Validate user exists
358                    // 2. Update user active status
359                    // 3. If deactivating, invalidate all user sessions
360
361                    let action = if req.active {
362                        "activated"
363                    } else {
364                        "deactivated"
365                    };
366                    tracing::info!("User {} {}", user_id, action);
367                    ApiResponse::<()>::ok_with_message(format!("User {} successfully", action))
368                }
369                Err(e) => e.into(),
370            }
371        }
372        None => ApiResponse::unauthorized(),
373    }
374}
375
376/// GET /admin/stats
377/// Get system statistics (admin only)
378pub async fn get_system_stats(
379    State(state): State<ApiState>,
380    headers: HeaderMap,
381) -> ApiResponse<SystemStats> {
382    match extract_bearer_token(&headers) {
383        Some(token) => {
384            match validate_api_token(&state.auth_framework, &token).await {
385                Ok(auth_token) => {
386                    // Check admin permissions
387                    if !auth_token.roles.contains(&"admin".to_string()) {
388                        return ApiResponse::forbidden_typed();
389                    }
390
391                    // In a real implementation, collect actual system statistics
392                    let stats = SystemStats {
393                        total_users: 1250,
394                        active_sessions: 45,
395                        total_tokens: 892,
396                        failed_logins_24h: 12,
397                        system_uptime: "15 days, 4 hours".to_string(),
398                        memory_usage: "256 MB / 1 GB".to_string(),
399                        cpu_usage: "12%".to_string(),
400                    };
401
402                    ApiResponse::success(stats)
403                }
404                Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
405            }
406        }
407        None => ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
408    }
409}
410
411/// GET /admin/audit-logs
412/// Get audit logs (admin only)
413#[derive(Debug, Serialize)]
414pub struct AuditLogEntry {
415    pub id: String,
416    pub timestamp: String,
417    pub user_id: String,
418    pub action: String,
419    pub resource: String,
420    pub ip_address: String,
421    pub user_agent: String,
422    pub result: String,
423}
424
425#[derive(Debug, Serialize)]
426pub struct AuditLogResponse {
427    pub logs: Vec<AuditLogEntry>,
428    pub pagination: Pagination,
429}
430
431#[derive(Debug, Deserialize)]
432pub struct AuditLogQuery {
433    #[serde(default = "default_page")]
434    pub page: u32,
435    #[serde(default = "default_limit")]
436    pub limit: u32,
437    #[serde(default)]
438    pub user_id: Option<String>,
439    #[serde(default)]
440    pub action: Option<String>,
441    #[serde(default)]
442    pub start_date: Option<String>,
443    #[serde(default)]
444    pub end_date: Option<String>,
445}
446
447pub async fn get_audit_logs(
448    State(state): State<ApiState>,
449    headers: HeaderMap,
450    Query(query): Query<AuditLogQuery>,
451) -> ApiResponse<AuditLogResponse> {
452    match extract_bearer_token(&headers) {
453        Some(token) => {
454            match validate_api_token(&state.auth_framework, &token).await {
455                Ok(auth_token) => {
456                    // Check admin permissions
457                    if !auth_token.roles.contains(&"admin".to_string()) {
458                        return ApiResponse::forbidden_typed();
459                    }
460
461                    // In a real implementation, fetch audit logs from storage with pagination
462                    let total_logs = 1500u64; // This would come from a count query in real implementation
463                    let _offset = (query.page - 1) * query.limit;
464
465                    // Validate pagination parameters
466                    let page = if query.page == 0 { 1 } else { query.page };
467                    let limit = if query.limit == 0 {
468                        20
469                    } else {
470                        query.limit.min(100)
471                    }; // Cap at 100 items per page
472
473                    // Calculate total pages
474                    let total_pages = ((total_logs as f64) / (limit as f64)).ceil() as u32;
475                    let total_pages = if total_pages == 0 { 1 } else { total_pages };
476
477                    let logs = vec![
478                        AuditLogEntry {
479                            id: "audit_1".to_string(),
480                            timestamp: "2024-08-17T10:30:00Z".to_string(),
481                            user_id: "user_123".to_string(),
482                            action: "login".to_string(),
483                            resource: "/auth/login".to_string(),
484                            ip_address: "192.168.1.100".to_string(),
485                            user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)".to_string(),
486                            result: "success".to_string(),
487                        },
488                        AuditLogEntry {
489                            id: "audit_2".to_string(),
490                            timestamp: "2024-08-17T10:25:00Z".to_string(),
491                            user_id: "user_456".to_string(),
492                            action: "password_change".to_string(),
493                            resource: "/users/change-password".to_string(),
494                            ip_address: "192.168.1.101".to_string(),
495                            user_agent: "Mozilla/5.0 (macOS; Intel Mac OS X 10_15_7)".to_string(),
496                            result: "success".to_string(),
497                        },
498                    ];
499
500                    let pagination = Pagination {
501                        page,
502                        limit,
503                        total: total_logs,
504                        pages: total_pages,
505                    };
506
507                    let response = AuditLogResponse { logs, pagination };
508
509                    ApiResponse::success(response)
510                }
511                Err(_e) => ApiResponse::error_typed("AUTH_ERROR", "Token validation failed"),
512            }
513        }
514        None => ApiResponse::unauthorized_typed("UNAUTHORIZED", "Authentication required"),
515    }
516}