feat: enforce implicit lowercase email address
This commit is contained in:
parent
90ac5c306d
commit
cca925f4eb
22
.sqlx/query-b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87.json
generated
Normal file
22
.sqlx/query-b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87.json
generated
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT id FROM user_ WHERE email = $1;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87"
|
||||||
|
}
|
@ -28,7 +28,8 @@ pub async fn post_edit(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let role = form.role.try_into()?;
|
let role = form.role.try_into()?;
|
||||||
if user.role == Role::AreaManager && (user.area_id != user_in_db.area_id || role == Role::Admin) {
|
if user.role == Role::AreaManager && (user.area_id != user_in_db.area_id || role == Role::Admin)
|
||||||
|
{
|
||||||
return Err(ApplicationError::Unauthorized);
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,14 +54,21 @@ pub async fn post_edit(
|
|||||||
|
|
||||||
let changeset = UserChangeset {
|
let changeset = UserChangeset {
|
||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
email: form.email.clone(),
|
email: form.email.to_lowercase(),
|
||||||
role,
|
role,
|
||||||
functions,
|
functions,
|
||||||
area_id,
|
area_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(existing_id) = User::exists(pool.get_ref(), &changeset.email).await? {
|
||||||
|
if existing_id != user_in_db.id {
|
||||||
|
return Ok(HttpResponse::UnprocessableEntity()
|
||||||
|
.body("email: an user already exists with the same email"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = changeset.validate() {
|
if let Err(e) = changeset.validate() {
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
User::update(pool.get_ref(), user_in_db.id, changeset).await?;
|
User::update(pool.get_ref(), user_in_db.id, changeset).await?;
|
||||||
@ -145,4 +153,67 @@ mod tests {
|
|||||||
let response = test_post(&context.db_pool, app, &config, form).await;
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
assert_eq!(StatusCode::BAD_REQUEST, response.status());
|
assert_eq!(StatusCode::BAD_REQUEST, response.status());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[db_test]
|
||||||
|
async fn email_gets_cast_to_lowercase(context: &DbTestContext) {
|
||||||
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
Area::create(&context.db_pool, "Süd").await.unwrap();
|
||||||
|
|
||||||
|
let app = context.app().await;
|
||||||
|
let config = RequestConfig::new("/users/edit/1").with_role(Role::Admin);
|
||||||
|
|
||||||
|
let new_name: String = Name().fake();
|
||||||
|
let new_mail: String = String::from("NONLowercaseEMAIL@example.com");
|
||||||
|
|
||||||
|
let form = NewOrEditUserForm {
|
||||||
|
name: new_name.clone(),
|
||||||
|
email: new_mail.clone(),
|
||||||
|
role: Role::AreaManager as u8,
|
||||||
|
is_posten: None,
|
||||||
|
is_wachhabender: None,
|
||||||
|
is_fuehrungsassistent: Some(true),
|
||||||
|
area: Some(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
|
let updated_user = User::read_by_id(&context.db_pool, 1)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(new_mail.to_lowercase(), updated_user.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[db_test]
|
||||||
|
async fn fails_when_email_already_present(context: &DbTestContext) {
|
||||||
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
Area::create(&context.db_pool, "Süd").await.unwrap();
|
||||||
|
|
||||||
|
let app = context.app().await;
|
||||||
|
let config = RequestConfig::new("/users/edit/1").with_role(Role::Admin);
|
||||||
|
|
||||||
|
let second_user = User::read_by_id(&context.db_pool, 2)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let new_name: String = Name().fake();
|
||||||
|
let new_mail: String = second_user.email;
|
||||||
|
|
||||||
|
let form = NewOrEditUserForm {
|
||||||
|
name: new_name.clone(),
|
||||||
|
email: new_mail.clone(),
|
||||||
|
role: Role::AreaManager as u8,
|
||||||
|
is_posten: None,
|
||||||
|
is_wachhabender: None,
|
||||||
|
is_fuehrungsassistent: Some(true),
|
||||||
|
area: Some(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
|
assert_eq!(StatusCode::BAD_REQUEST, response.status());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ async fn post(
|
|||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
if let Ok(user) = User::read_for_login(pool.get_ref(), &form.email).await {
|
if let Ok(user) = User::read_for_login(pool.get_ref(), &form.email.to_lowercase()).await {
|
||||||
let salt = user.salt.unwrap();
|
let salt = user.salt.unwrap();
|
||||||
|
|
||||||
let hash = hash_plain_password_with_salt(&form.password, &salt).unwrap();
|
let hash = hash_plain_password_with_salt(&form.password, &salt).unwrap();
|
||||||
|
@ -47,14 +47,19 @@ pub async fn post_new(
|
|||||||
|
|
||||||
let changeset = UserChangeset {
|
let changeset = UserChangeset {
|
||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
email: form.email.clone(),
|
email: form.email.to_lowercase(),
|
||||||
role,
|
role,
|
||||||
functions,
|
functions,
|
||||||
area_id,
|
area_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(_) = User::exists(pool.get_ref(), &changeset.email).await? {
|
||||||
|
return Ok(HttpResponse::UnprocessableEntity()
|
||||||
|
.body("email: an user already exists with the same email"));
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = changeset.validate() {
|
if let Err(e) = changeset.validate() {
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = User::create(pool.get_ref(), changeset).await?;
|
let id = User::create(pool.get_ref(), changeset).await?;
|
||||||
|
@ -29,7 +29,9 @@ async fn post(
|
|||||||
&& form.password.is_none()
|
&& form.password.is_none()
|
||||||
&& form.passwordretyped.is_none()
|
&& form.passwordretyped.is_none()
|
||||||
{
|
{
|
||||||
if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await {
|
if let Ok(user) =
|
||||||
|
User::read_for_login(pool.get_ref(), &form.email.as_ref().unwrap().to_lowercase()).await
|
||||||
|
{
|
||||||
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
||||||
mailer
|
mailer
|
||||||
.send_forgot_password_mail(&user, &reset.token)
|
.send_forgot_password_mail(&user, &reset.token)
|
||||||
|
@ -147,6 +147,14 @@ impl User {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn exists(pool: &PgPool, email: &str) -> Result<Option<i32>> {
|
||||||
|
let record = sqlx::query!("SELECT id FROM user_ WHERE email = $1;", email)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(record.and_then(|r| Some(r.id)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<User>> {
|
pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<User>> {
|
||||||
let records = sqlx::query!(
|
let records = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -21,6 +21,7 @@ pub struct RequestConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RequestConfig {
|
impl RequestConfig {
|
||||||
|
/// Creates a new [`RequestConfig`] with User as [`Role::Staff`] and [`Function::Posten`] in Area 1.
|
||||||
pub fn new(uri: &str) -> Self {
|
pub fn new(uri: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
|
@ -3,13 +3,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if id.is_some() %}
|
<form hx-post="/users/{% if let Some(id) = id %}edit/{{ id }}{% else %}new{% endif %}" hx-target-422="find p">
|
||||||
<form method="post" action="/users/edit/{{ id.unwrap() }}">
|
<h1 class="title">
|
||||||
<h1 class="title">Nutzer '{{ name.as_ref().unwrap() }}' bearbeiten</h1>
|
{% if let Some(name) = name %}Nutzer '{{ name }}' bearbeiten{% else %}Neuen Nutzer anlegen{% endif %}
|
||||||
{% else %}
|
</h1>
|
||||||
<form method="post" action="/users/new">
|
|
||||||
<h1 class="title">Neuen Nutzer anlegen</h1>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="field is-horizontal">
|
<div class="field is-horizontal">
|
||||||
<div class="field-label">
|
<div class="field-label">
|
||||||
@ -19,8 +16,9 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" name="email" placeholder="max.mustermann@brasiwa-leipzig.de" {{
|
<input class="input" type="text" name="email" placeholder="max.mustermann@brasiwa-leipzig.de" {{
|
||||||
email|insert_value|safe }} required />
|
email|insert_value|safe }} required _="on input put '' into the next <p/>" />
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user