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