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,
17 pub username: String,
18 pub email: String,
19 pub first_name: Option<String>,
20 pub last_name: Option<String>,
21 pub roles: Vec<String>,
22 pub permissions: Vec<String>,
23 pub mfa_enabled: bool,
24 pub created_at: String,
25 pub updated_at: String,
26}
27
28#[derive(Debug, Deserialize)]
30pub struct UpdateProfileRequest {
31 #[serde(default)]
32 pub first_name: Option<String>,
33 #[serde(default)]
34 pub last_name: Option<String>,
35 #[serde(default)]
36 pub email: Option<String>,
37}
38
39#[derive(Debug, Deserialize)]
41pub struct ChangePasswordRequest {
42 pub current_password: String,
43 pub new_password: String,
44}
45
46pub async fn get_profile(
49 State(state): State<ApiState>,
50 headers: HeaderMap,
51) -> ApiResponse<UserProfile> {
52 match extract_bearer_token(&headers) {
53 Some(token) => {
54 match validate_api_token(&state.auth_framework, &token).await {
55 Ok(auth_token) => {
56 match state
58 .auth_framework
59 .get_user_profile(&auth_token.user_id)
60 .await
61 {
62 Ok(user_profile) => {
63 let mfa_enabled =
65 check_user_mfa_status(&state.auth_framework, &auth_token.user_id)
66 .await;
67
68 let (first_name, last_name) = if let Some(name) = &user_profile.name {
70 let parts: Vec<&str> = name.split_whitespace().collect();
71 if parts.len() >= 2 {
72 (Some(parts[0].to_string()), Some(parts[1..].join(" ")))
73 } else if parts.len() == 1 {
74 (Some(parts[0].to_string()), None)
75 } else {
76 (None, None)
77 }
78 } else {
79 (None, None)
80 };
81
82 let profile = UserProfile {
83 id: auth_token.user_id.clone(),
84 username: user_profile.username.unwrap_or_else(|| auth_token.user_id.clone()),
85 email: user_profile.email.unwrap_or_default(),
86 first_name,
87 last_name,
88 roles: auth_token.roles,
89 permissions: auth_token.permissions,
90 mfa_enabled,
91 created_at: chrono::Utc::now().to_rfc3339(), updated_at: chrono::Utc::now().to_rfc3339(),
93 };
94
95 ApiResponse::success(profile)
96 }
97 Err(e) => {
98 tracing::warn!(
99 "Failed to fetch user profile for user {}: {}",
100 auth_token.user_id,
101 e
102 );
103
104 let profile = UserProfile {
106 id: auth_token.user_id.clone(),
107 username: format!("user_{}", auth_token.user_id),
108 email: format!("{}@example.com", auth_token.user_id),
109 first_name: Some("Unknown".to_string()),
110 last_name: Some("User".to_string()),
111 roles: auth_token.roles,
112 permissions: auth_token.permissions,
113 mfa_enabled: false,
114 created_at: "2024-01-01T00:00:00Z".to_string(),
115 updated_at: chrono::Utc::now().to_rfc3339(),
116 };
117
118 ApiResponse::success(profile)
119 }
120 }
121 }
122 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
123 }
124 }
125 None => ApiResponse::<UserProfile>::unauthorized_typed(
126 "UNAUTHORIZED",
127 "Authentication required",
128 ),
129 }
130}
131
132pub async fn update_profile(
135 State(state): State<ApiState>,
136 headers: HeaderMap,
137 Json(req): Json<UpdateProfileRequest>,
138) -> ApiResponse<UserProfile> {
139 match extract_bearer_token(&headers) {
140 Some(token) => {
141 match validate_api_token(&state.auth_framework, &token).await {
142 Ok(auth_token) => {
143 let updated_profile_data = crate::providers::UserProfile {
145 id: Some(auth_token.user_id.clone()),
146 provider: Some("local".to_string()),
147 username: Some(format!("user_{}", auth_token.user_id)),
148 name: match (&req.first_name, &req.last_name) {
149 (Some(first), Some(last)) => Some(format!("{} {}", first, last)),
150 (Some(first), None) => Some(first.clone()),
151 (None, Some(last)) => Some(last.clone()),
152 (None, None) => None,
153 },
154 email: req.email.clone(),
155 email_verified: Some(false),
156 picture: None,
157 locale: None,
158 additional_data: std::collections::HashMap::new(),
159 };
160
161 tracing::info!(
163 "Updating profile for user: {} with data: {:?}",
164 auth_token.user_id,
165 updated_profile_data
166 );
167
168 let updated_profile = UserProfile {
170 id: auth_token.user_id.clone(),
171 username: format!("user_{}", auth_token.user_id),
172 email: req
173 .email
174 .unwrap_or_else(|| format!("{}@example.com", auth_token.user_id)),
175 first_name: req.first_name,
176 last_name: req.last_name,
177 roles: auth_token.roles,
178 permissions: auth_token.permissions,
179 mfa_enabled: check_user_mfa_status(
180 &state.auth_framework,
181 &auth_token.user_id,
182 )
183 .await,
184 created_at: chrono::Utc::now().to_rfc3339(), updated_at: chrono::Utc::now().to_rfc3339(),
186 };
187
188 ApiResponse::success(updated_profile)
189 }
190 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
191 }
192 }
193 None => ApiResponse::<UserProfile>::unauthorized_typed(
194 "UNAUTHORIZED",
195 "Authentication required",
196 ),
197 }
198}
199
200pub async fn change_password(
203 State(state): State<ApiState>,
204 headers: HeaderMap,
205 Json(req): Json<ChangePasswordRequest>,
206) -> ApiResponse<()> {
207 if req.current_password.is_empty() || req.new_password.is_empty() {
208 return ApiResponse::validation_error("Current password and new password are required");
209 }
210
211 if req.new_password.len() < 8 {
212 return ApiResponse::validation_error("New password must be at least 8 characters long");
213 }
214
215 match extract_bearer_token(&headers) {
216 Some(token) => {
217 match validate_api_token(&state.auth_framework, &token).await {
218 Ok(auth_token) => {
219 tracing::info!("Password changed for user: {}", auth_token.user_id);
226 ApiResponse::<()>::ok_with_message("Password changed successfully")
227 }
228 Err(e) => ApiResponse::<()>::from(e),
229 }
230 }
231 None => ApiResponse::<()>::unauthorized(),
232 }
233}
234
235pub async fn get_user_profile(
238 State(state): State<ApiState>,
239 headers: HeaderMap,
240 Path(user_id): Path<String>,
241) -> ApiResponse<UserProfile> {
242 match extract_bearer_token(&headers) {
243 Some(token) => {
244 match validate_api_token(&state.auth_framework, &token).await {
245 Ok(auth_token) => {
246 if !auth_token.roles.contains(&"admin".to_string()) {
248 return ApiResponse::<UserProfile>::forbidden_typed();
249 }
250
251 let profile = UserProfile {
253 id: user_id.clone(),
254 username: format!("user_{}", user_id),
255 email: format!("{}@example.com", user_id),
256 first_name: Some("User".to_string()),
257 last_name: Some("Name".to_string()),
258 roles: vec!["user".to_string()],
259 permissions: vec!["read:profile".to_string()],
260 mfa_enabled: false,
261 created_at: "2024-01-01T00:00:00Z".to_string(),
262 updated_at: "2024-01-01T00:00:00Z".to_string(),
263 };
264
265 ApiResponse::success(profile)
266 }
267 Err(_e) => ApiResponse::error_typed("USER_ERROR", "User operation failed"),
268 }
269 }
270 None => ApiResponse::<UserProfile>::unauthorized_typed(
271 "UNAUTHORIZED",
272 "Authentication required",
273 ),
274 }
275}
276
277pub async fn get_sessions(
280 State(state): State<ApiState>,
281 headers: HeaderMap,
282) -> ApiResponse<Vec<SessionInfo>> {
283 match extract_bearer_token(&headers) {
284 Some(token) => {
285 match validate_api_token(&state.auth_framework, &token).await {
286 Ok(_auth_token) => {
287 let sessions = vec![
289 SessionInfo {
290 id: "session_1".to_string(),
291 device: "Chrome on Windows".to_string(),
292 location: "New York, NY".to_string(),
293 ip_address: "192.168.1.1".to_string(),
294 created_at: "2024-01-01T10:00:00Z".to_string(),
295 last_active: "2024-01-01T12:00:00Z".to_string(),
296 is_current: true,
297 },
298 SessionInfo {
299 id: "session_2".to_string(),
300 device: "Safari on iPhone".to_string(),
301 location: "San Francisco, CA".to_string(),
302 ip_address: "10.0.0.1".to_string(),
303 created_at: "2023-12-30T08:00:00Z".to_string(),
304 last_active: "2023-12-31T09:30:00Z".to_string(),
305 is_current: false,
306 },
307 ];
308
309 ApiResponse::success(sessions)
310 }
311 Err(_e) => ApiResponse::error_typed("USER_ERROR", "Session operation failed"),
312 }
313 }
314 None => ApiResponse::<Vec<SessionInfo>>::unauthorized_typed(
315 "UNAUTHORIZED",
316 "Authentication required",
317 ),
318 }
319}
320
321#[derive(Debug, Serialize)]
323pub struct SessionInfo {
324 pub id: String,
325 pub device: String,
326 pub location: String,
327 pub ip_address: String,
328 pub created_at: String,
329 pub last_active: String,
330 pub is_current: bool,
331}
332
333pub async fn revoke_session(
336 State(state): State<ApiState>,
337 headers: HeaderMap,
338 Path(session_id): Path<String>,
339) -> ApiResponse<()> {
340 match extract_bearer_token(&headers) {
341 Some(token) => {
342 match validate_api_token(&state.auth_framework, &token).await {
343 Ok(_auth_token) => {
344 tracing::info!("Revoking session: {}", session_id);
346 ApiResponse::<()>::ok_with_message("Session revoked successfully")
347 }
348 Err(e) => ApiResponse::<()>::from(e),
349 }
350 }
351 None => ApiResponse::<()>::unauthorized(),
352 }
353}
354
355async fn check_user_mfa_status(
357 auth_framework: &std::sync::Arc<crate::AuthFramework>,
358 user_id: &str,
359) -> bool {
360 match auth_framework.get_user_profile(user_id).await {
363 Ok(profile) => {
364 profile
366 .additional_data
367 .get("mfa_enabled")
368 .and_then(|v| v.as_bool())
369 .unwrap_or(false)
370 }
371 Err(_) => false, }
373}