From 3ab168fd67f8bd912a70b4a56df4ee5ad870567d Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Wed, 7 May 2025 01:12:51 +0200 Subject: [PATCH] Do not update user email if there is an error when sending the validation link to the new email --- Cargo.lock | 12 ++--- backend/scss/constants.scss | 4 ++ backend/scss/main.scss | 50 +++--------------- backend/src/data/db/user.rs | 62 +++++++++++++++++++---- backend/src/services/user.rs | 29 +++++++++-- backend/templates/ask_reset_password.html | 2 +- backend/templates/reset_password.html | 9 ++-- backend/translations/english.ron | 1 + backend/translations/french.ron | 1 + common/src/translation.rs | 1 + 10 files changed, 103 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9044875..f0e7b63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2586,9 +2586,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "log", "once_cell", @@ -2607,9 +2607,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "ring", "rustls-pki-types", @@ -3368,9 +3368,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", diff --git a/backend/scss/constants.scss b/backend/scss/constants.scss index 04dd159..28b84b7 100644 --- a/backend/scss/constants.scss +++ b/backend/scss/constants.scss @@ -5,15 +5,19 @@ $dark-theme: false !default; $color-1: #B29B89; $color-2: #89B29B; $color-3: #9B89B2; +$color-highlight: #cf2d2dff; $text-color: color.adjust($color-1, $lightness: -30%); $text-highlight: color.adjust($color-1, $lightness: +30%); + $link-color: color.adjust($color-3, $lightness: -25%); $link-hover-color: color.adjust($color-3, $lightness: +20%); @if $dark-theme { $text-color: color.adjust($color-1, $lightness: -10%); $text-highlight: color.adjust($color-1, $lightness: +10%); + $color-highlight: color.adjust($color-highlight, $lightness: +10%); + $link-color: color.adjust($color-3, $lightness: -5%); $link-hover-color: color.adjust($color-3, $lightness: +10%); diff --git a/backend/scss/main.scss b/backend/scss/main.scss index 6f32c6e..db60a61 100644 --- a/backend/scss/main.scss +++ b/backend/scss/main.scss @@ -28,6 +28,7 @@ body { .user-message { font-weight: bold; + color: consts.$color-highlight; } .drag-handle { @@ -286,7 +287,7 @@ body { } } - #user-edit form { + #reset-password form { grid-template-columns: auto 1fr auto; input[type="submit"] { @@ -294,48 +295,13 @@ body { } } - // #sign-in { + #user-edit form { + grid-template-columns: auto 1fr auto; - // } - - // #user-edit { - // .label-name { - // grid-column: 1; - // grid-row: 1; - // } - - // .input-name { - // grid-column: 2; - // grid-row: 1; - // } - - // .label-password-1 { - // grid-column: 1; - // grid-row: 2; - // } - - // .input-password-1 { - // grid-column: 2; - // grid-row: 2; - // } - - // .label-password-2 { - // grid-column: 1; - // grid-row: 3; - // } - - // .input-password-2 { - // grid-column: 2; - // grid-row: 3; - // } - - // .button-save { - // grid-column: 2; - // grid-row: 4; - // width: fit-content; - // justify-self: flex-end; - // } - //} + input[type="submit"] { + grid-column: 2 + } + } } } diff --git a/backend/src/data/db/user.rs b/backend/src/data/db/user.rs index 10041ff..316636a 100644 --- a/backend/src/data/db/user.rs +++ b/backend/src/data/db/user.rs @@ -8,6 +8,7 @@ use crate::{ consts, data::model, hash::{hash, verify_password}, + services::user, }; #[derive(Debug, Display)] @@ -20,8 +21,8 @@ pub enum SignUpResult { #[derive(Debug, Display)] pub enum UpdateUserResult { EmailAlreadyTaken, - /// Validation token. - UserUpdatedWaitingForRevalidation(String), + /// (New validation token, old email, old validation token, old validation time). + UserUpdatedWaitingForRevalidation(String, String, Option, String), Ok, } @@ -105,11 +106,22 @@ FROM [UserLoginToken] WHERE [token] = $1 let mut tx = self.tx().await?; let hashed_new_password = new_password.map(|p| hash(p).unwrap()); - let (email, name, default_servings, first_day_of_the_week, hashed_password) = sqlx::query_as::< - _, - (String, String, u32, u8, String), - >( - "SELECT [email], [name], [default_servings], [first_day_of_the_week], [password] FROM [User] WHERE [id] = $1", + let ( + email, + name, + default_servings, + first_day_of_the_week, + hashed_password, + validation_token, + validation_token_datetime, + ) = sqlx::query_as::<_, (String, String, u32, u8, String, Option, String)>( + r#" +SELECT + [email], [name], [default_servings], + [first_day_of_the_week], [password], + [validation_token], [validation_token_datetime] +FROM [User] WHERE [id] = $1 + "#, ) .bind(user_id) .fetch_one(&mut *tx) @@ -119,7 +131,7 @@ FROM [UserLoginToken] WHERE [token] = $1 let email_changed = new_email.is_some_and(|new_email| new_email != email); // Check if email not already taken. - let validation_token = if email_changed { + let new_validation_token = if email_changed { if sqlx::query_scalar( r#" SELECT COUNT(*) > 0 @@ -170,13 +182,41 @@ WHERE [id] = $1 tx.commit().await?; - Ok(if let Some(validation_token) = validation_token { - UpdateUserResult::UserUpdatedWaitingForRevalidation(validation_token) + Ok(if let Some(new_validation_token) = new_validation_token { + UpdateUserResult::UserUpdatedWaitingForRevalidation( + new_validation_token, + email, + validation_token, + validation_token_datetime, + ) } else { UpdateUserResult::Ok }) } + pub async fn update_user_email_and_token( + &self, + user_id: i64, + email: &str, + validation_token: Option<&str>, + validation_token_datetime: &str, + ) -> Result<()> { + sqlx::query( + r#" +UPDATE [User] +SET [email] = $2, [validation_token] = $3, [validation_token_datetime] = $4 +WHERE [id] = $1"#, + ) + .bind(user_id) + .bind(email) + .bind(validation_token) + .bind(validation_token_datetime) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) + } + pub async fn set_user_lang(&self, user_id: i64, lang: &str) -> Result<()> { sqlx::query("UPDATE [User] SET [lang] = $2 WHERE [id] = $1") .bind(user_id) @@ -975,7 +1015,7 @@ VALUES assert_eq!(user.name, "paul"); assert_eq!(user.email, "paul@atreides.com"); - if let UpdateUserResult::UserUpdatedWaitingForRevalidation(token) = connection + if let UpdateUserResult::UserUpdatedWaitingForRevalidation(token, _, _, _) = connection .update_user( 1, Some("muaddib@fremen.com"), diff --git a/backend/src/services/user.rs b/backend/src/services/user.rs index 25c9472..b8055cf 100644 --- a/backend/src/services/user.rs +++ b/backend/src/services/user.rs @@ -474,9 +474,6 @@ pub async fn ask_reset_password_post( email, message_email: match error { AskResetPasswordError::InvalidEmail => context.tr.t(Sentence::InvalidEmail), - _ => "", - }, - message: match error { AskResetPasswordError::EmailAlreadyReset => { context.tr.t(Sentence::AskResetEmailAlreadyResetError) } @@ -484,6 +481,9 @@ pub async fn ask_reset_password_post( AskResetPasswordError::UnableSendEmail(_) => { context.tr.t(Sentence::UnableToSendResetEmail) } + _ => "", + }, + message: match error { AskResetPasswordError::DatabaseError(_) => { context.tr.t(Sentence::DatabaseError) } @@ -853,7 +853,12 @@ pub async fn edit_user_post( Ok(db::user::UpdateUserResult::EmailAlreadyTaken) => { return error_response(ProfileUpdateError::EmailAlreadyTaken, &form_data, context); } - Ok(db::user::UpdateUserResult::UserUpdatedWaitingForRevalidation(token)) => { + Ok(db::user::UpdateUserResult::UserUpdatedWaitingForRevalidation( + token, + old_email, + old_token, + old_token_datetime, + )) => { let url = utils::get_url_from_host(&host); let email = form_data.email.clone(); match email_service @@ -874,6 +879,22 @@ pub async fn edit_user_post( message = context.tr.t(Sentence::ProfileEmailSent); } Err(error) => { + // If the email can't be set we revert the changes about email and token. + if let Err(error) = connection + .update_user_email_and_token( + user.id, + &old_email, + old_token.as_deref(), + &old_token_datetime, + ) + .await + { + error!( + "Unable to set email and token: (email={}): {}", + email, error + ); + } + return error_response( ProfileUpdateError::UnableToSendEmail(error), &form_data, diff --git a/backend/templates/ask_reset_password.html b/backend/templates/ask_reset_password.html index bfd951d..e22bf4b 100644 --- a/backend/templates/ask_reset_password.html +++ b/backend/templates/ask_reset_password.html @@ -8,7 +8,7 @@ - {{ message_email }} + {{ message_email }} diff --git a/backend/templates/reset_password.html b/backend/templates/reset_password.html index 002744b..b50814a 100644 --- a/backend/templates/reset_password.html +++ b/backend/templates/reset_password.html @@ -6,17 +6,18 @@
+ - - {{ message_password }} + {{ message_password }} - +
- {{ message }} + + {{ message }} {% endblock %} diff --git a/backend/translations/english.ron b/backend/translations/english.ron index 89b5313..9bbc1ac 100644 --- a/backend/translations/english.ron +++ b/backend/translations/english.ron @@ -63,6 +63,7 @@ (AskResetEmailSent, "An email has been sent, follow the link to reset your password"), (AskResetTokenMissing, "Reset token missing"), (AskResetTokenExpired, "Token expired, try to reset password again"), + (AskResetSubmit, "Reset password"), (PasswordReset, "Your password has been reset"), (EmailUnknown, "Email unknown"), (UnableToSendResetEmail, "Unable to send the reset password email"), diff --git a/backend/translations/french.ron b/backend/translations/french.ron index a12dc62..9ec817b 100644 --- a/backend/translations/french.ron +++ b/backend/translations/french.ron @@ -63,6 +63,7 @@ (AskResetEmailSent, "Un email a été envoyé, suivez le lien pour réinitialiser votre mot de passe"), (AskResetTokenMissing, "Jeton de réinitialisation manquant"), (AskResetTokenExpired, "Jeton expiré, essayez de réinitialiser votre mot de passe à nouveau"), + (AskResetSubmit, "Réinitialiser le mot de passe"), (PasswordReset, "Votre mot de passe a été réinitialisé"), (EmailUnknown, "Email inconnu"), (UnableToSendResetEmail, "Impossible d'envoyer l'email pour la réinitialisation du mot de passe"), diff --git a/common/src/translation.rs b/common/src/translation.rs index 25a11b8..2be2366 100644 --- a/common/src/translation.rs +++ b/common/src/translation.rs @@ -68,6 +68,7 @@ pub enum Sentence { AskResetEmailSent, AskResetTokenMissing, AskResetTokenExpired, + AskResetSubmit, PasswordReset, EmailUnknown, UnableToSendResetEmail,