1use 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#[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#[derive(Debug, Serialize)]
29pub struct UserListResponse {
30 pub users: Vec<UserListItem>,
31 pub pagination: Pagination,
32}
33
34#[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#[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#[derive(Debug, Deserialize)]
78pub struct UpdateUserRolesRequest {
79 pub roles: Vec<String>,
80}
81
82#[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
94pub 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 if !auth_token.roles.contains(&"admin".to_string()) {
107 return ApiResponse::<UserListResponse>::forbidden_typed();
108 }
109
110 let total_users = 150u64; let _offset = (query.page - 1) * query.limit;
113
114 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 }; 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 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
177pub async fn create_user(
180 State(state): State<ApiState>,
181 headers: HeaderMap,
182 Json(req): Json<CreateUserRequest>,
183) -> ApiResponse<UserListItem> {
184 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 let security_config = &state.auth_framework.config().security;
193
194 let password_policy = crate::utils::validation::PasswordPolicy {
196 min_length: security_config.min_password_length,
197 max_length: 128, 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 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 if !auth_token.roles.contains(&"admin".to_string()) {
223 return ApiResponse::forbidden_typed();
224 }
225
226 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 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
264pub 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 if !auth_token.roles.contains(&"admin".to_string()) {
278 return ApiResponse::forbidden();
279 }
280
281 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
297pub 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 if !auth_token.roles.contains(&"admin".to_string()) {
310 return ApiResponse::forbidden();
311 }
312
313 if auth_token.user_id == user_id {
315 return ApiResponse::validation_error("Cannot delete your own account");
316 }
317
318 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#[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 if !auth_token.roles.contains(&"admin".to_string()) {
353 return ApiResponse::forbidden();
354 }
355
356 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
376pub 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 if !auth_token.roles.contains(&"admin".to_string()) {
388 return ApiResponse::forbidden_typed();
389 }
390
391 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#[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 if !auth_token.roles.contains(&"admin".to_string()) {
458 return ApiResponse::forbidden_typed();
459 }
460
461 let total_logs = 1500u64; let _offset = (query.page - 1) * query.limit;
464
465 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 }; 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}