admin/users: add new user
This commit is contained in:
parent
85facf7dc6
commit
306f2a60c4
8 changed files with 167 additions and 5 deletions
|
|
@ -30,6 +30,7 @@ pub enum Page {
|
||||||
UserSecurityTotp(UserSecurityTotp),
|
UserSecurityTotp(UserSecurityTotp),
|
||||||
AuthorizeTotp(AuthorizeTotp),
|
AuthorizeTotp(AuthorizeTotp),
|
||||||
AdminUsersView(AdminUsersView),
|
AdminUsersView(AdminUsersView),
|
||||||
|
AdminUsersNew(AdminUsersNew),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|
@ -56,6 +57,7 @@ impl Page {
|
||||||
Page::UserSecurityTotp(_) => "pages/settings/totp",
|
Page::UserSecurityTotp(_) => "pages/settings/totp",
|
||||||
Page::AuthorizeTotp(_) => "pages/oauth/totp",
|
Page::AuthorizeTotp(_) => "pages/oauth/totp",
|
||||||
Page::AdminUsersView(_) => "pages/admin/users/view",
|
Page::AdminUsersView(_) => "pages/admin/users/view",
|
||||||
|
Page::AdminUsersNew(_) => "pages/admin/users/new",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,6 +84,7 @@ impl Page {
|
||||||
Page::UserSecurityTotp(_) => "Enable One-time password",
|
Page::UserSecurityTotp(_) => "Enable One-time password",
|
||||||
Page::AuthorizeTotp(_) => "Verifying your account",
|
Page::AuthorizeTotp(_) => "Verifying your account",
|
||||||
Page::AdminUsersView(_) => "User info",
|
Page::AdminUsersView(_) => "User info",
|
||||||
|
Page::AdminUsersNew(_) => "New user",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,6 +113,7 @@ impl Page {
|
||||||
Page::UserSecurityTotp(_) => Some(UserMenu::Settings.into()),
|
Page::UserSecurityTotp(_) => Some(UserMenu::Settings.into()),
|
||||||
Page::AuthorizeTotp(_) => None,
|
Page::AuthorizeTotp(_) => None,
|
||||||
Page::AdminUsersView(_) => Some(AdminMenu::Users.into()),
|
Page::AdminUsersView(_) => Some(AdminMenu::Users.into()),
|
||||||
|
Page::AdminUsersNew(_) => Some(AdminMenu::Users.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +140,7 @@ impl Page {
|
||||||
Page::UserSecurityTotp(totp) => Box::new(totp),
|
Page::UserSecurityTotp(totp) => Box::new(totp),
|
||||||
Page::AuthorizeTotp(totp) => Box::new(totp),
|
Page::AuthorizeTotp(totp) => Box::new(totp),
|
||||||
Page::AdminUsersView(view) => Box::new(view),
|
Page::AdminUsersView(view) => Box::new(view),
|
||||||
|
Page::AdminUsersNew(new) => Box::new(new),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ pub fn routes() -> Vec<Route> {
|
||||||
admin_apps_new_secret,
|
admin_apps_new_secret,
|
||||||
admin_apps_archive,
|
admin_apps_archive,
|
||||||
admin_users_list,
|
admin_users_list,
|
||||||
|
admin_users_new,
|
||||||
|
admin_users_new_form,
|
||||||
admin_users_view,
|
admin_users_view,
|
||||||
admin_users_archive,
|
admin_users_archive,
|
||||||
admin_users_password_reset,
|
admin_users_password_reset,
|
||||||
|
|
@ -103,4 +105,11 @@ pub mod content {
|
||||||
pub local: User,
|
pub local: User,
|
||||||
pub password_recover_expiration: Option<String>,
|
pub password_recover_expiration: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AdminUsersNew {
|
||||||
|
pub user: JwtClaims,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,62 @@ pub async fn admin_users_list(
|
||||||
.unwrap_or_else(|| page.into()))
|
.unwrap_or_else(|| page.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/admin/users/new")]
|
||||||
|
pub async fn admin_users_new(admin: JwtAdmin, flash: Option<FlashMessage<'_>>) -> Result<Template> {
|
||||||
|
let page = Page::AdminUsersNew(super::content::AdminUsersNew { user: admin.0 });
|
||||||
|
|
||||||
|
Ok(flash
|
||||||
|
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||||
|
.unwrap_or_else(|| page.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct UsersNewForm<'r> {
|
||||||
|
pub username: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/admin/users/new", data = "<form>")]
|
||||||
|
pub async fn admin_users_new_form(
|
||||||
|
_admin: JwtAdmin,
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
form: Form<UsersNewForm<'_>>,
|
||||||
|
) -> Result<Flash<Redirect>> {
|
||||||
|
// Parse username
|
||||||
|
let username = match Username::from_str(form.username) {
|
||||||
|
Ok(username) => username,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(admin_users_new)),
|
||||||
|
FlashKind::Danger,
|
||||||
|
INVALID_USERNAME_ERROR,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate UserID
|
||||||
|
let user_id = task::spawn_blocking(UserID::default).await?;
|
||||||
|
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Insert user in database
|
||||||
|
if let Err(e) = User::insert(&mut transaction, &user_id, false, &username, None).await {
|
||||||
|
return Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(admin_users_new)),
|
||||||
|
FlashKind::Danger,
|
||||||
|
e.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
let id = RocketUserID(user_id);
|
||||||
|
Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(admin_users_view(id))),
|
||||||
|
FlashKind::Success,
|
||||||
|
"User has been created.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/admin/users/<id>")]
|
#[get("/admin/users/<id>")]
|
||||||
pub async fn admin_users_view(
|
pub async fn admin_users_view(
|
||||||
admin_not_current: JwtAdminNotCurrent,
|
admin_not_current: JwtAdminNotCurrent,
|
||||||
|
|
|
||||||
61
crates/ezidam/templates/pages/admin/users/new.html.tera
Normal file
61
crates/ezidam/templates/pages/admin/users/new.html.tera
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{% extends "shell" %}
|
||||||
|
|
||||||
|
{% import "utils/form" as form %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="page-pretitle">
|
||||||
|
Admin dashboard
|
||||||
|
</div>
|
||||||
|
<h2 class="page-title">
|
||||||
|
Users
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Page body -->
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
{% if flash %}
|
||||||
|
<div class="alert alert-{{flash.0}}" role="alert">
|
||||||
|
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form id="new_user" action="" method="post" autocomplete="off" class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">New User</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Username -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label required" for="username">Username</label>
|
||||||
|
<div>
|
||||||
|
<input name="username" id="username" type="text" placeholder="Enter username"
|
||||||
|
class="form-control"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form::disable_button_delay_submit(form_id="new_user") }}
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block libs_js %}
|
||||||
|
{% endblock lib_js %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
{% endblock additional_js %}
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
{% if flash %}
|
{% if flash %}
|
||||||
<div class="alert alert-{{flash.0}}" role="alert">
|
<div class="alert alert-{{flash.0}}" role="alert">
|
||||||
<h4 class="alert-title">{{ flash.1 }}</h4>
|
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub const INVALID_USERNAME_ERROR: &str = "Invalid username. Pattern is [a-zA-Z0-9]";
|
pub const INVALID_USERNAME_ERROR: &str = "Invalid username. Pattern is <code>[a-zA-Z0-9]</code>";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct Username(pub String);
|
pub struct Username(pub String);
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,43 @@ impl User {
|
||||||
is_admin: bool,
|
is_admin: bool,
|
||||||
username: &Username,
|
username: &Username,
|
||||||
password: Option<&Password>,
|
password: Option<&Password>,
|
||||||
) -> Result<Option<()>, Error> {
|
) -> Result<(), Error> {
|
||||||
Ok(DatabaseUsers::insert(
|
DatabaseUsers::insert(
|
||||||
conn,
|
conn,
|
||||||
&id.0,
|
&id.0,
|
||||||
is_admin,
|
is_admin,
|
||||||
username.as_ref(),
|
username.as_ref(),
|
||||||
password.map(|p| p.hash()),
|
password.map(|p| p.hash()),
|
||||||
)
|
)
|
||||||
.await?)
|
.await
|
||||||
|
.map_err(|e| match e {
|
||||||
|
DatabaseError::UniqueConstraintPrimaryKey => Error::IdNotAvailable(id.to_string()),
|
||||||
|
DatabaseError::UniqueConstraint(column) => {
|
||||||
|
if &column == "username" {
|
||||||
|
Error::UsernameNotAvailable(username.into())
|
||||||
|
} else {
|
||||||
|
Error::ColumnNotAvailable(column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => e.into(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
// DatabaseUsers::set_username(conn, self.id.as_ref(), username.as_ref())
|
||||||
|
// .await
|
||||||
|
// .map_err(|e| match e {
|
||||||
|
// DatabaseError::UniqueConstraint(column) => {
|
||||||
|
// if &column == "username" {
|
||||||
|
// Error::UsernameNotAvailable(username.into())
|
||||||
|
// } else {
|
||||||
|
// Error::ColumnNotAvailable(column)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// _ => e.into(),
|
||||||
|
// })?;
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_by_id(
|
pub async fn get_by_id(
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ pub enum Error {
|
||||||
#[error("The database column \"{0}\" is not available.")]
|
#[error("The database column \"{0}\" is not available.")]
|
||||||
ColumnNotAvailable(String),
|
ColumnNotAvailable(String),
|
||||||
|
|
||||||
|
#[error("The generated id \"{0}\" is not available. Please retry.")]
|
||||||
|
IdNotAvailable(String),
|
||||||
|
|
||||||
#[error("The username \"{0}\" is not available.")]
|
#[error("The username \"{0}\" is not available.")]
|
||||||
UsernameNotAvailable(String),
|
UsernameNotAvailable(String),
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue