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::{
|
use crate::{
|
||||||
config::Configuration,
|
config::Configuration,
|
||||||
passwords::create_password_hash,
|
passwords::create_password_hash,
|
||||||
store::{IdCoopStore, RoleInfo, User, UserInfo},
|
store::{CreateUser, IdCoopStore, RoleInfo, User, UserInfo},
|
||||||
utils::Clock,
|
utils::Clock,
|
||||||
web::make_login_redirect,
|
web::make_login_redirect,
|
||||||
};
|
};
|
||||||
@ -36,6 +36,10 @@ pub(crate) fn admin_router() -> Router<()> {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(get_admin_users))
|
.route("/", get(get_admin_users))
|
||||||
.route("/users", get(get_admin_users))
|
.route("/users", get(get_admin_users))
|
||||||
|
.route(
|
||||||
|
"/add_user",
|
||||||
|
get(admin_get_add_user).post(admin_post_add_user),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/users/{user_id}",
|
"/users/{user_id}",
|
||||||
get(admin_get_user).post(admin_post_user),
|
get(admin_get_user).post(admin_post_user),
|
||||||
@ -514,3 +518,131 @@ async fn admin_post_add_roles(
|
|||||||
)
|
)
|
||||||
.await
|
.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
|
||||||
"forms/basics": true,
|
"forms/basics": true,
|
||||||
"forms/checkbox-radio-switch": false,
|
"forms/checkbox-radio-switch": true,
|
||||||
"forms/input-color": false,
|
"forms/input-color": false,
|
||||||
"forms/input-date": false,
|
"forms/input-date": false,
|
||||||
"forms/input-file": false,
|
"forms/input-file": false,
|
||||||
|
@ -11,6 +11,9 @@ declare
|
|||||||
// and this will be set automatically.
|
// and this will be set automatically.
|
||||||
param $type = "text"
|
param $type = "text"
|
||||||
|
|
||||||
|
// ID of an element that describes this field.
|
||||||
|
param $aria-describedby = None
|
||||||
|
|
||||||
|
|
||||||
set $minlength = None
|
set $minlength = None
|
||||||
set $maxlength = None
|
set $maxlength = None
|
||||||
@ -18,6 +21,7 @@ set $required = None
|
|||||||
set $email = None
|
set $email = None
|
||||||
set $pattern = None
|
set $pattern = None
|
||||||
|
|
||||||
|
|
||||||
for $validator in $form.info.field_validators($name)
|
for $validator in $form.info.field_validators($name)
|
||||||
match $validator
|
match $validator
|
||||||
MinLength($l) =>
|
MinLength($l) =>
|
||||||
@ -34,7 +38,7 @@ for $validator in $form.info.field_validators($name)
|
|||||||
"$validator"
|
"$validator"
|
||||||
|
|
||||||
|
|
||||||
input {$type, $name, $minlength?, $maxlength?, $required?, $pattern?}
|
input {$type, $name, $minlength?, $maxlength?, $required?, $pattern?, $aria-describedby?}
|
||||||
|
|
||||||
set $errs = $form.errors.__get($name)
|
set $errs = $form.errors.__get($name)
|
||||||
if $errs.len() != 0
|
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
|
h1
|
||||||
@admin_users_title
|
@admin_users_title
|
||||||
|
|
||||||
|
a.outline.lowprofile {href = "/admin/add_user", role = "button"}
|
||||||
|
@admin_add_user_title
|
||||||
|
|
||||||
table.striped
|
table.striped
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
@ -45,4 +48,3 @@ AdminPage {$ambient}
|
|||||||
set $first = false
|
set $first = false
|
||||||
span.font-mono
|
span.font-mono
|
||||||
"${$role}"
|
"${$role}"
|
||||||
|
|
||||||
|
@ -32,3 +32,6 @@ admin_user_add_roles_btn_add = Add role
|
|||||||
admin_user_add_roles_available = Available Roles
|
admin_user_add_roles_available = Available Roles
|
||||||
admin_user_add_roles_finish = Finish
|
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