Skip to main content

quorum_public/routes/
auth.rs

1//! Authentication-related route handlers
2//! Handles user signup, login, account deletion, data retrieval, token refresh, and logout.
3
4use axum::{Json, extract::State, http::StatusCode};
5
6use crate::db::queries::auth;
7use crate::models::user::User;
8use crate::models::user::{
9    AuthTokenResponse, DeleteAccountRequest, GetUserDataRequest, LoginRequest, SignupRequest,
10    TokenResponse, UpdateUserProfileRequest, UserDataResponse,
11};
12use crate::utility::auth_common::generate_backup_codes;
13use quorum_core::db::DB;
14use quorum_core::db::queries::audit_logs::log_audit_event;
15use quorum_core::models::server::AuditEvent;
16use quorum_core::utility::config::Config;
17
18use chrono;
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Deserialize)]
22pub struct RefreshTokenRequest {
23    pub user_id: String,
24    pub refresh_token: String,
25}
26
27#[derive(Debug, Serialize)]
28pub struct SignupResponse {
29    #[serde(flatten)]
30    pub auth: AuthTokenResponse,
31
32    pub backup_codes: Option<Vec<String>>,
33}
34
35/// Verifies user credentials (username/email and password) against the database
36///
37// # Arguments
38/// * `db` - A reference to the database connection
39/// * `username_or_email` - The username or email provided by the user
40/// * `password` - The password provided by the user
41///
42/// # Returns
43/// * `Ok(User)` - The User struct if the credentials are valid
44/// * `Err((StatusCode, String))` - A tuple containing the HTTP status code and an error message if the credentials are invalid
45async fn verify_user_credentials(
46    db: &DB,
47    username_or_email: &str,
48    password: &str,
49) -> Result<User, (StatusCode, String)> {
50    auth::verify_user_credentials(db, username_or_email, password)
51        .await
52        .map_err(|_| {
53            (
54                StatusCode::UNAUTHORIZED,
55                "Invalid username/email or password".to_string(),
56            )
57        })
58}
59
60/// Extracts the user ID as a string from the User struct
61///
62/// # Arguments
63/// * `user` - The User struct from which to extract the ID
64///
65/// # Returns
66/// * `Ok(String)` - The user ID as a string if extraction is successful
67/// * `Err(String)` - An error message if the ID format is invalid
68fn extract_user_id(user: &User) -> Result<String, String> {
69    match &user.id.key {
70        surrealdb_types::RecordIdKey::String(s) => Ok(s.to_string()),
71        surrealdb_types::RecordIdKey::Number(n) => Ok(n.to_string()),
72        _ => Err("Invalid user ID format".to_string()),
73    }
74}
75
76/// Handles user signup requests
77///
78/// # Arguments
79/// * `State(db)` - Shared state containing the database connection and JWT configuration
80/// * `Json(payload)` - The signup request payload containing username, email, and password
81///
82/// # Returns
83/// * `(StatusCode, Json<AuthTokenResponse>)` - The HTTP status code and JSON response containing the authentication token or error message
84///
85/// # Error
86/// * `StatusCode::INTERNAL_SERVER_ERROR` - If there is an error processing the password, creating the user, generating tokens, or storing the refresh token
87/// * `StatusCode::CONFLICT` - If the username already exists
88/// * `StatusCode::BAD_REQUEST` - If there is a user ID mismatch during account deletion
89///
90/// # Example
91/// ```rust
92/// let payload = SignupRequest {
93///     username: "newuser".to_string(),
94///     email: Some("newuser@example.com".to_string()),
95///     password: "password123".to_string(),
96/// };
97/// ```
98pub async fn signup(
99    State(db): State<DB>,
100    Json(payload): Json<SignupRequest>,
101) -> (StatusCode, Json<SignupResponse>) {
102    let mut plain_backup_codes = None;
103    let mut hash_backup_codes = None;
104
105    if payload.email.is_none() {
106        let backup_codes = Some(generate_backup_codes());
107
108        plain_backup_codes = Some(
109            backup_codes
110                .as_ref()
111                .unwrap()
112                .iter()
113                .filter_map(|code| code.plain.clone())
114                .collect::<Vec<String>>(),
115        );
116        hash_backup_codes = Some(
117            backup_codes
118                .as_ref()
119                .unwrap()
120                .iter()
121                .map(|code| code.hash.clone())
122                .collect::<Vec<String>>(),
123        );
124    }
125
126    let signup_result = auth::signup_user(
127        &db,
128        &payload.username,
129        payload.email.as_deref(),
130        &payload.password,
131        hash_backup_codes.clone(),
132    )
133    .await;
134
135    if let Err(e) = signup_result {
136        let message = e.to_string();
137        let formatted = format!("Failed to create user: {}", message);
138
139        let status = if message.contains("Invalid password length")
140            || message.contains("idx_username")
141            || message.contains("idx_email")
142        {
143            StatusCode::BAD_REQUEST
144        } else if message.contains("idx_username") || message.contains("UNIQUE") {
145            StatusCode::CONFLICT
146        } else {
147            StatusCode::INTERNAL_SERVER_ERROR
148        };
149
150        let _ = log_audit_event(
151            &db,
152            AuditEvent {
153                log_type: "signup_failed".to_string(),
154                action: Some(message.clone()),
155                ..Default::default()
156            },
157        )
158        .await;
159
160        return (
161            status,
162            Json(SignupResponse {
163                auth: AuthTokenResponse {
164                    success: false,
165                    user: None,
166                    tokens: None,
167                    message: formatted,
168                },
169                backup_codes: None,
170            }),
171        );
172    }
173
174    let user = signup_result.unwrap();
175
176    let user_id = match extract_user_id(&user) {
177        Ok(id) => id,
178        Err(e) => {
179            return (
180                StatusCode::INTERNAL_SERVER_ERROR,
181                Json(SignupResponse {
182                    auth: AuthTokenResponse {
183                        success: false,
184                        user: None,
185                        tokens: None,
186                        message: e,
187                    },
188                    backup_codes: None,
189                }),
190            );
191        }
192    };
193
194    let access_token = match crate::utility::jwt::generate_access_token(&user_id, &user.username) {
195        Ok(token) => token,
196        Err(_) => {
197            return (
198                StatusCode::INTERNAL_SERVER_ERROR,
199                Json(SignupResponse {
200                    auth: AuthTokenResponse {
201                        success: false,
202                        user: None,
203                        tokens: None,
204                        message: "Failed to generate access token".to_string(),
205                    },
206                    backup_codes: None,
207                }),
208            );
209        }
210    };
211
212    let refresh_token = match crate::utility::jwt::generate_refresh_token(&user_id, &user.username)
213    {
214        Ok(token) => token,
215        Err(_) => {
216            return (
217                StatusCode::INTERNAL_SERVER_ERROR,
218                Json(SignupResponse {
219                    auth: AuthTokenResponse {
220                        success: false,
221                        user: None,
222                        tokens: None,
223                        message: "Failed to generate refresh token".to_string(),
224                    },
225                    backup_codes: None,
226                }),
227            );
228        }
229    };
230
231    let config = quorum_core::utility::config::Config::get();
232    let expires_at = chrono::Utc::now().timestamp() + (config.jwt_refresh_days * 86400);
233
234    if auth::store_refresh_token(&db, &user_id, &refresh_token, expires_at)
235        .await
236        .is_err()
237    {
238        return (
239            StatusCode::INTERNAL_SERVER_ERROR,
240            Json(SignupResponse {
241                auth: AuthTokenResponse {
242                    success: false,
243                    user: None,
244                    tokens: None,
245                    message: "Failed to store refresh token".to_string(),
246                },
247                backup_codes: None,
248            }),
249        );
250    }
251
252    let _ = log_audit_event(
253        &db,
254        AuditEvent {
255            log_type: "signup_success".to_string(),
256            target_type_table: Some("users".to_string()),
257            target_type_table_id: Some(user_id.clone()),
258            user_id: Some(user_id.clone()),
259            ..Default::default()
260        },
261    )
262    .await;
263
264    (
265        StatusCode::CREATED,
266        Json(SignupResponse {
267            auth: AuthTokenResponse {
268                success: true,
269                user: Some(user.to_response()),
270                tokens: Some(TokenResponse {
271                    access_token,
272                    refresh_token,
273                    expires_in: quorum_core::utility::config::Config::get().jwt_access_minutes * 60,
274                }),
275                message: "User created successfully".to_string(),
276            },
277            backup_codes: plain_backup_codes,
278        }),
279    )
280}
281
282/// Handles user login requests
283///
284/// # Arguments
285/// * `State(db)` - Shared state containing the database connection and JWT configuration
286/// * `Json(payload)` - The login request payload containing username/email and password
287///
288/// # Returns
289/// * `(StatusCode, Json<AuthTokenResponse>)` - The HTTP status code and JSON response containing the authentication token or error message
290///
291/// # Error
292/// * `StatusCode::UNAUTHORIZED` - If the username/email or password is incorrect
293/// * `StatusCode::INTERNAL_SERVER_ERROR` - If there is an error verifying credentials, generating tokens, or storing the refresh token
294///
295/// # Example
296/// ```rust
297/// let payload = LoginRequest {
298///     username_or_email: "user@example.com".to_string(),
299///     password: "password123".to_string(),
300/// };
301/// ```
302pub async fn login(
303    State(db): State<DB>,
304    Json(payload): Json<LoginRequest>,
305) -> (StatusCode, Json<AuthTokenResponse>) {
306    let user =
307        match verify_user_credentials(&db, &payload.username_or_email, &payload.password).await {
308            Ok(user) => user,
309            Err((status, message)) => {
310                return (
311                    status,
312                    Json(AuthTokenResponse {
313                        success: false,
314                        user: None,
315                        tokens: None,
316                        message,
317                    }),
318                );
319            }
320        };
321
322    let user_id = match extract_user_id(&user) {
323        Ok(id) => id,
324        Err(e) => {
325            let _ = log_audit_event(
326                &db,
327                AuditEvent {
328                    log_type: "login_failed".to_string(),
329                    action: Some(e.clone()),
330                    ..Default::default()
331                },
332            )
333            .await;
334
335            return (
336                StatusCode::INTERNAL_SERVER_ERROR,
337                Json(AuthTokenResponse {
338                    success: false,
339                    user: None,
340                    tokens: None,
341                    message: e,
342                }),
343            );
344        }
345    };
346
347    let access_token = match crate::utility::jwt::generate_access_token(&user_id, &user.username) {
348        Ok(token) => token,
349        Err(_) => {
350            return (
351                StatusCode::INTERNAL_SERVER_ERROR,
352                Json(AuthTokenResponse {
353                    success: false,
354                    user: None,
355                    tokens: None,
356                    message: "Failed to generate access token".to_string(),
357                }),
358            );
359        }
360    };
361
362    let refresh_token = match crate::utility::jwt::generate_refresh_token(&user_id, &user.username)
363    {
364        Ok(token) => token,
365        Err(_) => {
366            return (
367                StatusCode::INTERNAL_SERVER_ERROR,
368                Json(AuthTokenResponse {
369                    success: false,
370                    user: None,
371                    tokens: None,
372                    message: "Failed to generate refresh token".to_string(),
373                }),
374            );
375        }
376    };
377
378    let expires_at = chrono::Utc::now().timestamp() + (Config::get().jwt_refresh_days * 86400);
379    if auth::store_refresh_token(&db, &user_id, &refresh_token, expires_at)
380        .await
381        .is_err()
382    {
383        return (
384            StatusCode::INTERNAL_SERVER_ERROR,
385            Json(AuthTokenResponse {
386                success: false,
387                user: None,
388                tokens: None,
389                message: "Failed to store refresh token".to_string(),
390            }),
391        );
392    }
393
394    let _ = log_audit_event(
395        &db,
396        AuditEvent {
397            log_type: "login_success".to_string(),
398            target_type_table: Some("users".to_string()),
399            target_type_table_id: Some(user_id.clone()),
400            user_id: Some(user_id.clone()),
401            ..Default::default()
402        },
403    )
404    .await;
405
406    (
407        StatusCode::OK,
408        Json(AuthTokenResponse {
409            success: true,
410            user: Some(user.to_response()),
411            tokens: Some(TokenResponse {
412                access_token,
413                refresh_token,
414                expires_in: Config::get().jwt_access_minutes * 60,
415            }),
416            message: "Login successful".to_string(),
417        }),
418    )
419}
420
421/// Handles user account deletion requests
422///
423/// # Arguments
424/// * `State(db)` - Shared state containing the database connection
425/// * `Json(payload)` - The account deletion request payload containing username/email, password, and user ID
426///
427/// # Returns
428/// * `(StatusCode, Json<AuthTokenResponse>)` - The HTTP status code and JSON response indicating success or failure of the account deletion
429///
430/// # Example
431/// ```rust
432/// let payload = DeleteAccountRequest {
433///     username_or_email: "john_doe",
434///     password: "secret_password",
435///     user_id: "user_id"
436/// };
437/// ```
438pub async fn delete_account(
439    State(db): State<DB>,
440    Json(payload): Json<DeleteAccountRequest>,
441) -> (StatusCode, Json<AuthTokenResponse>) {
442    let _user =
443        match auth::verify_user_credentials(&db, &payload.username_or_email, &payload.password)
444            .await
445        {
446            Ok(user) => user,
447            Err((status, message)) => {
448                let _ = log_audit_event(
449                    &db,
450                    AuditEvent {
451                        log_type: "delete_account_failed".to_string(),
452                        action: Some(message.clone()),
453                        target_type_table: Some("users".to_string()),
454                        target_type_table_id: Some(payload.user_id.clone()),
455                        ..Default::default()
456                    },
457                )
458                .await;
459
460                return (
461                    status,
462                    Json(AuthTokenResponse {
463                        success: false,
464                        user: None,
465                        tokens: None,
466                        message,
467                    }),
468                );
469            }
470        };
471
472    if auth::delete_user_by_id(&db, &payload.user_id)
473        .await
474        .is_err()
475    {
476        return (
477            StatusCode::INTERNAL_SERVER_ERROR,
478            Json(AuthTokenResponse {
479                success: false,
480                user: None,
481                tokens: None,
482                message: "Failed to delete account".to_string(),
483            }),
484        );
485    }
486
487    let _ = log_audit_event(
488        &db,
489        AuditEvent {
490            log_type: "delete_account_success".to_string(),
491            ..Default::default()
492        },
493    )
494    .await;
495
496    (
497        StatusCode::OK,
498        Json(AuthTokenResponse {
499            success: true,
500            user: None,
501            tokens: None,
502            message: "Account deleted successfully".to_string(),
503        }),
504    )
505}
506
507/// Handles user data retrieval requests
508///
509/// # Arguments
510/// * `State(db)` - Shared state containing the database connection
511/// * `Json(payload)` - The user data retrieval request payload containing username/email, password, and requested fields
512///
513/// # Returns
514/// * `(StatusCode, Json<UserDataResponse>)` - The HTTP status code and JSON response containing the requested user data or an error message
515///
516/// # Example
517/// ```rust
518/// let payload = GetUserDataRequest {
519///     username_or_email: "john_doe",
520///     password: "secret_password",
521///     user_id: "user_id",
522///     fields: vec!["id".to_string(), "username".to_string()],
523/// };
524/// ```
525pub async fn get_user_data(
526    State(db): State<DB>,
527    Json(payload): Json<GetUserDataRequest>,
528) -> (StatusCode, Json<UserDataResponse>) {
529    let user =
530        match verify_user_credentials(&db, &payload.username_or_email, &payload.password).await {
531            Ok(user) => user,
532            Err((status, message)) => {
533                let _ = log_audit_event(
534                    &db,
535                    AuditEvent {
536                        log_type: "get_user_data_failed".to_string(),
537                        action: Some(message.clone()),
538                        target_type_table: Some("users".to_string()),
539                        target_type_table_id: Some(payload.username_or_email.clone()),
540                        user_id: Some(payload.user_id),
541                        ..Default::default()
542                    },
543                )
544                .await;
545
546                return (
547                    status,
548                    Json(UserDataResponse {
549                        success: false,
550                        data: None,
551                        message,
552                    }),
553                );
554            }
555        };
556
557    let mut user_data = serde_json::Map::new();
558
559    for field in &payload.fields {
560        match field.as_str() {
561            "id" => {
562                user_data.insert(
563                    "id".to_string(),
564                    serde_json::Value::String(format!("{:?}", user.id.key)),
565                );
566            }
567            "username" => {
568                user_data.insert(
569                    "username".to_string(),
570                    serde_json::Value::String(user.username.clone()),
571                );
572            }
573            "email" => {
574                if let Some(email) = &user.email {
575                    user_data.insert(
576                        "email".to_string(),
577                        serde_json::Value::String(email.clone()),
578                    );
579                }
580            }
581            "created_at" => {
582                user_data.insert(
583                    "created_at".to_string(),
584                    serde_json::Value::String(user.created_at.to_string()),
585                );
586            }
587            _ => {}
588        }
589    }
590
591    let _ = log_audit_event(
592        &db,
593        AuditEvent {
594            log_type: "get_user_data_success".to_string(),
595            target_type_table: Some("users".to_string()),
596            target_type_table_id: Some(payload.user_id.clone()),
597            user_id: Some(payload.user_id),
598            ..Default::default()
599        },
600    )
601    .await;
602
603    (
604        StatusCode::OK,
605        Json(UserDataResponse {
606            success: true,
607            data: Some(user_data),
608            message: "User data retrieved successfully".to_string(),
609        }),
610    )
611}
612
613/// Handles token refresh requests
614/// This endpoint allows clients to obtain a new access token using a valid refresh token. It verifies the refresh token, checks its validity in the database, and generates a new access token if everything is valid. The refresh token is not rotated in this implementation, but it can be easily modified to do so if desired.
615///
616/// # Arguments
617/// * `State(_db)` - Shared state containing the database connection (not used but required by Axum)
618/// * `Json(payload)` - The refresh token request payload containing the refresh token
619///
620/// # Returns
621/// * `(StatusCode, Json<AuthTokenResponse>)` - The HTTP status code and JSON response containing the new access token or an error message
622///
623/// # Error
624/// * `StatusCode::UNAUTHORIZED` - If the refresh token is invalid, expired, of the wrong type, or not found in the database
625/// * `StatusCode::INTERNAL_SERVER_ERROR` - If there is an error generating the new access token
626///
627/// # Example
628/// ```rust
629/// let payload = RefreshTokenRequest {
630///     refresh_token: "valid_refresh_token".to_string(),
631/// };
632/// ```
633pub async fn refresh_token(
634    State(db): State<DB>,
635    Json(payload): Json<RefreshTokenRequest>,
636) -> (StatusCode, Json<AuthTokenResponse>) {
637    // Verify token
638    let claims = match crate::utility::jwt::verify_token(&payload.refresh_token) {
639        Ok(claims) => {
640            if claims.token_type != "refresh" {
641                return (
642                    StatusCode::UNAUTHORIZED,
643                    Json(AuthTokenResponse {
644                        success: false,
645                        user: None,
646                        tokens: None,
647                        message: "Invalid token type".to_string(),
648                    }),
649                );
650            }
651            claims
652        }
653        Err(_) => {
654            let user_id = payload.user_id.clone();
655
656            let _ = log_audit_event(
657                &db,
658                AuditEvent {
659                    log_type: "refresh_token_failed".to_string(),
660                    action: Some("Invalid or expired refresh token".to_string()),
661                    target_type_table_id: Some(user_id.clone()),
662                    user_id: Some(user_id),
663                    ..Default::default()
664                },
665            )
666            .await;
667
668            return (
669                StatusCode::UNAUTHORIZED,
670                Json(AuthTokenResponse {
671                    success: false,
672                    user: None,
673                    tokens: None,
674                    message: "Invalid or expired refresh token".to_string(),
675                }),
676            );
677        }
678    };
679
680    let user_id = claims.sub.clone();
681
682    if auth::validate_refresh_token(&db, &user_id, &payload.refresh_token)
683        .await
684        .is_err()
685    {
686        let _ = log_audit_event(
687            &db,
688            AuditEvent {
689                log_type: "refresh_token_failed".to_string(),
690                action: Some("Refresh token not found or revoked".to_string()),
691                target_type_table: Some("users".to_string()),
692                target_type_table_id: Some(user_id.clone()),
693                user_id: Some(user_id.clone()),
694                ..Default::default()
695            },
696        )
697        .await;
698
699        return (
700            StatusCode::UNAUTHORIZED,
701            Json(AuthTokenResponse {
702                success: false,
703                user: None,
704                tokens: None,
705                message: "Invalid or expired refresh token".to_string(),
706            }),
707        );
708    }
709
710    //Generate access token
711    let access_token =
712        match crate::utility::jwt::generate_access_token(&claims.sub, &claims.username) {
713            Ok(token) => token,
714            Err(_) => {
715                return (
716                    StatusCode::INTERNAL_SERVER_ERROR,
717                    Json(AuthTokenResponse {
718                        success: false,
719                        user: None,
720                        tokens: None,
721                        message: "Failed to generate access token".to_string(),
722                    }),
723                );
724            }
725        };
726
727    let _ = log_audit_event(
728        &db,
729        AuditEvent {
730            log_type: "refresh_token_success".to_string(),
731            target_type_table: Some("users".to_string()),
732            target_type_table_id: Some(user_id.clone()),
733            user_id: Some(user_id),
734            ..Default::default()
735        },
736    )
737    .await;
738
739    (
740        StatusCode::OK,
741        Json(AuthTokenResponse {
742            success: true,
743            user: None,
744            tokens: Some(TokenResponse {
745                access_token,
746                refresh_token: payload.refresh_token,
747                expires_in: Config::get().jwt_access_minutes * 60,
748            }),
749            message: "Token refreshed successfully".to_string(),
750        }),
751    )
752}
753
754/// Handles user logout requests
755/// This endpoint allows clients to log out by revoking the provided refresh token. It removes the refresh token from the database,
756/// effectively invalidating it and preventing any further use for obtaining new access tokens.
757///
758/// # Arguments
759/// * `State(db)` - Shared state containing the database connection and JWT configuration (JWT config is not used in this handler)
760/// * `Json(payload)` - The logout request payload containing the refresh token to be revoked
761///
762/// # Returns
763/// * `(StatusCode, Json<AuthResponse>)` - The HTTP status code and JSON response indicating success or failure of the logout operation
764///
765/// # Error
766/// * `StatusCode::INTERNAL_SERVER_ERROR` - If there is an error revoking the refresh token
767///
768/// # Example
769/// ```rust
770/// let payload = RefreshTokenRequest {
771///     refresh_token: "valid_refresh_token".to_string(),
772/// };
773/// ```
774pub async fn logout(
775    State(db): State<DB>,
776    Json(payload): Json<RefreshTokenRequest>,
777) -> (StatusCode, Json<AuthTokenResponse>) {
778    if crate::utility::jwt::verify_token(&payload.refresh_token).is_err() {
779        let user_id = payload.user_id.clone();
780
781        let _ = log_audit_event(
782            &db,
783            AuditEvent {
784                log_type: "logout_failed".to_string(),
785                action: Some("Invalid or expired refresh token".to_string()),
786                user_id: Some(user_id),
787                ..Default::default()
788            },
789        )
790        .await;
791
792        return (
793            StatusCode::UNAUTHORIZED,
794            Json(AuthTokenResponse {
795                success: false,
796                user: None,
797                tokens: None,
798                message: "Invalid or expired refresh token".to_string(),
799            }),
800        );
801    }
802
803    if auth::revoke_refresh_token(&db, &payload.refresh_token)
804        .await
805        .is_err()
806    {
807        let user_id = payload.user_id.clone();
808
809        let _ = log_audit_event(
810            &db,
811            AuditEvent {
812                log_type: "logout_failed".to_string(),
813                action: Some("Failed to revoke refresh token".to_string()),
814                user_id: Some(user_id),
815                ..Default::default()
816            },
817        )
818        .await;
819
820        return (
821            StatusCode::INTERNAL_SERVER_ERROR,
822            Json(AuthTokenResponse {
823                success: false,
824                user: None,
825                tokens: None,
826                message: "Failed to logout".to_string(),
827            }),
828        );
829    }
830
831    let _ = log_audit_event(
832        &db,
833        AuditEvent {
834            log_type: "logout_success".to_string(),
835            user_id: Some(payload.user_id),
836            ..Default::default()
837        },
838    )
839    .await;
840
841    (
842        StatusCode::OK,
843        Json(AuthTokenResponse {
844            success: true,
845            user: None,
846            tokens: None,
847            message: "Logged out successfully".to_string(),
848        }),
849    )
850}
851
852/// Handles user profile update requests
853/// This endpoint allows clients to update their user profile information,
854/// such as username and email. It verifies the provided data, updates the user profile in the database, and logs the operation.
855///
856/// # Arguments
857/// * `State(db)` - Shared state containing the database connection
858/// * `Json(payload)` - The profile update request payload containing the user ID and new profile information
859///
860/// # Returns
861/// * `(StatusCode, Json<AuthTokenResponse>)` - The HTTP status code and JSON response indicating success or failure of the profile update operation
862///
863/// # Example
864/// ```rust
865/// let payload = UpdateUserProfileRequest {
866///     user_id: "user_id".to_string(),
867///     username: Some("new_username".to_string()),
868///     email: Some("new_email@example.com".to_string()),
869/// };
870/// ```
871pub async fn update_user_profile(
872    State(db): State<DB>,
873    Json(payload): Json<UpdateUserProfileRequest>,
874) -> (StatusCode, Json<AuthTokenResponse>) {
875    if payload.username.trim().is_empty() {
876        return (
877            StatusCode::BAD_REQUEST,
878            Json(AuthTokenResponse {
879                success: false,
880                user: None,
881                tokens: None,
882                message: "Username cannot be empty".to_string(),
883            }),
884        );
885    }
886
887    let user = match auth::update_user_profile(&db, &payload).await {
888        Ok(user) => user,
889        Err((status, message)) => {
890            let _ = log_audit_event(
891                &db,
892                AuditEvent {
893                    log_type: "update_user_profile_failed".to_string(),
894                    action: Some(message.clone()),
895                    target_type_table: Some("users".to_string()),
896                    target_type_table_id: Some(payload.user_id.clone()),
897                    user_id: Some(payload.user_id),
898                    ..Default::default()
899                },
900            )
901            .await;
902
903            return (
904                status,
905                Json(AuthTokenResponse {
906                    success: false,
907                    user: None,
908                    tokens: None,
909                    message,
910                }),
911            );
912        }
913    };
914
915    let _ = log_audit_event(
916        &db,
917        AuditEvent {
918            log_type: "update_user_profile_success".to_string(),
919            target_type_table: Some("users".to_string()),
920            target_type_table_id: Some(payload.user_id.clone()),
921            user_id: Some(payload.user_id),
922            ..Default::default()
923        },
924    )
925    .await;
926
927    (
928        StatusCode::OK,
929        Json(AuthTokenResponse {
930            success: true,
931            user: Some(user.to_response()),
932            tokens: None,
933            message: "Profile updated successfully".to_string(),
934        }),
935    )
936}