admin/users: update username/name/email/admin status
This commit is contained in:
parent
bdd5eca9f1
commit
05373a2800
7 changed files with 202 additions and 50 deletions
5
crates/database/queries/users/set_admin_status.sql
Normal file
5
crates/database/queries/users/set_admin_status.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
update users
|
||||
|
||||
set is_admin = ?
|
||||
|
||||
where id is ?
|
||||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue