1use crate::api::{ApiResponse, ApiState, extract_bearer_token, validate_api_token};
6use axum::{
7 Json,
8 extract::{Path, State},
9 http::HeaderMap,
10};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Serialize)]
15pub struct UserProfile {
16 pub id: String,
18 pub username: String,
19 pub email: String,
20 pub first_name: Option<String>,
21 pub last_name: Option<String>,
22 pub roles: crate::types::Roles,
24 pub permissions: crate::types::Permissions,
26 pub mfa_enabled: bool,
28 pub email_verified: bool,
30 pub created_at: String,
32 pub updated_at: String,
34}
35
36#[derive(Debug, Deserialize)]
38pub struct UpdateProfileRequest {
39 #[serde(default)]
40 pub first_name: Option<String>,
41 #[serde(default)]
42 pub last_name: Option<String>,
43 #[serde(default)]
45 pub email: Option<String>,
46}
47
48#[derive(Debug, Deserialize)]
54pub struct ChangePasswordRequest {
55 pub current_password: String,
56 pub new_password: String,
57}
58
59pub async fn get_profile(
61 State(state): State<ApiState>,
62 headers: HeaderMap,
63) -> ApiResponse<UserProfile> {
64 match extract_bearer_token(&headers) {
65 Some(token) => {
66 match validate_api_token(&state.auth_framework, &token).await {
67 Ok(auth_token) => {
68 match state
70 .auth_framework
71 .get_user_profile(&auth_token.user_id)
72 .await
73 {
74 Ok(user_profile) => {
75 let mfa_enabled =
77 check_user_mfa_status(&state.auth_framework, &auth_token.user_id)
78 .await;
79
80 let (first_name, last_name) = if let Some(name) = &user_profile.name {
82 let parts: Vec<&str> = name.split_whitespace().collect();
83 if parts.len() >= 2 {
84 (Some(parts[0].to_string()), Some(parts[1..].join(" ")))
85 } else if parts.len() == 1 {
86 (Some(parts[0].to_string()), None)
87 } else {
88 (None, None)
89 }
90 } else {
91 (None, None)
92 };
93
94 let profile = UserProfile {
95 id: auth_token.user_id.clone(),
96 username: user_profile
97 .username
98 .unwrap_or_else(|| format!("user_{}", auth_token.user_id)),
99 email: user_profile.email.unwrap_or_default(),
100 first_name,
101 last_name,
102 roles: auth_token.roles,
103 permissions: auth_token.permissions,
104 mfa_enabled,
105 email_verified: user_profile.email_verified.unwrap_or(false),
106 created_at: user_profile
107 .additional_data
108 .get("created_at")
109 .and_then(|v| v.as_str())
110 .unwrap_or("")
111 .to_string(),
112 updated_at: user_profile
113 .additional_data
114 .get("updated_at")
115 .and_then(|v| v.as_str())
116 .unwrap_or("")
117 .to_string(),
118 };
119
120 ApiResponse::success(profile)
121 }
122 Err(e) => {
123 tracing::warn!(
124 "Failed to fetch user profile for user {}: {}",
125 auth_token.user_id,
126 e
127 );
128 ApiResponse::error_typed(
131 "PROFILE_UNAVAILABLE",
132 "User profile could not be retrieved; please try again",
133 )
134 }
135 }
136 }
137 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
138 }
139 }
140 None => ApiResponse::<UserProfile>::unauthorized_typed(),
141 }
142}
143
144pub async fn update_profile(
146 State(state): State<ApiState>,
147 headers: HeaderMap,
148 Json(req): Json<UpdateProfileRequest>,
149) -> ApiResponse<UserProfile> {
150 match extract_bearer_token(&headers) {
151 Some(token) => {
152 match validate_api_token(&state.auth_framework, &token).await {
153 Ok(auth_token) => {
154 if let Some(ref email) = req.email
157 && crate::utils::validation::validate_email(email).is_err()
158 {
159 return ApiResponse::validation_error_typed("Invalid email format");
160 }
161
162 if req.first_name.as_deref().is_some_and(|n| n.len() > 100) {
164 return ApiResponse::validation_error_typed(
165 "First name must be 100 characters or fewer",
166 );
167 }
168 if req.last_name.as_deref().is_some_and(|n| n.len() > 100) {
169 return ApiResponse::validation_error_typed(
170 "Last name must be 100 characters or fewer",
171 );
172 }
173
174 let storage = state.auth_framework.storage();
176 let user_key = format!("user:{}", auth_token.user_id);
177 let current_data = storage.get_kv(&user_key).await.ok().flatten();
178 let mut user_json: serde_json::Value = current_data
179 .and_then(|b| serde_json::from_slice(&b).ok())
180 .unwrap_or_else(|| serde_json::json!({}));
181
182 if let Some(ref new_email) = req.email {
185 let old_email = user_json["email"].as_str().unwrap_or("").to_string();
186 if old_email != *new_email {
187 let new_email_key = format!("user:email:{}", new_email);
190 if let Ok(Some(existing)) = storage.get_kv(&new_email_key).await {
191 let owner = String::from_utf8_lossy(&existing).to_string();
192 if owner != auth_token.user_id {
193 return ApiResponse::error_typed(
194 "CONFLICT",
195 "Email address is already in use",
196 );
197 }
198 }
199 if !old_email.is_empty() {
201 let _ = storage
202 .delete_kv(&format!("user:email:{}", old_email))
203 .await;
204 }
205 let _ = storage
207 .store_kv(&new_email_key, auth_token.user_id.as_bytes(), None)
208 .await;
209 }
210 user_json["email"] = serde_json::json!(new_email);
211 }
212 let name = match (&req.first_name, &req.last_name) {
213 (Some(f), Some(l)) => Some(format!("{} {}", f, l)),
214 (Some(f), None) => Some(f.clone()),
215 (None, Some(l)) => Some(l.clone()),
216 (None, None) => None,
217 };
218 if let Some(ref n) = name {
219 user_json["name"] = serde_json::json!(n);
220 }
221 user_json["updated_at"] = serde_json::json!(chrono::Utc::now().to_rfc3339());
222
223 if let Ok(serialized) = serde_json::to_vec(&user_json) {
224 let _ = storage.store_kv(&user_key, &serialized, None).await;
225 }
226
227 tracing::info!("Profile updated for user: {}", auth_token.user_id);
228
229 let (stored_username, stored_email, stored_created_at) = {
231 let fresh = storage.get_kv(&user_key).await.ok().flatten();
232 let j: serde_json::Value = fresh
233 .and_then(|b| serde_json::from_slice(&b).ok())
234 .unwrap_or_default();
235 (
236 j["username"].as_str().map(|s| s.to_string()),
237 j["email"].as_str().unwrap_or("").to_string(),
238 j["created_at"].as_str().unwrap_or("").to_string(),
239 )
240 };
241
242 let updated_profile = UserProfile {
244 id: auth_token.user_id.clone(),
245 username: stored_username
246 .unwrap_or_else(|| format!("user_{}", auth_token.user_id)),
247 email: stored_email,
248 first_name: req.first_name,
249 last_name: req.last_name,
250 roles: auth_token.roles,
251 permissions: auth_token.permissions,
252 mfa_enabled: check_user_mfa_status(
253 &state.auth_framework,
254 &auth_token.user_id,
255 )
256 .await,
257 email_verified: user_json["email_verified"].as_bool().unwrap_or(false),
258 created_at: stored_created_at,
259 updated_at: user_json["updated_at"].as_str().unwrap_or("").to_string(),
260 };
261
262 ApiResponse::success(updated_profile)
263 }
264 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
265 }
266 }
267 None => ApiResponse::<UserProfile>::unauthorized_typed(),
268 }
269}
270
271pub async fn change_password(
275 State(state): State<ApiState>,
276 headers: HeaderMap,
277 Json(req): Json<ChangePasswordRequest>,
278) -> ApiResponse<()> {
279 if req.current_password.is_empty() || req.new_password.is_empty() {
280 return ApiResponse::validation_error("Current password and new password are required");
281 }
282
283 if let Err(e) = crate::utils::validation::validate_password(&req.new_password) {
285 return ApiResponse::validation_error_typed(format!("{e}"));
286 }
287
288 match crate::utils::breach_check::is_password_breached(&req.new_password).await {
290 Ok(true) => {
291 return ApiResponse::validation_error(
292 "This password has appeared in a known data breach. Please choose a different password.",
293 );
294 }
295 Ok(false) => {}
296 Err(_) => {} }
298
299 match extract_bearer_token(&headers) {
300 Some(token) => {
301 match validate_api_token(&state.auth_framework, &token).await {
302 Ok(auth_token) => {
303 match state
305 .auth_framework
306 .verify_user_password(&auth_token.user_id, &req.current_password)
307 .await
308 {
309 Ok(true) => {}
310 Ok(false) => {
311 return ApiResponse::validation_error("Current password is incorrect");
312 }
313 Err(_) => {
314 return ApiResponse::validation_error("Current password is incorrect");
317 }
318 }
319
320 let username = match state
322 .auth_framework
323 .get_username_by_id(&auth_token.user_id)
324 .await
325 {
326 Ok(u) => u,
327 Err(e) => return ApiResponse::<()>::from(e),
328 };
329
330 match state
331 .auth_framework
332 .update_user_password(&username, &req.new_password)
333 .await
334 {
335 Ok(()) => {
336 tracing::info!("Password changed for user: {}", auth_token.user_id);
337 ApiResponse::<()>::ok_with_message("Password changed successfully")
338 }
339 Err(e) => ApiResponse::<()>::from(e),
340 }
341 }
342 Err(e) => ApiResponse::<()>::from(e),
343 }
344 }
345 None => ApiResponse::<()>::unauthorized(),
346 }
347}
348
349pub async fn get_user_profile(
351 State(state): State<ApiState>,
352 headers: HeaderMap,
353 Path(user_id): Path<String>,
354) -> ApiResponse<UserProfile> {
355 match extract_bearer_token(&headers) {
356 Some(token) => {
357 match validate_api_token(&state.auth_framework, &token).await {
358 Ok(auth_token) => {
359 if !auth_token.roles.contains(&"admin".to_string()) {
360 return ApiResponse::<UserProfile>::forbidden_typed();
361 }
362
363 match state.auth_framework.get_user_profile(&user_id).await {
364 Ok(user_profile) => {
365 let user_kv_bytes = {
371 let uk = format!("user:{}", user_id);
372 state
373 .auth_framework
374 .storage()
375 .get_kv(&uk)
376 .await
377 .ok()
378 .flatten()
379 };
380 let user_kv_json: serde_json::Value = user_kv_bytes
381 .as_deref()
382 .and_then(|b| serde_json::from_slice(b).ok())
383 .unwrap_or_default();
384
385 let profile_roles: Vec<String> = user_kv_json["roles"]
386 .as_array()
387 .map(|a| {
388 a.iter()
389 .filter_map(|v| v.as_str().map(String::from))
390 .collect()
391 })
392 .unwrap_or_default();
393
394 let profile_permissions: Vec<String> = user_kv_json["permissions"]
395 .as_array()
396 .map(|a| {
397 a.iter()
398 .filter_map(|v| v.as_str().map(String::from))
399 .collect()
400 })
401 .unwrap_or_default();
402
403 let (first_name, last_name) = if let Some(name) = &user_profile.name {
404 let parts: Vec<&str> = name.split_whitespace().collect();
405 if parts.len() >= 2 {
406 (Some(parts[0].to_string()), Some(parts[1..].join(" ")))
407 } else if parts.len() == 1 {
408 (Some(parts[0].to_string()), None)
409 } else {
410 (None, None)
411 }
412 } else {
413 (None, None)
414 };
415
416 let profile = UserProfile {
417 id: user_id.clone(),
418 username: user_profile
419 .username
420 .unwrap_or_else(|| format!("user_{}", user_id)),
421 email: user_profile.email.unwrap_or_default(),
422 first_name,
423 last_name,
424 roles: profile_roles.into(),
425 permissions: profile_permissions.into(),
426 mfa_enabled: user_profile
427 .additional_data
428 .get("mfa_enabled")
429 .and_then(|v| v.as_bool())
430 .unwrap_or(false),
431 email_verified: user_kv_json["email_verified"]
432 .as_bool()
433 .unwrap_or(false),
434 created_at: user_profile
435 .additional_data
436 .get("created_at")
437 .and_then(|v| v.as_str())
438 .unwrap_or("")
439 .to_string(),
440 updated_at: user_profile
441 .additional_data
442 .get("updated_at")
443 .and_then(|v| v.as_str())
444 .unwrap_or("")
445 .to_string(),
446 };
447 ApiResponse::success(profile)
448 }
449 Err(e) => {
450 let error_response = ApiResponse::<()>::from(e);
451 ApiResponse::<UserProfile> {
452 success: error_response.success,
453 data: None,
454 error: error_response.error,
455 message: error_response.message,
456 }
457 }
458 }
459 }
460 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
461 }
462 }
463 None => ApiResponse::<UserProfile>::unauthorized_typed(),
464 }
465}
466
467pub async fn get_sessions(
469 State(state): State<ApiState>,
470 headers: HeaderMap,
471) -> ApiResponse<Vec<SessionInfo>> {
472 match extract_bearer_token(&headers) {
473 Some(token) => match validate_api_token(&state.auth_framework, &token).await {
474 Ok(auth_token) => {
475 let storage = state.auth_framework.storage();
476 match storage.list_user_sessions(&auth_token.user_id).await {
477 Ok(sessions) => {
478 let session_list: Vec<SessionInfo> = sessions
479 .into_iter()
480 .filter(|s| !s.is_expired())
481 .map(|s| SessionInfo {
482 id: s.session_id.clone(),
483 device: s.user_agent.unwrap_or_default(),
484 location: String::new(),
485 ip_address: s.ip_address.unwrap_or_default(),
486 created_at: s.created_at.to_rfc3339(),
487 last_active: s.last_activity.to_rfc3339(),
488 is_current: false,
489 })
490 .collect();
491 ApiResponse::success(session_list)
492 }
493 Err(_e) => {
494 ApiResponse::error_typed("SESSION_ERROR", "Failed to retrieve sessions")
495 }
496 }
497 }
498 Err(_e) => ApiResponse::error_typed("USER_ERROR", "Session operation failed"),
499 },
500 None => ApiResponse::<Vec<SessionInfo>>::unauthorized_typed(),
501 }
502}
503
504#[derive(Debug, Serialize)]
506pub struct SessionInfo {
507 pub id: String,
509 pub device: String,
511 pub location: String,
513 pub ip_address: String,
515 pub created_at: String,
517 pub last_active: String,
519 pub is_current: bool,
521}
522
523pub async fn revoke_session(
528 State(state): State<ApiState>,
529 headers: HeaderMap,
530 Path(session_id): Path<String>,
531) -> ApiResponse<()> {
532 match extract_bearer_token(&headers) {
533 Some(token) => {
534 match validate_api_token(&state.auth_framework, &token).await {
535 Ok(auth_token) => {
536 let storage = state.auth_framework.storage();
537
538 match storage.get_session(&session_id).await {
542 Ok(Some(ref session)) if session.user_id == auth_token.user_id => {}
543 Ok(Some(_)) => {
544 return ApiResponse::<()>::error_typed(
545 "FORBIDDEN",
546 "You do not have permission to revoke this session",
547 );
548 }
549 Ok(None) => {
550 return ApiResponse::<()>::error_typed(
551 "NOT_FOUND",
552 "Session not found",
553 );
554 }
555 Err(e) => return ApiResponse::<()>::from(e),
556 }
557
558 match storage.delete_session(&session_id).await {
559 Ok(()) => {
560 tracing::info!("Revoked session: {}", session_id);
561 ApiResponse::<()>::ok_with_message("Session revoked successfully")
562 }
563 Err(e) => ApiResponse::<()>::from(e),
564 }
565 }
566 Err(e) => ApiResponse::<()>::from(e),
567 }
568 }
569 None => ApiResponse::<()>::unauthorized(),
570 }
571}
572
573async fn check_user_mfa_status(
579 auth_framework: &std::sync::Arc<crate::AuthFramework>,
580 user_id: &str,
581) -> bool {
582 crate::api::mfa::check_user_mfa_status(auth_framework, user_id).await
583}