diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index a9def783..0b25f6ea 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -37,6 +37,10 @@ pub fn init(cfg: &mut ServiceConfig) { cfg.service(user::post_login::post); cfg.service(user::get_reset::get); cfg.service(user::post_reset::post); + cfg.service(user::get_profile::get); + cfg.service(user::post_toggle::post); + cfg.service(user::get_changepassword::get); + cfg.service(user::post_changepassword::post); cfg.service(availability::delete::delete); cfg.service(availability::get_new::get); diff --git a/src/endpoints/user/get_changepassword.rs b/src/endpoints/user/get_changepassword.rs new file mode 100644 index 00000000..826e3fa7 --- /dev/null +++ b/src/endpoints/user/get_changepassword.rs @@ -0,0 +1,45 @@ +use actix_web::{HttpRequest, HttpResponse, Responder}; + +#[actix_web::get("/users/changepassword")] +pub async fn get(request: HttpRequest) -> impl Responder { + if let Some(_) = request.headers().get("HX-Request") { + return HttpResponse::Ok().body(r##" +
+
+ Schließen +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ "##); + } + + HttpResponse::NotFound().finish() +} diff --git a/src/endpoints/user/get_profile.rs b/src/endpoints/user/get_profile.rs new file mode 100644 index 00000000..d1b44297 --- /dev/null +++ b/src/endpoints/user/get_profile.rs @@ -0,0 +1,24 @@ +use actix_web::{web, Responder}; +use askama::Template; +use askama_actix::TemplateToResponse; +use sqlx::PgPool; + +use crate::models::{Area, Function, Role, User}; + +#[derive(Template)] +#[template(path = "user/profile.html")] +struct ProfileTemplate { + user: User +} + +#[actix_web::get("/profile")] +pub async fn get(user: web::ReqData, pool: web::Data) -> impl Responder { + let area = Area::read_by_id(pool.get_ref(), user.area_id).await.unwrap(); + + let mut user = user.into_inner(); + user.area = Some(area); + + let template = ProfileTemplate { user }; + + return template.to_response(); +} diff --git a/src/endpoints/user/mod.rs b/src/endpoints/user/mod.rs index 69ee17f7..8303493a 100644 --- a/src/endpoints/user/mod.rs +++ b/src/endpoints/user/mod.rs @@ -10,3 +10,7 @@ pub mod get_login; pub mod post_login; pub mod get_reset; pub mod post_reset; +pub mod get_profile; +pub mod post_toggle; +pub mod get_changepassword; +pub mod post_changepassword; diff --git a/src/endpoints/user/patch.rs b/src/endpoints/user/patch.rs index a7dc0788..5586d363 100644 --- a/src/endpoints/user/patch.rs +++ b/src/endpoints/user/patch.rs @@ -13,32 +13,28 @@ use crate::{ pub struct JsonPatchDoc { op: String, path: String, - value: Value + value: Value, } #[actix_web::patch("/users/edit/{id}")] pub async fn patch( - user: Identity, + user: web::ReqData, pool: web::Data, path: web::Path, patch_docs: web::Json>, ) -> impl Responder { - let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()) - .await - .unwrap(); - if current_user.role != Role::AreaManager && current_user.role != Role::Admin { - return HttpResponse::Unauthorized().finish(); - } + let is_superuser = user.role != Role::AreaManager && user.role != Role::Admin; if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await { - if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id { + if user.role == Role::AreaManager && user.area_id != user_in_db.area_id { return HttpResponse::Unauthorized().finish(); } let mut changed = false; let mut locked: Option = None; + let mut receive_notifications: Option = None; for doc in patch_docs.iter() { if doc.op.as_str() != "replace" { @@ -47,21 +43,44 @@ pub async fn patch( match doc.path.as_str() { "/locked" => { + if !is_superuser { + return HttpResponse::Unauthorized().finish(); + } changed = true; if let Value::Bool(b) = doc.value { locked = Some(b); } - }, - _ => panic!("other patch paths are not supported!"), + } + "/receiveNotifications" => { + changed = true; + if let Value::Bool(b) = doc.value { + receive_notifications = Some(b) + } + } + _ => return HttpResponse::BadRequest().body("Other PATCH paths are not supported!") }; } if changed { - if let Ok(_) = User::update(pool.get_ref(), path.id, None, None, None, None, None, None, None, locked).await { + if let Ok(_) = User::update( + pool.get_ref(), + path.id, + None, + None, + None, + None, + None, + None, + None, + receive_notifications, + locked, + ) + .await + { return HttpResponse::Ok().body(""); } } else { - return HttpResponse::Ok().body(""); + return HttpResponse::Ok().body(""); } } diff --git a/src/endpoints/user/post_changepassword.rs b/src/endpoints/user/post_changepassword.rs new file mode 100644 index 00000000..ddba251b --- /dev/null +++ b/src/endpoints/user/post_changepassword.rs @@ -0,0 +1,49 @@ +use actix_web::{web, HttpResponse, Responder}; +use serde::Deserialize; +use sqlx::PgPool; + +use crate::{auth::utils, models::User}; + +#[derive(Deserialize)] +struct ChangePasswordForm { + currentpassword: String, + password: String, + passwordretyped: String, +} + +#[actix_web::post("/users/changepassword")] +async fn post( + user: web::ReqData, + form: web::Form, + pool: web::Data, +) -> impl Responder { + if user.password + == utils::hash_plain_password_with_salt(&form.currentpassword, &user.salt).unwrap() + { + if form.password != form.passwordretyped { + return HttpResponse::BadRequest().body("Passwörter stimmen nicht überein!"); + } + + let (hash, salt) = utils::generate_salt_and_hash_plain_password(&form.password).unwrap(); + + User::update( + pool.get_ref(), + user.id, + None, + None, + Some(&hash), + Some(&salt), + None, + None, + None, + None, + None, + ) + .await + .unwrap(); + + return HttpResponse::Ok().body(r#"
Passwort wurde geändert.
"#); + } else { + return HttpResponse::BadRequest().body("Aktuelles Passwort ist nicht korrekt!"); + } +} diff --git a/src/endpoints/user/post_edit.rs b/src/endpoints/user/post_edit.rs index 63605cf5..583a1c30 100644 --- a/src/endpoints/user/post_edit.rs +++ b/src/endpoints/user/post_edit.rs @@ -83,7 +83,7 @@ pub async fn post_edit( }; if changed { - match User::update(pool.get_ref(), path.id, email, name, None, None, role, function, area, None).await { + match User::update(pool.get_ref(), path.id, email, name, None, None, role, function, area, None, None).await { Ok(_) => return HttpResponse::Found().insert_header((LOCATION, "/users")).finish(), Err(err) => println!("{}", err) } diff --git a/src/endpoints/user/post_reset.rs b/src/endpoints/user/post_reset.rs index cdb2552c..612c9f4f 100644 --- a/src/endpoints/user/post_reset.rs +++ b/src/endpoints/user/post_reset.rs @@ -91,6 +91,7 @@ async fn post(form: web::Form, pool: web::Data) -> im None, None, None, + None ) .await .unwrap(); diff --git a/src/endpoints/user/post_toggle.rs b/src/endpoints/user/post_toggle.rs new file mode 100644 index 00000000..b91c0782 --- /dev/null +++ b/src/endpoints/user/post_toggle.rs @@ -0,0 +1,70 @@ +use actix_web::{web, HttpResponse, Responder}; +use serde::Deserialize; +use sqlx::PgPool; + +use crate::{ + endpoints::IdPath, + models::{Role, User}, +}; + +#[derive(Deserialize)] +struct ToggleQuery { + field: String, +} + +#[actix_web::post("/users/{id}/toggle")] +pub async fn post( + user: web::ReqData, + pool: web::Data, + path: web::Path, + query: web::Query, +) -> impl Responder { + if user.id != path.id && (user.role != Role::Admin || user.role != Role::AreaManager) { + return HttpResponse::Unauthorized().finish(); + } + + let user = if user.id != path.id { + User::read_by_id(pool.get_ref(), path.id).await.unwrap() + } else { + user.into_inner() + }; + + match query.field.as_str() { + "locked" => User::update( + pool.get_ref(), + user.id, + None, + None, + None, + None, + None, + None, + None, + None, + Some(!user.locked), + ) + .await + .unwrap(), + "receiveNotifications" => { + User::update( + pool.get_ref(), + user.id, + None, + None, + None, + None, + None, + None, + None, + Some(!user.receive_notifications), + None, + ) + .await + .unwrap(); + //return HttpResponse::Ok().body(""); + } + _ => return HttpResponse::BadRequest().body("Other PATCH paths are not supported!"), + }; + + HttpResponse::Ok().finish() +} diff --git a/src/models/user.rs b/src/models/user.rs index 3154e5ee..b48dc606 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -66,7 +66,7 @@ impl User { areaId, locked, lastLogin, - receiveNotifications + receiveNotifications FROM user_ WHERE id = $1; "#, @@ -106,7 +106,7 @@ impl User { areaId, locked, lastLogin, - receiveNotifications + receiveNotifications FROM user_ WHERE email = $1 AND locked = FALSE; "#, @@ -146,7 +146,7 @@ impl User { areaId, locked, lastLogin, - receiveNotifications + receiveNotifications FROM user_; "#, ) @@ -235,7 +235,7 @@ impl User { areaId, locked, lastLogin, - receiveNotifications + receiveNotifications FROM user_ WHERE areaId = $1; "#, @@ -275,6 +275,7 @@ impl User { role: Option, function: Option, area_id: Option, + receive_notifications: Option, locked: Option, ) -> anyhow::Result<()> { let mut query_builder = sqlx::QueryBuilder::new("UPDATE user_ SET "); @@ -315,6 +316,11 @@ impl User { separated.push_bind_unseparated(area_id); } + if let Some(receive_notifications) = receive_notifications { + separated.push("receiveNotifications = "); + separated.push_bind_unseparated(receive_notifications); + } + if let Some(locked) = locked { separated.push("locked = "); separated.push_bind_unseparated(locked); diff --git a/templates/base.html b/templates/base.html index bba187b7..a0e8ed8b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -33,6 +33,16 @@ top: -.05em; vertical-align: middle; } + + .result { + visibility: hidden; + } + + .fadeout { + visibility: visible; + opacity: 0; + transition: opacity 2s ease-in; + } diff --git a/templates/nav.html b/templates/nav.html index ddc778c1..91b81df4 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -66,10 +66,10 @@