Do not update user email if there is an error when sending the validation link to the new email

This commit is contained in:
Greg Burri 2025-05-07 01:12:51 +02:00
parent 45f4e2d169
commit 3ab168fd67
10 changed files with 103 additions and 68 deletions

View file

@ -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>, 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>, 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"),

View file

@ -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,