Tell the user if the reset password token is expired.
authorGreg Burri <greg.burri@gmail.com>
Sat, 9 Nov 2024 17:58:39 +0000 (18:58 +0100)
committerGreg Burri <greg.burri@gmail.com>
Sat, 9 Nov 2024 17:58:39 +0000 (18:58 +0100)
Cargo.lock
backend/src/data/db.rs
backend/src/services.rs
backend/templates/reset_password.html

index 2e05eed..0a841ab 100644 (file)
@@ -40,9 +40,9 @@ dependencies = [
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f"
 
 [[package]]
 name = "android-tzdata"
@@ -381,9 +381,9 @@ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
 
 [[package]]
 name = "cc"
-version = "1.1.36"
+version = "1.1.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70"
+checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
 dependencies = [
  "shlex",
 ]
@@ -737,9 +737,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
 
 [[package]]
 name = "flume"
@@ -2351,9 +2351,9 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.13.0"
+version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
 dependencies = [
  "cfg-if",
  "fastrand",
index 6694f03..3e6b291 100644 (file)
@@ -74,12 +74,18 @@ pub enum AuthenticationResult {
 }
 
 #[derive(Debug)]
-pub enum GetTokenResetPassword {
+pub enum GetTokenResetPasswordResult {
     PasswordAlreadyReset,
     EmailUnknown,
     Ok(String),
 }
 
+#[derive(Debug)]
+pub enum ResetPasswordResult {
+    ResetTokenExpired,
+    Ok,
+}
+
 #[derive(Clone)]
 pub struct Connection {
     pool: Pool<Sqlite>,
@@ -440,7 +446,7 @@ WHERE [id] = $1
         &self,
         email: &str,
         validation_time: Duration,
-    ) -> Result<GetTokenResetPassword> {
+    ) -> Result<GetTokenResetPasswordResult> {
         let mut tx = self.tx().await?;
 
         if let Some(db_datetime_nullable) = sqlx::query_scalar::<_, Option<DateTime<Utc>>>(
@@ -456,11 +462,11 @@ WHERE [email] = $1
         {
             if let Some(db_datetime) = db_datetime_nullable {
                 if Utc::now() - db_datetime <= validation_time {
-                    return Ok(GetTokenResetPassword::PasswordAlreadyReset);
+                    return Ok(GetTokenResetPasswordResult::PasswordAlreadyReset);
                 }
             }
         } else {
-            return Ok(GetTokenResetPassword::EmailUnknown);
+            return Ok(GetTokenResetPasswordResult::EmailUnknown);
         }
 
         let token = generate_token();
@@ -480,7 +486,7 @@ WHERE [email] = $1
 
         tx.commit().await?;
 
-        Ok(GetTokenResetPassword::Ok(token))
+        Ok(GetTokenResetPasswordResult::Ok(token))
     }
 
     pub async fn reset_password(
@@ -488,7 +494,7 @@ WHERE [email] = $1
         new_password: &str,
         token: &str,
         validation_time: Duration,
-    ) -> Result<()> {
+    ) -> Result<ResetPasswordResult> {
         let mut tx = self.tx().await?;
         // There is no index on [password_reset_token]. Is it useful?
         if let (user_id, Some(db_datetime)) = sqlx::query_as::<_, (i64, Option<DateTime<Utc>>)>(
@@ -503,9 +509,7 @@ WHERE [password_reset_token] = $1
         .await?
         {
             if Utc::now() - db_datetime > validation_time {
-                return Err(DBError::Other(
-                    "Can't reset password: validation time exceeded".to_string(),
-                ));
+                return Ok(ResetPasswordResult::ResetTokenExpired);
             }
 
             // Remove all login tokens (for security reasons).
@@ -530,7 +534,7 @@ WHERE [id] = $1
 
             tx.commit().await?;
 
-            Ok(())
+            Ok(ResetPasswordResult::Ok)
         } else {
             Err(DBError::Other(
                 "Can't reset password: stored token or datetime not set (NULL)".to_string(),
@@ -983,7 +987,7 @@ VALUES (
             .get_token_reset_password(email, Duration::hours(1))
             .await?
         {
-            GetTokenResetPassword::EmailUnknown => Ok(()), // Nominal case.
+            GetTokenResetPasswordResult::EmailUnknown => Ok(()), // Nominal case.
             other => panic!("{:?}", other),
         }
     }
@@ -1031,7 +1035,7 @@ VALUES (
             .get_token_reset_password(email, Duration::hours(1))
             .await?
         {
-            GetTokenResetPassword::Ok(token) => token,
+            GetTokenResetPasswordResult::Ok(token) => token,
             other => panic!("{:?}", other),
         };
 
index 8d4f41f..243dfe5 100644 (file)
@@ -574,15 +574,15 @@ pub async fn ask_reset_password_post(
         )
         .await
     {
-        Ok(db::GetTokenResetPassword::PasswordAlreadyReset) => error_response(
+        Ok(db::GetTokenResetPasswordResult::PasswordAlreadyReset) => error_response(
             AskResetPasswordError::EmailAlreadyReset,
             &form_data.email,
             user,
         ),
-        Ok(db::GetTokenResetPassword::EmailUnknown) => {
+        Ok(db::GetTokenResetPasswordResult::EmailUnknown) => {
             error_response(AskResetPasswordError::EmailUnknown, &form_data.email, user)
         }
-        Ok(db::GetTokenResetPassword::Ok(token)) => {
+        Ok(db::GetTokenResetPasswordResult::Ok(token)) => {
             let url = utils::get_url_from_host(&host);
             match email::send_email(
                 &form_data.email,
@@ -663,6 +663,7 @@ pub struct ResetPasswordForm {
 enum ResetPasswordError {
     PasswordsNotEqual,
     InvalidPassword,
+    TokenExpired,
     DatabaseError,
 }
 
@@ -691,6 +692,7 @@ pub async fn reset_password_post(
             }
             .to_string(),
             message: match error {
+                ResetPasswordError::TokenExpired => "Token expired, try to reset password again",
                 ResetPasswordError::DatabaseError => "Database error",
                 _ => "",
             }
@@ -717,11 +719,14 @@ pub async fn reset_password_post(
         )
         .await
     {
-        Ok(_) => Ok(MessageTemplate {
+        Ok(db::ResetPasswordResult::Ok) => Ok(MessageTemplate {
             user,
             message: "Your password has been reset",
         }
         .into_response()),
+        Ok(db::ResetPasswordResult::ResetTokenExpired) => {
+            error_response(ResetPasswordError::TokenExpired, &form_data, user)
+        }
         Err(_) => error_response(ResetPasswordError::DatabaseError, &form_data, user),
     }
 }
index e1e8429..a0663fe 100644 (file)
@@ -8,7 +8,7 @@
 
             <label for="password_field_1">Re-enter password</label>
             <input id="password_field_2" type="password" name="password_2" />
-
+            
             {{ message_password }}
 
             <input type="hidden" name="reset_token" value="{{ reset_token }}" />