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]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.26"
|
version = "0.23.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -2607,9 +2607,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.1"
|
version = "0.103.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
|
checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
|
@ -3368,9 +3368,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.44.2"
|
version = "1.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
|
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,19 @@ $dark-theme: false !default;
|
||||||
$color-1: #B29B89;
|
$color-1: #B29B89;
|
||||||
$color-2: #89B29B;
|
$color-2: #89B29B;
|
||||||
$color-3: #9B89B2;
|
$color-3: #9B89B2;
|
||||||
|
$color-highlight: #cf2d2dff;
|
||||||
|
|
||||||
$text-color: color.adjust($color-1, $lightness: -30%);
|
$text-color: color.adjust($color-1, $lightness: -30%);
|
||||||
$text-highlight: color.adjust($color-1, $lightness: +30%);
|
$text-highlight: color.adjust($color-1, $lightness: +30%);
|
||||||
|
|
||||||
$link-color: color.adjust($color-3, $lightness: -25%);
|
$link-color: color.adjust($color-3, $lightness: -25%);
|
||||||
$link-hover-color: color.adjust($color-3, $lightness: +20%);
|
$link-hover-color: color.adjust($color-3, $lightness: +20%);
|
||||||
|
|
||||||
@if $dark-theme {
|
@if $dark-theme {
|
||||||
$text-color: color.adjust($color-1, $lightness: -10%);
|
$text-color: color.adjust($color-1, $lightness: -10%);
|
||||||
$text-highlight: 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-color: color.adjust($color-3, $lightness: -5%);
|
||||||
$link-hover-color: color.adjust($color-3, $lightness: +10%);
|
$link-hover-color: color.adjust($color-3, $lightness: +10%);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ body {
|
||||||
|
|
||||||
.user-message {
|
.user-message {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: consts.$color-highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-handle {
|
.drag-handle {
|
||||||
|
|
@ -286,7 +287,7 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-edit form {
|
#reset-password form {
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
|
|
@ -294,48 +295,13 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #sign-in {
|
#user-edit form {
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|
||||||
// }
|
input[type="submit"] {
|
||||||
|
grid-column: 2
|
||||||
// #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;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
consts,
|
consts,
|
||||||
data::model,
|
data::model,
|
||||||
hash::{hash, verify_password},
|
hash::{hash, verify_password},
|
||||||
|
services::user,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
|
|
@ -20,8 +21,8 @@ pub enum SignUpResult {
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
pub enum UpdateUserResult {
|
pub enum UpdateUserResult {
|
||||||
EmailAlreadyTaken,
|
EmailAlreadyTaken,
|
||||||
/// Validation token.
|
/// (New validation token, old email, old validation token, old validation time).
|
||||||
UserUpdatedWaitingForRevalidation(String),
|
UserUpdatedWaitingForRevalidation(String, String, Option<String>, String),
|
||||||
Ok,
|
Ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,11 +106,22 @@ FROM [UserLoginToken] WHERE [token] = $1
|
||||||
let mut tx = self.tx().await?;
|
let mut tx = self.tx().await?;
|
||||||
let hashed_new_password = new_password.map(|p| hash(p).unwrap());
|
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::<
|
let (
|
||||||
_,
|
email,
|
||||||
(String, String, u32, u8, String),
|
name,
|
||||||
>(
|
default_servings,
|
||||||
"SELECT [email], [name], [default_servings], [first_day_of_the_week], [password] FROM [User] WHERE [id] = $1",
|
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)
|
.bind(user_id)
|
||||||
.fetch_one(&mut *tx)
|
.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);
|
let email_changed = new_email.is_some_and(|new_email| new_email != email);
|
||||||
|
|
||||||
// Check if email not already taken.
|
// Check if email not already taken.
|
||||||
let validation_token = if email_changed {
|
let new_validation_token = if email_changed {
|
||||||
if sqlx::query_scalar(
|
if sqlx::query_scalar(
|
||||||
r#"
|
r#"
|
||||||
SELECT COUNT(*) > 0
|
SELECT COUNT(*) > 0
|
||||||
|
|
@ -170,13 +182,41 @@ WHERE [id] = $1
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(if let Some(validation_token) = validation_token {
|
Ok(if let Some(new_validation_token) = new_validation_token {
|
||||||
UpdateUserResult::UserUpdatedWaitingForRevalidation(validation_token)
|
UpdateUserResult::UserUpdatedWaitingForRevalidation(
|
||||||
|
new_validation_token,
|
||||||
|
email,
|
||||||
|
validation_token,
|
||||||
|
validation_token_datetime,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
UpdateUserResult::Ok
|
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<()> {
|
pub async fn set_user_lang(&self, user_id: i64, lang: &str) -> Result<()> {
|
||||||
sqlx::query("UPDATE [User] SET [lang] = $2 WHERE [id] = $1")
|
sqlx::query("UPDATE [User] SET [lang] = $2 WHERE [id] = $1")
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
|
|
@ -975,7 +1015,7 @@ VALUES
|
||||||
assert_eq!(user.name, "paul");
|
assert_eq!(user.name, "paul");
|
||||||
assert_eq!(user.email, "paul@atreides.com");
|
assert_eq!(user.email, "paul@atreides.com");
|
||||||
|
|
||||||
if let UpdateUserResult::UserUpdatedWaitingForRevalidation(token) = connection
|
if let UpdateUserResult::UserUpdatedWaitingForRevalidation(token, _, _, _) = connection
|
||||||
.update_user(
|
.update_user(
|
||||||
1,
|
1,
|
||||||
Some("muaddib@fremen.com"),
|
Some("muaddib@fremen.com"),
|
||||||
|
|
|
||||||
|
|
@ -474,9 +474,6 @@ pub async fn ask_reset_password_post(
|
||||||
email,
|
email,
|
||||||
message_email: match error {
|
message_email: match error {
|
||||||
AskResetPasswordError::InvalidEmail => context.tr.t(Sentence::InvalidEmail),
|
AskResetPasswordError::InvalidEmail => context.tr.t(Sentence::InvalidEmail),
|
||||||
_ => "",
|
|
||||||
},
|
|
||||||
message: match error {
|
|
||||||
AskResetPasswordError::EmailAlreadyReset => {
|
AskResetPasswordError::EmailAlreadyReset => {
|
||||||
context.tr.t(Sentence::AskResetEmailAlreadyResetError)
|
context.tr.t(Sentence::AskResetEmailAlreadyResetError)
|
||||||
}
|
}
|
||||||
|
|
@ -484,6 +481,9 @@ pub async fn ask_reset_password_post(
|
||||||
AskResetPasswordError::UnableSendEmail(_) => {
|
AskResetPasswordError::UnableSendEmail(_) => {
|
||||||
context.tr.t(Sentence::UnableToSendResetEmail)
|
context.tr.t(Sentence::UnableToSendResetEmail)
|
||||||
}
|
}
|
||||||
|
_ => "",
|
||||||
|
},
|
||||||
|
message: match error {
|
||||||
AskResetPasswordError::DatabaseError(_) => {
|
AskResetPasswordError::DatabaseError(_) => {
|
||||||
context.tr.t(Sentence::DatabaseError)
|
context.tr.t(Sentence::DatabaseError)
|
||||||
}
|
}
|
||||||
|
|
@ -853,7 +853,12 @@ pub async fn edit_user_post(
|
||||||
Ok(db::user::UpdateUserResult::EmailAlreadyTaken) => {
|
Ok(db::user::UpdateUserResult::EmailAlreadyTaken) => {
|
||||||
return error_response(ProfileUpdateError::EmailAlreadyTaken, &form_data, context);
|
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 url = utils::get_url_from_host(&host);
|
||||||
let email = form_data.email.clone();
|
let email = form_data.email.clone();
|
||||||
match email_service
|
match email_service
|
||||||
|
|
@ -874,6 +879,22 @@ pub async fn edit_user_post(
|
||||||
message = context.tr.t(Sentence::ProfileEmailSent);
|
message = context.tr.t(Sentence::ProfileEmailSent);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
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(
|
return error_response(
|
||||||
ProfileUpdateError::UnableToSendEmail(error),
|
ProfileUpdateError::UnableToSendEmail(error),
|
||||||
&form_data,
|
&form_data,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<input id="email_field" type="email"
|
<input id="email_field" type="email"
|
||||||
name="email" value="{{ email }}"
|
name="email" value="{{ email }}"
|
||||||
autocapitalize="none" autocomplete="email" autofocus="autofocus">
|
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) }}">
|
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::AskResetButton) }}">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,18 @@
|
||||||
<form action="/reset_password" method="post">
|
<form action="/reset_password" method="post">
|
||||||
<label for="password_field_1">{{ context.tr.tp(Sentence::AskResetChooseNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
<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">
|
<input id="password_field_1" type="password" name="password_1">
|
||||||
|
<span></span>
|
||||||
|
|
||||||
<label for="password_field_1">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
<label for="password_field_1">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
||||||
<input id="password_field_2" type="password" name="password_2">
|
<input id="password_field_2" type="password" name="password_2">
|
||||||
|
<span class="user-message">{{ message_password }}</span>
|
||||||
{{ message_password }}
|
|
||||||
|
|
||||||
<input type="hidden" name="reset_token" value="{{ reset_token }}">
|
<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>
|
</form>
|
||||||
{{ message }}
|
|
||||||
|
<span class="user-message">{{ message }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
(AskResetEmailSent, "An email has been sent, follow the link to reset your password"),
|
(AskResetEmailSent, "An email has been sent, follow the link to reset your password"),
|
||||||
(AskResetTokenMissing, "Reset token missing"),
|
(AskResetTokenMissing, "Reset token missing"),
|
||||||
(AskResetTokenExpired, "Token expired, try to reset password again"),
|
(AskResetTokenExpired, "Token expired, try to reset password again"),
|
||||||
|
(AskResetSubmit, "Reset password"),
|
||||||
(PasswordReset, "Your password has been reset"),
|
(PasswordReset, "Your password has been reset"),
|
||||||
(EmailUnknown, "Email unknown"),
|
(EmailUnknown, "Email unknown"),
|
||||||
(UnableToSendResetEmail, "Unable to send the reset password email"),
|
(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"),
|
(AskResetEmailSent, "Un email a été envoyé, suivez le lien pour réinitialiser votre mot de passe"),
|
||||||
(AskResetTokenMissing, "Jeton de réinitialisation manquant"),
|
(AskResetTokenMissing, "Jeton de réinitialisation manquant"),
|
||||||
(AskResetTokenExpired, "Jeton expiré, essayez de réinitialiser votre mot de passe à nouveau"),
|
(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é"),
|
(PasswordReset, "Votre mot de passe a été réinitialisé"),
|
||||||
(EmailUnknown, "Email inconnu"),
|
(EmailUnknown, "Email inconnu"),
|
||||||
(UnableToSendResetEmail, "Impossible d'envoyer l'email pour la réinitialisation du mot de passe"),
|
(UnableToSendResetEmail, "Impossible d'envoyer l'email pour la réinitialisation du mot de passe"),
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ pub enum Sentence {
|
||||||
AskResetEmailSent,
|
AskResetEmailSent,
|
||||||
AskResetTokenMissing,
|
AskResetTokenMissing,
|
||||||
AskResetTokenExpired,
|
AskResetTokenExpired,
|
||||||
|
AskResetSubmit,
|
||||||
PasswordReset,
|
PasswordReset,
|
||||||
EmailUnknown,
|
EmailUnknown,
|
||||||
UnableToSendResetEmail,
|
UnableToSendResetEmail,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue