admin/users: update username/name/email/admin status

This commit is contained in:
Philippe Loctaux 2023-05-04 23:53:32 +02:00
parent bdd5eca9f1
commit 05373a2800
7 changed files with 202 additions and 50 deletions

View file

@ -0,0 +1,5 @@
update users
set is_admin = ?
where id is ?

View file

@ -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": [

View file

@ -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<Option<()>, 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(()))
}
}

View file

@ -30,6 +30,7 @@ pub fn routes() -> Vec<Route> {
admin_users_paper_key_reset,
admin_users_totp_secret_disable,
admin_users_totp_backup_delete,
admin_users_info_update,
]
}

View file

@ -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/<id>/info", data = "<form>")]
pub async fn admin_users_info_update(
_admin_not_current: JwtAdminNotCurrent,
mut db: Connection<Database>,
id: RocketUserID,
form: Form<UpdateUserForm<'_>>,
) -> Result<Flash<Redirect>> {
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.\
<br>Some changes can take up to {JWT_DURATION_MINUTES} minutes to appear."
),
))
}

View file

@ -46,6 +46,7 @@
<div class="card-header">
<h3 class="card-title">User information</h3>
</div>
<form action="{{ local.id }}/info" method="post">
<div class="card-body">
<div class="datagrid">
<div class="datagrid-item">
@ -106,9 +107,9 @@
<div class="mt-2">
<label class="form-check">
{% if local.is_admin %}
<input class="form-check-input" type="checkbox" checked>
<input class="form-check-input" type="checkbox" name="is_admin" checked>
{% else %}
<input class="form-check-input" type="checkbox">
<input class="form-check-input" type="checkbox" name="is_admin">
{% endif %}
<span class="form-check-label">Administrator</span>
</label>
@ -124,6 +125,7 @@
</div>
</div>
</form>
</div>
<div class="mt-4 card">

View file

@ -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(())
}
}