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

@ -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,84 +46,86 @@
<div class="card-header">
<h3 class="card-title">User information</h3>
</div>
<div class="card-body">
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title">
<label class="required" for="username">Username</label>
</div>
<form action="{{ local.id }}/info" method="post">
<div class="card-body">
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title">
<label class="required" for="username">Username</label>
</div>
<div class="datagrid-content">
<div class="input-icon">
<div class="datagrid-content">
<div class="input-icon">
<span class="input-icon-addon">
{% include "icons/id-badge-2" %}
</span>
<input name="username" id="username" value="{{ local.username }}" type="text"
placeholder="Enter a username"
class="form-control"
required>
<input name="username" id="username" value="{{ local.username }}" type="text"
placeholder="Enter a username"
class="form-control"
required>
</div>
</div>
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">
<label for="name">Full Name</label>
</div>
<div class="datagrid-item">
<div class="datagrid-title">
<label for="name">Full Name</label>
</div>
<div class="datagrid-content">
<div class="input-icon">
<div class="datagrid-content">
<div class="input-icon">
<span class="input-icon-addon">
{% include "icons/user" %}
</span>
<input name="name" id="name" value="{{ local.name }}" type="text"
placeholder="Napoleon Bonaparte"
class="form-control">
<input name="name" id="name" value="{{ local.name }}" type="text"
placeholder="Napoleon Bonaparte"
class="form-control">
</div>
</div>
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">
<label for="email">Email address</label>
</div>
<div class="datagrid-item">
<div class="datagrid-title">
<label for="email">Email address</label>
</div>
<div class="datagrid-content">
<div class="input-icon">
<div class="datagrid-content">
<div class="input-icon">
<span class="input-icon-addon">
{% include "icons/at" %}
</span>
<input name="email" id="email" value="{{ local.email }}" type="email"
placeholder="napoleon@bonaparte.fr"
class="form-control">
<input name="email" id="email" value="{{ local.email }}" type="email"
placeholder="napoleon@bonaparte.fr"
class="form-control">
</div>
</div>
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Admin status</div>
<div class="datagrid-content">
<div class="mt-2">
<label class="form-check">
{% if local.is_admin %}
<input class="form-check-input" type="checkbox" checked>
{% else %}
<input class="form-check-input" type="checkbox">
{% endif %}
<span class="form-check-label">Administrator</span>
</label>
<div class="datagrid-item">
<div class="datagrid-title">Admin status</div>
<div class="datagrid-content">
<div class="mt-2">
<label class="form-check">
{% if local.is_admin %}
<input class="form-check-input" type="checkbox" name="is_admin" checked>
{% else %}
<input class="form-check-input" type="checkbox" name="is_admin">
{% endif %}
<span class="form-check-label">Administrator</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer text-end">
<div class="d-flex">
<button type="submit" class="btn btn-primary ms-auto">Save</button>
<div class="card-footer text-end">
<div class="d-flex">
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</div>
</form>
</div>
<div class="mt-4 card">