diff --git a/.sqlx/query-b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87.json b/.sqlx/query-b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87.json new file mode 100644 index 00000000..509abb38 --- /dev/null +++ b/.sqlx/query-b4edfcac9404060d487db765b8c18ef8b7440699583e0bede95f4d214e668a87.json @@ -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" +} diff --git a/web/src/endpoints/user/post_edit.rs b/web/src/endpoints/user/post_edit.rs index e34f28ad..e38d5212 100644 --- a/web/src/endpoints/user/post_edit.rs +++ b/web/src/endpoints/user/post_edit.rs @@ -28,7 +28,8 @@ pub async fn post_edit( }; 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); } @@ -53,14 +54,21 @@ pub async fn post_edit( let changeset = UserChangeset { name: form.name.clone(), - email: form.email.clone(), + email: form.email.to_lowercase(), role, functions, 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() { - 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?; @@ -145,4 +153,67 @@ mod tests { let response = test_post(&context.db_pool, app, &config, form).await; 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()); + } } diff --git a/web/src/endpoints/user/post_login.rs b/web/src/endpoints/user/post_login.rs index bdc656e8..bad42e15 100644 --- a/web/src/endpoints/user/post_login.rs +++ b/web/src/endpoints/user/post_login.rs @@ -18,7 +18,7 @@ async fn post( request: HttpRequest, pool: web::Data, ) -> 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 hash = hash_plain_password_with_salt(&form.password, &salt).unwrap(); diff --git a/web/src/endpoints/user/post_new.rs b/web/src/endpoints/user/post_new.rs index 4f7e84cb..dc0a9992 100644 --- a/web/src/endpoints/user/post_new.rs +++ b/web/src/endpoints/user/post_new.rs @@ -47,14 +47,19 @@ pub async fn post_new( let changeset = UserChangeset { name: form.name.clone(), - email: form.email.clone(), + email: form.email.to_lowercase(), role, functions, 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() { - 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?; diff --git a/web/src/endpoints/user/post_reset.rs b/web/src/endpoints/user/post_reset.rs index f978ab9c..1d099f5e 100644 --- a/web/src/endpoints/user/post_reset.rs +++ b/web/src/endpoints/user/post_reset.rs @@ -29,7 +29,9 @@ async fn post( && form.password.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?; mailer .send_forgot_password_mail(&user, &reset.token) diff --git a/web/src/models/user.rs b/web/src/models/user.rs index 3cc808f0..872f95e3 100644 --- a/web/src/models/user.rs +++ b/web/src/models/user.rs @@ -147,6 +147,14 @@ impl User { Ok(result) } + pub async fn exists(pool: &PgPool, email: &str) -> Result> { + 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> { let records = sqlx::query!( r#" diff --git a/web/src/utils/test_helper/test_requests.rs b/web/src/utils/test_helper/test_requests.rs index f7a33cc0..96b4cea2 100644 --- a/web/src/utils/test_helper/test_requests.rs +++ b/web/src/utils/test_helper/test_requests.rs @@ -21,6 +21,7 @@ pub struct RequestConfig { } impl RequestConfig { + /// Creates a new [`RequestConfig`] with User as [`Role::Staff`] and [`Function::Posten`] in Area 1. pub fn new(uri: &str) -> Self { Self { uri: uri.to_string(), diff --git a/web/templates/user/new_or_edit.html b/web/templates/user/new_or_edit.html index a4a68293..ee43cc76 100644 --- a/web/templates/user/new_or_edit.html +++ b/web/templates/user/new_or_edit.html @@ -3,149 +3,147 @@ {% block content %}
- {% if id.is_some() %} -
-

Nutzer '{{ name.as_ref().unwrap() }}' bearbeiten

- {% else %} - -

Neuen Nutzer anlegen

- {% endif %} + +

+ {% if let Some(name) = name %}Nutzer '{{ name }}' bearbeiten{% else %}Neuen Nutzer anlegen{% endif %} +

-
-
- +
+
+ +
+
+
+
+ +
+

-
-
-
- +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
-
-
- +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
-
-
-
- +
+
+ + {% if user.role == Role::Admin %} +
+
+ +
+
+
+
+
+
+
+ {% endif %} -
-
- -
-
-
-
-
- -
-
+
+
+
+
+
+ +
+
+
-
-
- -
-
-
-
- -
-
- -
-
- -
-
-
-
- - {% if user.role == Role::Admin %} -
-
- -
-
-
-
-
- -
-
-
-
-
- {% endif %} - -
-
-
-
-
- -
- -
-
-
- - +