Răsfoiți Sursa

Create route to reset user password.

Lee Morgan 2 luni în urmă
părinte
comite
51cbe9477d

+ 2 - 30
src/controllers/user/create.rs

@@ -4,17 +4,10 @@ use serde_json::json;
 use sqlx::PgPool;
 use email_address::EmailAddress;
 use chrono::{DateTime, Utc, Duration};
-use argon2::{
-    Argon2,
-    password_hash::{
-        SaltString,
-        PasswordHasher,
-        rand_core::OsRng
-    }
-};
 use crate::{
     app_error::AppError,
-    models::user::User
+    models::user::User,
+    logic::{valid_password, hash_password}
 };
 
 #[derive(Deserialize)]
@@ -66,18 +59,6 @@ fn is_bot(url: &Option<String>, time: &Option<String>) -> bool {
     age > Duration::minutes(10)
 }
 
-fn valid_password(pass: &String, confirm_pass: &String) -> Result<(), AppError> {
-    if *pass != *confirm_pass {
-        return Err(AppError::invalid_input("Passwords do not match"));
-    }
-
-    if pass.chars().count() < 12 {
-        return Err(AppError::invalid_input("Password must contain at least 12 characters"));
-    }
-
-    Ok(())
-}
-
 fn validate_email(email: &String) -> Result<String, AppError> {
     if !EmailAddress::is_valid(email) {
         return Err(AppError::invalid_input("Invalid email address"));
@@ -85,12 +66,3 @@ fn validate_email(email: &String) -> Result<String, AppError> {
 
     Ok(email.to_lowercase())
 }
-
-fn hash_password(pass: &String) -> Result<String, AppError> {
-    let salt = SaltString::generate(&mut OsRng);
-    let argon2 = Argon2::default();
-    match argon2.hash_password(pass.as_bytes(), &salt) {
-        Ok(h) => Ok(h.to_string()),
-        Err(e) => Err(AppError::InternalError(e.to_string()))
-    }
-}

+ 1 - 0
src/controllers/user/mod.rs

@@ -2,3 +2,4 @@ pub mod create;
 pub mod login;
 pub mod get_one;
 pub mod logout;
+pub mod reset_password;

+ 31 - 0
src/controllers/user/reset_password.rs

@@ -0,0 +1,31 @@
+use actix_web::{HttpResponse, web, patch};
+use sqlx::PgPool;
+use uuid::Uuid;
+use serde_json::json;
+use serde::Deserialize;
+use crate::{
+    app_error::AppError,
+    models::user::User,
+    logic::{valid_password, hash_password}
+};
+
+#[derive(Deserialize)]
+struct Body {
+    user_id: String,
+    token: String,
+    password: String,
+    confirm_password: String
+}
+
+#[patch("/user/password/reset")]
+pub async fn route(
+    db: web::Data<PgPool>,
+    body: web::Json<Body>
+) -> Result<HttpResponse, AppError> {
+    let user_id = Uuid::parse_str(&body.user_id)?;
+    let token = Uuid::parse_str(&body.token)?;
+    valid_password(&body.password, &body.confirm_password)?;
+    let pass_hash = hash_password(&body.password)?;
+    User::reset_password(&db, user_id, token, pass_hash).await?;
+    Ok(HttpResponse::Ok().json(json!({"success": true})))
+}

+ 18 - 0
src/logic/hash_password.rs

@@ -0,0 +1,18 @@
+use argon2::{
+    Argon2,
+    password_hash::{
+        SaltString,
+        PasswordHasher,
+        rand_core::OsRng
+    }
+};
+use crate::app_error::AppError;
+
+pub fn hash_password(pass: &String) -> Result<String, AppError> {
+    let salt = SaltString::generate(&mut OsRng);
+    let argon2 = Argon2::default();
+    match argon2.hash_password(pass.as_bytes(), &salt) {
+        Ok(h) => Ok(h.to_string()),
+        Err(e) => Err(AppError::InternalError(e.to_string()))
+    }
+}

+ 4 - 0
src/logic/mod.rs

@@ -1,7 +1,11 @@
 pub mod create_cookie;
 pub mod remove_cookie;
 pub mod compare_password;
+pub mod valid_password;
+pub mod hash_password;
 
 pub use create_cookie::create_cookie;
 pub use remove_cookie::remove_cookie;
 pub use compare_password::compare_password;
+pub use valid_password::valid_password;
+pub use hash_password::hash_password;

+ 13 - 0
src/logic/valid_password.rs

@@ -0,0 +1,13 @@
+use crate::app_error::AppError;
+
+pub fn valid_password(pass: &String, confirm_pass: &String) -> Result<(), AppError> {
+    if *pass != *confirm_pass {
+        return Err(AppError::invalid_input("Passwords do not match"));
+    }
+
+    if pass.chars().count() < 12 {
+        return Err(AppError::invalid_input("Password must contain at least 12 characters"));
+    }
+
+    Ok(())
+}

+ 25 - 0
src/models/user.rs

@@ -96,6 +96,31 @@ impl User {
         }
     }
 
+    pub async fn reset_password(
+        db: &PgPool,
+        user_id: Uuid,
+        token: Uuid,
+        pass_hash: String
+    ) -> Result<(), AppError> {
+        let result = sqlx::query(
+            "UPDATE users
+            SET pass_hash = $1
+            WHERE id = $2
+                AND token = $3"
+        )
+            .bind(pass_hash)
+            .bind(user_id)
+            .bind(token)
+            .execute(db)
+            .await?;
+
+        if result.rows_affected() == 0 {
+            return Err(AppError::forbidden("Invalid authorization"));
+        }
+
+        Ok(())
+    }
+
     pub fn response_user(self) -> ResponseUser {
         ResponseUser {
             id: self.id.to_string(),

+ 3 - 1
src/routes/user.rs

@@ -3,7 +3,8 @@ use crate::controllers::user::{
     create,
     login,
     get_one,
-    logout
+    logout,
+    reset_password
 };
 
 pub fn config(cfg: &mut web::ServiceConfig) {
@@ -11,4 +12,5 @@ pub fn config(cfg: &mut web::ServiceConfig) {
     cfg.service(login::route);
     cfg.service(get_one::route);
     cfg.service(logout::route);
+    cfg.service(reset_password::route);
 }