Add admin UI for creating a user
This commit is contained in:
parent
9c966ea7f1
commit
f988388e50
@ -21,7 +21,7 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
config::Configuration,
|
||||
passwords::create_password_hash,
|
||||
store::{IdCoopStore, RoleInfo, User, UserInfo},
|
||||
store::{CreateUser, IdCoopStore, RoleInfo, User, UserInfo},
|
||||
utils::Clock,
|
||||
web::make_login_redirect,
|
||||
};
|
||||
@ -36,6 +36,10 @@ pub(crate) fn admin_router() -> Router<()> {
|
||||
Router::new()
|
||||
.route("/", get(get_admin_users))
|
||||
.route("/users", get(get_admin_users))
|
||||
.route(
|
||||
"/add_user",
|
||||
get(admin_get_add_user).post(admin_post_add_user),
|
||||
)
|
||||
.route(
|
||||
"/users/{user_id}",
|
||||
get(admin_get_user).post(admin_post_user),
|
||||
@ -514,3 +518,131 @@ async fn admin_post_add_roles(
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(formbeam_derive::Form)]
|
||||
struct AddUserForm {
|
||||
#[form(min_chars(3), max_chars(36), regex(r"[a-z][a-z0-9]+"))]
|
||||
username: String,
|
||||
|
||||
locked: bool,
|
||||
|
||||
/// Hidden XSRF token, used to check this request isn't being made from another site
|
||||
/// (i.e. protects against cross-site request forgery)
|
||||
xsrf: String,
|
||||
}
|
||||
|
||||
async fn admin_get_add_user(
|
||||
ambient: Ambient,
|
||||
login_session: LoginSession,
|
||||
Extension(clock): Extension<Clock>,
|
||||
DesiredLocale(locale): DesiredLocale,
|
||||
) -> WebResult<Response> {
|
||||
let form = ReflectedForm::<AddUserFormRaw>::default();
|
||||
|
||||
let xsrf_token = login_session
|
||||
.generate_xsrf_token(clock.now_utc())
|
||||
.expect("must be able to create a XSRF token");
|
||||
|
||||
Ok(Rendered(
|
||||
render_template_string!(TEMPLATING, admin_add_user, locale, {
|
||||
ambient,
|
||||
form,
|
||||
xsrf_token
|
||||
}),
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
async fn admin_post_add_user(
|
||||
ambient: Ambient,
|
||||
Extension(store): Extension<Arc<IdCoopStore>>,
|
||||
DesiredLocale(locale): DesiredLocale,
|
||||
login_session: LoginSession,
|
||||
Extension(clock): Extension<Clock>,
|
||||
Form(form_raw): Form<AddUserFormRaw>,
|
||||
) -> WebResult<Response> {
|
||||
let mut validation = form_raw
|
||||
.validate()
|
||||
.await
|
||||
.context("failed to run form validator")?;
|
||||
|
||||
if !validation.is_valid() {
|
||||
let form = ReflectedForm::new(form_raw, validation);
|
||||
let xsrf_token = login_session
|
||||
.generate_xsrf_token(clock.now_utc())
|
||||
.expect("must be able to create a XSRF token");
|
||||
|
||||
return Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Rendered(
|
||||
render_template_string!(TEMPLATING, admin_add_user, locale, {
|
||||
ambient,
|
||||
form,
|
||||
xsrf_token
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
// INVARIANT: Form fields are syntactically valid at this point (but note XSRF not checked)
|
||||
|
||||
let form = form_raw
|
||||
.form()
|
||||
.map_err(|e| eyre!("failed to extract a so-called valid form: {e}"))?;
|
||||
|
||||
if login_session
|
||||
.validate_xsrf_token(&form.xsrf, clock.now_utc())
|
||||
.is_err()
|
||||
{
|
||||
// Invalid XSRF token: try again
|
||||
validation.xsrf.push(errors::xsrf_invalid());
|
||||
|
||||
let form = ReflectedForm::new(form_raw, validation);
|
||||
let xsrf_token = login_session
|
||||
.generate_xsrf_token(clock.now_utc())
|
||||
.expect("must be able to create a XSRF token");
|
||||
|
||||
return Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Rendered(
|
||||
render_template_string!(TEMPLATING, admin_add_user, locale, {
|
||||
ambient,
|
||||
form,
|
||||
xsrf_token
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
// INVARIANT: Form validated at this point
|
||||
|
||||
// Create the user
|
||||
let user_id = store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move {
|
||||
let user_id = txn
|
||||
.create_user(CreateUser {
|
||||
user_login_name: form
|
||||
.username
|
||||
.parse()
|
||||
.expect("form validation should already know username is valid"),
|
||||
password_hash: None,
|
||||
locked: form.locked,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(user_id)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Redirect to the user page
|
||||
Ok((
|
||||
StatusCode::FOUND,
|
||||
[("Location", format!("/admin/users/{user_id}"))],
|
||||
"User created successfully. Taking you to the user page.",
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
// Forms
|
||||
"forms/basics": true,
|
||||
"forms/checkbox-radio-switch": false,
|
||||
"forms/checkbox-radio-switch": true,
|
||||
"forms/input-color": false,
|
||||
"forms/input-date": false,
|
||||
"forms/input-file": false,
|
||||
|
@ -11,6 +11,9 @@ declare
|
||||
// and this will be set automatically.
|
||||
param $type = "text"
|
||||
|
||||
// ID of an element that describes this field.
|
||||
param $aria-describedby = None
|
||||
|
||||
|
||||
set $minlength = None
|
||||
set $maxlength = None
|
||||
@ -18,6 +21,7 @@ set $required = None
|
||||
set $email = None
|
||||
set $pattern = None
|
||||
|
||||
|
||||
for $validator in $form.info.field_validators($name)
|
||||
match $validator
|
||||
MinLength($l) =>
|
||||
@ -34,7 +38,7 @@ for $validator in $form.info.field_validators($name)
|
||||
"$validator"
|
||||
|
||||
|
||||
input {$type, $name, $minlength?, $maxlength?, $required?, $pattern?}
|
||||
input {$type, $name, $minlength?, $maxlength?, $required?, $pattern?, $aria-describedby?}
|
||||
|
||||
set $errs = $form.errors.__get($name)
|
||||
if $errs.len() != 0
|
||||
|
38
templates/pages/admin_add_user.hnb
Normal file
38
templates/pages/admin_add_user.hnb
Normal file
@ -0,0 +1,38 @@
|
||||
AdminPage {$ambient}
|
||||
:title
|
||||
@admin_add_user_title
|
||||
|
||||
:main
|
||||
nav {aria-label = "breadcrumb"}
|
||||
ul
|
||||
li
|
||||
a {href = "/admin/users"}
|
||||
@admin_nav_users
|
||||
li
|
||||
@admin_add_user_title
|
||||
|
||||
article
|
||||
form {method = "POST"}
|
||||
set $errs = $form.errors.form_wide
|
||||
if $errs.len() != 0
|
||||
@form_errors{count = $errs.len()}
|
||||
|
||||
ul
|
||||
for $err in $errs
|
||||
li
|
||||
@login_error{errcode = $err.error_code()}
|
||||
|
||||
fieldset
|
||||
label
|
||||
@admin_users_attr_name
|
||||
Text {$form, name = "username", aria-describedby="username-help"}
|
||||
small#username-help
|
||||
@admin_add_user_username_help
|
||||
|
||||
label
|
||||
input {type = "checkbox", name = "locked"}
|
||||
@admin_users_attr_locked
|
||||
|
||||
input {type = "hidden", name = "xsrf", value = $xsrf_token}
|
||||
button {type = "submit"}
|
||||
@admin_add_user_submit
|
@ -6,6 +6,9 @@ AdminPage {$ambient}
|
||||
h1
|
||||
@admin_users_title
|
||||
|
||||
a.outline.lowprofile {href = "/admin/add_user", role = "button"}
|
||||
@admin_add_user_title
|
||||
|
||||
table.striped
|
||||
thead
|
||||
tr
|
||||
@ -45,4 +48,3 @@ AdminPage {$ambient}
|
||||
set $first = false
|
||||
span.font-mono
|
||||
"${$role}"
|
||||
|
||||
|
@ -32,3 +32,6 @@ admin_user_add_roles_btn_add = Add role
|
||||
admin_user_add_roles_available = Available Roles
|
||||
admin_user_add_roles_finish = Finish
|
||||
|
||||
admin_add_user_title = Add user
|
||||
admin_add_user_submit = Add user
|
||||
admin_add_user_username_help = This is the login name of the user and can't be changed later. Must only contain lowercase letters and numbers, but can't start with a number. Minimum of 3 characters. Maximum of 36 characters.
|
||||
|
Loading…
x
Reference in New Issue
Block a user