diff --git a/crates/database/queries/users/set_admin_status.sql b/crates/database/queries/users/set_admin_status.sql new file mode 100644 index 0000000..c5646be --- /dev/null +++ b/crates/database/queries/users/set_admin_status.sql @@ -0,0 +1,5 @@ +update users + +set is_admin = ? + +where id is ? \ No newline at end of file diff --git a/crates/database/sqlx-data.json b/crates/database/sqlx-data.json index bd26b26..d93011c 100644 --- a/crates/database/sqlx-data.json +++ b/crates/database/sqlx-data.json @@ -546,6 +546,16 @@ }, "query": "update users\n\nset paper_key = ?\n\nwhere id is ?" }, + "6ff12f357d884a50035d708577a7f3109a07a1ca193cb3082d13687af65c6de0": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 2 + } + }, + "query": "update users\n\nset is_admin = ?\n\nwhere id is ?" + }, "71c74369dc5d374d8ec5aa347b5f599728b74e545df3e986e3e7e66882f73ba0": { "describe": { "columns": [ diff --git a/crates/database/src/tables/users.rs b/crates/database/src/tables/users.rs index 84a22b4..eeee264 100644 --- a/crates/database/src/tables/users.rs +++ b/crates/database/src/tables/users.rs @@ -257,4 +257,18 @@ impl Users { Ok((query.rows_affected() == 1).then_some(())) } + + pub async fn set_admin_status( + conn: impl SqliteExecutor<'_>, + id: &str, + value: bool, + ) -> Result, Error> { + let query: SqliteQueryResult = + sqlx::query_file!("queries/users/set_admin_status.sql", value, id) + .execute(conn) + .await + .map_err(handle_error)?; + + Ok((query.rows_affected() == 1).then_some(())) + } } diff --git a/crates/ezidam/src/routes/admin.rs b/crates/ezidam/src/routes/admin.rs index 0edacea..1d68779 100644 --- a/crates/ezidam/src/routes/admin.rs +++ b/crates/ezidam/src/routes/admin.rs @@ -30,6 +30,7 @@ pub fn routes() -> Vec { admin_users_paper_key_reset, admin_users_totp_secret_disable, admin_users_totp_backup_delete, + admin_users_info_update, ] } diff --git a/crates/ezidam/src/routes/admin/users.rs b/crates/ezidam/src/routes/admin/users.rs index 1c87c9c..6a0b029 100644 --- a/crates/ezidam/src/routes/admin/users.rs +++ b/crates/ezidam/src/routes/admin/users.rs @@ -3,9 +3,11 @@ use crate::routes::root::forgot_password::ResetPasswordEmail; use crate::tokens::JWT_DURATION_MINUTES; use authorization_codes::AuthorizationCode; use chrono_humanize::Humanize; +use email_address::EmailAddress; use rocket::State; use rocket::{get, post}; use settings::Settings; +use std::str::FromStr; use url::Url; use users::totp_login_request::TotpLoginRequest; use users::{password_reset::PasswordResetToken, User}; @@ -446,3 +448,111 @@ pub async fn admin_users_totp_backup_delete( flash_message, )) } + +#[derive(Debug, FromForm)] +pub struct UpdateUserForm<'r> { + pub username: &'r str, + pub name: &'r str, + pub email: &'r str, + pub is_admin: Option<&'r str>, +} + +#[post("/admin/users//info", data = "
")] +pub async fn admin_users_info_update( + _admin_not_current: JwtAdminNotCurrent, + mut db: Connection, + id: RocketUserID, + form: Form>, +) -> Result> { + let mut transaction = db.begin().await?; + + let user = User::get_by_id(&mut transaction, &id.0) + .await? + .ok_or_else(|| Error::not_found("Could not find user"))?; + + if user.is_archived() { + return Err(Error::forbidden("User is archived")); + } + + // Update username + if user.username().0 != form.username { + // Parse username + let username = match Username::from_str(form.username) { + Ok(username) => username, + Err(_) => { + return Ok(Flash::new( + Redirect::to(uri!(admin_users_view(id))), + FlashKind::Danger, + INVALID_USERNAME_ERROR, + )); + } + }; + + if let Err(e) = user.set_username(&mut transaction, &username).await { + return Ok(Flash::new( + Redirect::to(uri!(admin_users_view(id))), + FlashKind::Danger, + e.to_string(), + )); + } + } + + // Update name + if !form.name.is_empty() + && user + .name() + // If it exists in database, check if provided value is different + .map(|current| current != form.name) + // If it does not exist, use provided value + .unwrap_or(true) + { + user.set_name(&mut transaction, form.name).await?; + } + + // Update email + if !form.email.is_empty() + && user + .email() + // If it exists in database, check if provided value is different + .map(|current| current != form.email) + // If it does not exist, use provided value + .unwrap_or(true) + { + // Parse email address + let email = match EmailAddress::from_str(form.email) { + Ok(email) => email, + Err(e) => { + return Ok(Flash::new( + Redirect::to(uri!(admin_users_view(id))), + FlashKind::Danger, + e.to_string(), + )); + } + }; + + if let Err(e) = user.set_email(&mut transaction, email).await { + return Ok(Flash::new( + Redirect::to(uri!(admin_users_view(id))), + FlashKind::Danger, + e.to_string(), + )); + } + } + + // Admin status + let new_status = matches!(form.is_admin, Some("on")); + if user.is_admin() != new_status { + user.set_admin_status(&mut transaction, new_status).await?; + } + + transaction.commit().await?; + + Ok(Flash::new( + Redirect::to(uri!(admin_users_view(id))), + FlashKind::Success, + format!( + "User has been updated.\ +
Some changes can take up to {JWT_DURATION_MINUTES} minutes to appear." + ), + )) +} diff --git a/crates/ezidam/templates/pages/admin/users/view.html.tera b/crates/ezidam/templates/pages/admin/users/view.html.tera index eebaeaa..fd17c45 100644 --- a/crates/ezidam/templates/pages/admin/users/view.html.tera +++ b/crates/ezidam/templates/pages/admin/users/view.html.tera @@ -46,84 +46,86 @@

User information

-
-
-
-
- -
+ +
+
+
+
+ +
-
-
+
+
{% include "icons/id-badge-2" %} - + +
-
-
-
- -
+
+
+ +
-
-
+
+
{% include "icons/user" %} - + +
-
-
-
- -
+
+
+ +
-
-
+
+
{% include "icons/at" %} - + +
-
-
-
Admin status
-
-
- +
+
Admin status
+
+
+ +
-
-
diff --git a/crates/users/src/database.rs b/crates/users/src/database.rs index 442803c..6029315 100644 --- a/crates/users/src/database.rs +++ b/crates/users/src/database.rs @@ -270,4 +270,14 @@ impl User { Ok(()) } + + pub async fn set_admin_status( + &self, + conn: impl SqliteExecutor<'_>, + value: bool, + ) -> Result<(), Error> { + DatabaseUsers::set_admin_status(conn, self.id.as_ref(), value).await?; + + Ok(()) + } }