Do not update user email if there is an error when sending the validation link to the new email
This commit is contained in:
parent
45f4e2d169
commit
3ab168fd67
10 changed files with 103 additions and 68 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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%);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<input id="email_field" type="email"
|
||||
name="email" value="{{ email }}"
|
||||
autocapitalize="none" autocomplete="email" autofocus="autofocus">
|
||||
<span class="user-message">{{ message_email }}</span>
|
||||
<span class="user-message ">{{ message_email }}</span>
|
||||
|
||||
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::AskResetButton) }}">
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -6,17 +6,18 @@
|
|||
<form action="/reset_password" method="post">
|
||||
<label for="password_field_1">{{ context.tr.tp(Sentence::AskResetChooseNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
||||
<input id="password_field_1" type="password" name="password_1">
|
||||
<span></span>
|
||||
|
||||
<label for="password_field_1">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<input id="password_field_2" type="password" name="password_2">
|
||||
|
||||
{{ message_password }}
|
||||
<span class="user-message">{{ message_password }}</span>
|
||||
|
||||
<input type="hidden" name="reset_token" value="{{ reset_token }}">
|
||||
|
||||
<input type="submit" name="commit" value="Reset password">
|
||||
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::AskResetSubmit) }}">
|
||||
</form>
|
||||
{{ message }}
|
||||
|
||||
<span class="user-message">{{ message }}</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ pub enum Sentence {
|
|||
AskResetEmailSent,
|
||||
AskResetTokenMissing,
|
||||
AskResetTokenExpired,
|
||||
AskResetSubmit,
|
||||
PasswordReset,
|
||||
EmailUnknown,
|
||||
UnableToSendResetEmail,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue