api/routes/auth/
post.rs

1use std::fs;
2use std::path::PathBuf;
3use axum::{
4    extract::{State, Multipart},
5    http::StatusCode,
6    response::IntoResponse,
7    Json,
8};
9use sea_orm::{EntityTrait, ColumnTrait, QueryFilter, PaginatorTrait, ActiveModelTrait, ActiveValue::Set, IntoActiveModel};
10use serde::{Deserialize, Serialize};
11use util::state::AppState;
12use validator::Validate;
13use chrono::{Utc, Duration};
14use tokio::io::AsyncWriteExt;
15use crate::{
16    auth::generate_jwt,
17    response::ApiResponse,
18    services::email::EmailService,
19};
20use db::models::{
21    user::{self, Model as UserModel},
22    password_reset_token::{self, Model as PasswordResetTokenModel}
23};
24use crate::auth::AuthUser;
25
26#[derive(Debug, Deserialize, Validate)]
27pub struct RegisterRequest {
28    pub username: String,
29
30    #[validate(email(message = "Invalid email format"))]
31    pub email: String,
32
33    #[validate(length(min = 8, message = "Password must be at least 8 characters"))]
34    pub password: String,
35
36    // TODO: Add some more password validation later
37}
38
39#[derive(Debug, Serialize, Default)]
40pub struct UserResponse {
41    pub id: i64,
42    pub username: String,
43    pub email: String,
44    pub admin: bool,
45    pub token: String,
46    pub expires_at: String,
47}
48
49/// POST /api/auth/register
50///
51/// Register a new user.
52///
53/// ### Request Body
54/// ```json
55/// {
56///   "username": "u12345678",
57///   "email": "[email protected]",
58///   "password": "strongpassword"
59/// }
60/// ```
61///
62/// ### Responses
63///
64/// - `201 Created`  
65/// ```json
66/// {
67///   "success": true,
68///   "data": {
69///     "id": 1,
70///     "username": "u12345678",
71///     "email": "[email protected]",
72///     "admin": false,
73///     "token": "jwt_token_here",
74///     "expires_at": "2025-05-23T11:00:00Z"
75///   },
76///   "message": "User registered successfully"
77/// }
78/// ```
79///
80/// - `400 Bad Request` (validation failure)  
81/// ```json
82/// {
83///   "success": false,
84///   "message": "Student number must be in format u12345678"
85/// }
86/// ```
87///
88/// - `409 Conflict` (duplicate)  
89/// ```json
90/// {
91///   "success": false,
92///   "message": "A user with this email already exists"
93/// }
94/// ```
95///
96/// - `500 Internal Server Error`  
97/// ```json
98/// {
99///   "success": false,
100///   "message": "Database error: detailed error here"
101/// }
102/// ```
103pub async fn register(
104    State(app_state): State<AppState>,
105    Json(req): Json<RegisterRequest>
106) -> impl IntoResponse {
107    let db = app_state.db();
108
109    if let Err(validation_errors) = req.validate() {
110        let error_message = common::format_validation_errors(&validation_errors);
111        return (
112            StatusCode::BAD_REQUEST,
113            Json(ApiResponse::<UserResponse>::error(error_message)),
114        );
115    }
116
117    let email_exists = user::Entity::find()
118        .filter(user::Column::Email.eq(req.email.clone()))
119        .one(db)
120        .await
121        .unwrap();
122
123    if email_exists.is_some() {
124        return (
125            StatusCode::CONFLICT,
126            Json(ApiResponse::<UserResponse>::error("A user with this email already exists")),
127        );
128    }
129
130    let sn_exists = user::Entity::find()
131        .filter(user::Column::Username.eq(req.username.clone()))
132        .one(db)
133        .await
134        .unwrap();
135
136    if sn_exists.is_some() {
137        return (
138            StatusCode::CONFLICT,
139            Json(ApiResponse::<UserResponse>::error("A user with this student number already exists")),
140        );
141    }
142
143    let inserted_user = match UserModel::create(
144        &db,
145        &req.username,
146        &req.email,
147        &req.password,
148        false,
149    ).await {
150        Ok(user) => user,
151        Err(e) => {
152            return (
153                StatusCode::INTERNAL_SERVER_ERROR,
154                Json(ApiResponse::<UserResponse>::error(format!("Database error: {}", e))),
155            );
156        }
157    };
158
159    let (token, expiry) = generate_jwt(inserted_user.id, inserted_user.admin);
160    let user_response = UserResponse {
161        id: inserted_user.id,
162        username: inserted_user.username,
163        email: inserted_user.email,
164        admin: inserted_user.admin,
165        token,
166        expires_at: expiry,
167    };
168
169    (
170        StatusCode::CREATED,
171        Json(ApiResponse::success(user_response, "User registered successfully")),
172    )
173}
174
175
176#[derive(Debug, Deserialize, Validate)]
177pub struct LoginRequest {
178    pub username: String,
179    pub password: String,
180}
181
182/// POST /api/auth/login
183///
184/// Authenticate an existing user and issue a JWT.
185///
186/// ### Request Body
187/// ```json
188/// {
189///   "username": "u12345678",
190///   "password": "strongpassword"
191/// }
192/// ```
193///
194/// ### Responses
195///
196/// - `200 OK`  
197/// ```json
198/// {
199///   "success": true,
200///   "data": {
201///     "id": 1,
202///     "username": "u12345678",
203///     "email": "[email protected]",
204///     "admin": false,
205///     "token": "jwt_token_here",
206///     "expires_at": "2025-05-23T12:00:00Z"
207///   },
208///   "message": "Login successful"
209/// }
210/// ```
211///
212/// - `401 Unauthorized` (invalid credentials)  
213/// ```json
214/// {
215///   "success": false,
216///   "message": "Invalid password"
217/// }
218/// ```
219///
220/// - `500 Internal Server Error`  
221/// ```json
222/// {
223///   "success": false,
224///   "message": "Database error: detailed error here"
225/// }
226/// ```
227pub async fn login(
228    State(app_state): State<AppState>,
229    Json(req): Json<LoginRequest>
230) -> impl IntoResponse {
231    let db = app_state.db();
232
233    if let Err(validation_errors) = req.validate() {
234        let error_message = common::format_validation_errors(&validation_errors);
235        return (
236            StatusCode::BAD_REQUEST,
237            Json(ApiResponse::<UserResponse>::error(error_message)),
238        );
239    }
240
241    let user = match UserModel::verify_credentials(db, &req.username, &req.password).await {
242        Ok(Some(u)) => u,
243        Ok(None) => {
244            return (
245                StatusCode::UNAUTHORIZED,
246                Json(ApiResponse::<UserResponse>::error("Invalid student number or password")),
247            );
248        }
249        Err(e) => {
250            return (
251                StatusCode::INTERNAL_SERVER_ERROR,
252                Json(ApiResponse::<UserResponse>::error(format!("Database error: {}", e))),
253            );
254        }
255    };
256
257    let (token, expiry) = generate_jwt(user.id, user.admin);
258    let user_response = UserResponse {
259        id: user.id,
260        username: user.username,
261        email: user.email,
262        admin: user.admin,
263        token,
264        expires_at: expiry,
265    };
266
267    (
268        StatusCode::OK,
269        Json(ApiResponse::success(user_response, "Login successful")),
270    )
271}
272
273#[derive(Debug, Deserialize, Validate)]
274pub struct RequestPasswordResetRequest {
275    #[validate(email(message = "Invalid email format"))]
276    pub email: String,
277}
278
279/// POST /api/auth/request-password-reset
280///
281/// Request a password reset token for a user.
282///
283/// ### Request Body
284/// ```json
285/// {
286///   "email": "[email protected]"
287/// }
288/// ```
289///
290/// ### Responses
291///
292/// - `200 OK`  
293/// ```json
294/// {
295///   "success": true,
296///   "data": null,
297///   "message": "If the account exists, a reset link has been sent."
298/// }
299/// ```
300///
301/// - `400 Bad Request` (validation failure)  
302/// ```json
303/// {
304///   "success": false,
305///   "message": "Invalid email format"
306/// }
307/// ```
308///
309/// - `429 Too Many Requests`  
310/// ```json
311/// {
312///   "success": false,
313///   "message": "Too many password reset requests. Please try again later."
314/// }
315/// ```
316///
317/// - `500 Internal Server Error`  
318/// ```json
319/// {
320///   "success": false,
321///   "message": "Database error: detailed error here"
322/// }
323/// ```
324pub async fn request_password_reset(
325    State(app_state): State<AppState>,
326    Json(req): Json<RequestPasswordResetRequest>
327) -> impl IntoResponse {
328    let db = app_state.db();
329
330    if let Err(validation_errors) = req.validate() {
331        let error_message = common::format_validation_errors(&validation_errors);
332        return (
333            StatusCode::BAD_REQUEST,
334            Json(ApiResponse::<()>::error(error_message)),
335        );
336    }
337
338    let user = match user::Entity::find()
339        .filter(user::Column::Email.eq(req.email.clone()))
340        .one(db)
341        .await
342    {
343        Ok(Some(u)) => u,
344        Ok(None) => {
345            return (
346                StatusCode::OK,
347                Json(ApiResponse::success(
348                    (),
349                    "If the account exists, a reset link has been sent.",
350                )),
351            );
352        }
353        Err(e) => {
354            return (
355                StatusCode::INTERNAL_SERVER_ERROR,
356                Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
357            );
358        }
359    };
360
361    let one_hour_ago = Utc::now() - Duration::hours(1);
362    let recent_requests = password_reset_token::Entity::find()
363        .filter(password_reset_token::Column::UserId.eq(user.id))
364        .filter(password_reset_token::Column::CreatedAt.gt(one_hour_ago))
365        .count(db)
366        .await
367        .unwrap_or(0);
368
369    let max_requests = std::env::var("MAX_PASSWORD_RESET_REQUESTS_PER_HOUR")
370        .unwrap_or_else(|_| "3".to_string())
371        .parse::<u64>()
372        .unwrap_or(3);
373
374    if recent_requests >= max_requests {
375        return (
376            StatusCode::TOO_MANY_REQUESTS,
377            Json(ApiResponse::<()>::error(
378                "Too many password reset requests. Please try again later.",
379            )),
380        );
381    }
382
383    let expiry_minutes = std::env::var("RESET_TOKEN_EXPIRY_MINUTES")
384        .unwrap_or_else(|_| "15".to_string())
385        .parse::<i64>()
386        .unwrap_or(15);
387
388    match PasswordResetTokenModel::create(db, user.id, expiry_minutes).await {
389        Ok(token) => {
390            match EmailService::send_password_reset_email(&user.email, &token.token).await {
391                Ok(_) => (
392                    StatusCode::OK,
393                    Json(ApiResponse::success(
394                        (),
395                        "If the account exists, a reset link has been sent.",
396                    )),
397                ),
398                Err(e) => {
399                    eprintln!("Failed to send password reset email: {}", e);
400                    (
401                        StatusCode::OK,
402                        Json(ApiResponse::success(
403                            (),
404                            "If the account exists, a reset link has been sent.",
405                        )),
406                    )
407                }
408            }
409        }
410        Err(e) => {
411            (
412                StatusCode::INTERNAL_SERVER_ERROR,
413                Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
414            )
415        }
416    }
417}
418
419#[derive(Debug, Deserialize, Validate)]
420pub struct VerifyResetTokenRequest {
421    #[validate(length(min = 1, message = "Token is required"))]
422    pub token: String,
423}
424
425#[derive(Debug, Serialize)]
426pub struct VerifyResetTokenResponse {
427    pub email_hint: String,
428}
429
430/// POST /api/auth/verify-reset-token
431///
432/// Verify the validity of a password reset token.
433///
434/// ### Request Body
435/// ```json
436/// {
437///   "token": "abcdef123456"
438/// }
439/// ```
440///
441/// ### Responses
442///
443/// - `200 OK`  
444/// ```json
445/// {
446///   "success": true,
447///   "data": {
448///     "email_hint": "u***@example.com"
449///   },
450///   "message": "Token verified. You may now reset your password."
451/// }
452/// ```
453///
454/// - `400 Bad Request` (validation failure)  
455/// ```json
456/// {
457///   "success": false,
458///   "message": "Token is required"
459/// }
460/// ```
461///
462/// - `400 Bad Request` (invalid token)  
463/// ```json
464/// {
465///   "success": false,
466///   "message": "Invalid or expired token."
467/// }
468/// ```
469pub async fn verify_reset_token(
470    State(app_state): State<AppState>,
471    Json(req): Json<VerifyResetTokenRequest>
472) -> impl IntoResponse {
473    let db = app_state.db();
474
475    if let Err(validation_errors) = req.validate() {
476        let error_message = common::format_validation_errors(&validation_errors);
477        return (
478            StatusCode::BAD_REQUEST,
479            Json(ApiResponse::<VerifyResetTokenResponse>::error(error_message)),
480        );
481    }
482
483    match PasswordResetTokenModel::find_valid_token(db, &req.token).await {
484        Ok(Some(token)) => {
485            match user::Entity::find_by_id(token.user_id).one(db).await {
486                Ok(Some(user)) => {
487                    let email_parts: Vec<&str> = user.email.split('@').collect();
488                    let username = email_parts[0];
489                    let domain = email_parts[1];
490                    let masked_username = format!("{}***", &username[0..1]);
491                    let email_hint = format!("{}@{}", masked_username, domain);
492
493                    let response = VerifyResetTokenResponse { email_hint };
494                    (
495                        StatusCode::OK,
496                        Json(ApiResponse::success(
497                            response,
498                            "Token verified. You may now reset your password.",
499                        )),
500                    )
501                }
502                Ok(None) => (
503                    StatusCode::BAD_REQUEST,
504                    Json(ApiResponse::<VerifyResetTokenResponse>::error("Invalid or expired token.")),
505                ),
506                Err(e) => (
507                    StatusCode::INTERNAL_SERVER_ERROR,
508                    Json(ApiResponse::<VerifyResetTokenResponse>::error(format!("Database error: {}", e))),
509                ),
510            }
511        }
512        Ok(None) => (
513            StatusCode::BAD_REQUEST,
514            Json(ApiResponse::<VerifyResetTokenResponse>::error("Invalid or expired token.")),
515        ),
516        Err(e) => (
517            StatusCode::INTERNAL_SERVER_ERROR,
518            Json(ApiResponse::<VerifyResetTokenResponse>::error(format!("Database error: {}", e))),
519        ),
520    }
521}
522
523#[derive(Debug, Deserialize, Validate)]
524pub struct ResetPasswordRequest {
525    #[validate(length(min = 1, message = "Token is required"))]
526    pub token: String,
527
528    #[validate(length(min = 8, message = "Password must be at least 8 characters"))]
529    pub new_password: String,
530}
531
532/// POST /api/auth/reset-password
533///
534/// Reset a user's password using a valid reset token.
535///
536/// ### Request Body
537/// ```json
538/// {
539///   "token": "abcdef123456",
540///   "new_password": "SecureP@ssw0rd!"
541/// }
542/// ```
543///
544/// ### Responses
545///
546/// - `200 OK`  
547/// ```json
548/// {
549///   "success": true,
550///   "data": null,
551///   "message": "Password has been reset successfully."
552/// }
553/// ```
554///
555/// - `400 Bad Request` (validation failure)  
556/// ```json
557/// {
558///   "success": false,
559///   "message": "Password must be at least 8 characters"
560/// }
561/// ```
562///
563/// - `400 Bad Request` (invalid token)  
564/// ```json
565/// {
566///   "success": false,
567///   "message": "Reset failed. The token may be invalid or expired."
568/// }
569/// ```
570pub async fn reset_password(
571    State(app_state): State<AppState>,
572    Json(req): Json<ResetPasswordRequest>
573) -> impl IntoResponse {
574    let db = app_state.db();
575
576    if let Err(validation_errors) = req.validate() {
577        let error_message = common::format_validation_errors(&validation_errors);
578        return (
579            StatusCode::BAD_REQUEST,
580            Json(ApiResponse::<()>::error(error_message)),
581        );
582    }
583
584    match PasswordResetTokenModel::find_valid_token(db, &req.token).await {
585        Ok(Some(token)) => {
586            match user::Entity::find_by_id(token.user_id).one(db).await {
587                Ok(Some(user)) => {
588                    let user_email = user.email.clone();
589                    
590                    let mut active_model: user::ActiveModel = user.into();
591                    active_model.password_hash = Set(UserModel::hash_password(&req.new_password));
592                    
593                    match active_model.update(db).await {
594                        Ok(_) => {
595                            if let Err(e) = token.mark_as_used(db).await {
596                                eprintln!("Failed to mark token as used: {}", e);
597                            }
598
599                            if let Err(e) = EmailService::send_password_changed_email(&user_email).await {
600                                eprintln!("Failed to send password change confirmation email: {}", e);
601                            }
602
603                            (
604                                StatusCode::OK,
605                                Json(ApiResponse::success(
606                                    (),
607                                    "Password has been reset successfully.",
608                                )),
609                            )
610                        }
611                        Err(e) => (
612                            StatusCode::INTERNAL_SERVER_ERROR,
613                            Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
614                        ),
615                    }
616                }
617                Ok(None) => (
618                    StatusCode::BAD_REQUEST,
619                    Json(ApiResponse::<()>::error("Reset failed. The token may be invalid or expired.")),
620                ),
621                Err(e) => (
622                    StatusCode::INTERNAL_SERVER_ERROR,
623                    Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
624                ),
625            }
626        }
627        Ok(None) => (
628            StatusCode::BAD_REQUEST,
629            Json(ApiResponse::<()>::error("Reset failed. The token may be invalid or expired.")),
630        ),
631        Err(e) => (
632            StatusCode::INTERNAL_SERVER_ERROR,
633            Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
634        ),
635    }
636}
637
638#[derive(serde::Serialize)]
639struct ProfilePictureResponse {
640    profile_picture_path: String,
641}
642
643/// POST /api/auth/upload-profile-picture
644///
645/// Upload a profile picture for the authenticated user.
646///
647/// This endpoint accepts a `multipart/form-data` request containing a single file field named `file`.
648/// Only JPEG, PNG, and GIF images are allowed, and the file size must not exceed 2MB.
649/// The uploaded file is stored in a user-specific directory, and the user's profile in the database
650/// is updated with the relative path to the profile picture.
651///
652/// # Request (multipart/form-data)
653/// - `file`: The image file to upload (JPEG, PNG, or GIF, max 2MB)
654///
655/// # Responses
656///
657/// - `200 OK`  
658///   ```json
659///   {
660///     "success": true,
661///     "data": {
662///       "profile_picture_path": "user_1/avatar.jpg"
663///     },
664///     "message": "Profile picture uploaded."
665///   }
666///   ```
667///
668/// - `400 Bad Request` (invalid file type, too large, or missing file)  
669///   ```json
670///   {
671///     "success": false,
672///     "message": "File type not supported."
673///   }
674///   ```
675///   or
676///   ```json
677///   {
678///     "success": false,
679///     "message": "File too large."
680///   }
681///   ```
682///   or
683///   ```json
684///   {
685///     "success": false,
686///     "message": "No file uploaded."
687///   }
688///   ```
689///
690/// - `500 Internal Server Error`  
691///   ```json
692///   {
693///     "success": false,
694///     "message": "Database error: detailed error here"
695///   }
696///   ```
697pub async fn upload_profile_picture(
698    State(app_state): State<AppState>,
699    AuthUser(claims): AuthUser,
700    mut multipart: Multipart,
701) -> impl IntoResponse {
702    let db = app_state.db();
703
704    const MAX_SIZE: u64 = 2 * 1024 * 1024;
705    const ALLOWED_MIME: &[&str] = &["image/jpeg", "image/png", "image/gif"];
706
707    let mut content_type = None;
708    let mut file_data = None;
709
710    while let Some(field) = multipart.next_field().await.unwrap_or(None) {
711        if field.name() == Some("file") {
712            content_type = field.content_type().map(|ct| ct.to_string());
713
714            if let Some(ct) = &content_type {
715                if !ALLOWED_MIME.contains(&ct.as_str()) {
716                    return (
717                        StatusCode::BAD_REQUEST,
718                        Json(ApiResponse::<ProfilePictureResponse>::error("File type not supported.")),
719                    );
720                }
721            }
722
723            let bytes = field.bytes().await.unwrap();
724            if bytes.len() as u64 > MAX_SIZE {
725                return (
726                    StatusCode::BAD_REQUEST,
727                    Json(ApiResponse::<ProfilePictureResponse>::error("File too large.")),
728                );
729            }
730
731            file_data = Some(bytes);
732        }
733    }
734
735    let Some(file_bytes) = file_data else {
736        return (
737            StatusCode::BAD_REQUEST,
738            Json(ApiResponse::<ProfilePictureResponse>::error("No file uploaded.")),
739        );
740    };
741
742    let ext = match content_type.as_deref() {
743        Some("image/png") => "png",
744        Some("image/jpeg") => "jpg",
745        Some("image/gif") => "gif",
746        _ => "bin",
747    };
748
749    let root = std::env::var("USER_PROFILE_STORAGE_ROOT")
750        .unwrap_or_else(|_| "data/user_profile_pictures".to_string());
751
752    let user_dir = PathBuf::from(&root).join(format!("user_{}", claims.sub));
753    let _ = fs::create_dir_all(&user_dir);
754
755    let filename = format!("avatar.{}", ext);
756    let path = user_dir.join(&filename);
757    let mut file = tokio::fs::File::create(&path).await.unwrap();
758    file.write_all(&file_bytes).await.unwrap();
759
760    let relative_path = path
761        .strip_prefix(&root)
762        .unwrap()
763        .to_string_lossy()
764        .to_string();
765
766    let current = user::Entity::find_by_id(claims.sub).one(db).await.unwrap().unwrap();
767    let mut model = current.into_active_model();
768    model.profile_picture_path = Set(Some(relative_path.clone()));
769    model.update(db).await.unwrap();
770
771    let response = ProfilePictureResponse {
772        profile_picture_path: relative_path,
773    };
774
775   (
776        StatusCode::OK,
777        Json(ApiResponse::success(response, "Profile picture uploaded.")),
778    )
779}
780
781#[derive(Debug, Deserialize, Validate)]
782pub struct ChangePasswordRequest {
783    #[validate(length(min = 1, message = "Current password is required"))]
784    pub current_password: String,
785
786    #[validate(length(min = 8, message = "Password must be at least 8 characters"))]
787    pub new_password: String,
788}
789
790/// POST /api/auth/change_password
791///
792/// Change the password for an authenticated user.
793///
794/// ### Request Body
795/// ```json
796/// {
797///   "current_password": "OldPassword123",
798///   "new_password": "NewSecurePassword456"
799/// }
800/// ```
801///
802/// ### Responses
803///
804/// - `200 OK`  
805/// ```json
806/// {
807///   "success": true,
808///   "data": null,
809///   "message": "Password changed successfully."
810/// }
811/// ```
812///
813/// - `400 Bad Request` (validation failure)  
814/// ```json
815/// {
816///   "success": false,
817///   "message": "Password must be at least 8 characters"
818/// }
819/// ```
820///
821/// - `401 Unauthorized` (invalid current password)  
822/// ```json
823/// {
824///   "success": false,
825///   "message": "Current password is incorrect"
826/// }
827/// ```
828///
829/// - `401 Unauthorized` (not authenticated)  
830/// ```json
831/// {
832///   "success": false,
833///   "message": "Authentication required"
834/// }
835/// ```
836pub async fn change_password(
837    State(app_state): State<AppState>,
838    AuthUser(claims): AuthUser,
839    Json(req): Json<ChangePasswordRequest>,
840) -> impl IntoResponse {
841    let db = app_state.db();
842
843    if let Err(validation_errors) = req.validate() {
844        let error_message = common::format_validation_errors(&validation_errors);
845        return (
846            StatusCode::BAD_REQUEST,
847            Json(ApiResponse::<()>::error(error_message)),
848        );
849    }
850
851    let user = match user::Entity::find_by_id(claims.sub).one(db).await {
852        Ok(Some(user)) => user,
853        Ok(None) => {
854            return (
855                StatusCode::UNAUTHORIZED,
856                Json(ApiResponse::<()>::error("Authentication required")),
857            );
858        }
859        Err(e) => {
860            return (
861                StatusCode::INTERNAL_SERVER_ERROR,
862                Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
863            );
864        }
865    };
866
867    if !user.verify_password(&req.current_password) {
868        return (
869            StatusCode::UNAUTHORIZED,
870            Json(ApiResponse::<()>::error("Current password is incorrect")),
871        );
872    }
873
874    let mut active_user = user.into_active_model();
875    active_user.password_hash = Set(UserModel::hash_password(&req.new_password));
876    
877    match active_user.update(db).await {
878        Ok(_) => (
879            StatusCode::OK,
880            Json(ApiResponse::success((), "Password changed successfully.")),
881        ),
882        Err(e) => (
883            StatusCode::INTERNAL_SERVER_ERROR,
884            Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
885        ),
886    }
887}