use crate::{ components::router::AppRoute, infra::{ api::HostService, common_component::{CommonComponent, CommonComponentParts}, }, }; use anyhow::{bail, Result}; use gloo_console::log; use graphql_client::GraphQLQuery; use lldap_auth::{opaque, registration}; use validator_derive::Validate; use yew::prelude::*; use yew_form_derive::Model; use yew_router::{prelude::History, scope_ext::RouterScopeExt}; #[derive(GraphQLQuery)] #[graphql( schema_path = "../schema.graphql", query_path = "queries/create_user.graphql", response_derives = "Debug", custom_scalars_module = "crate::infra::graphql" )] pub struct CreateUser; pub struct CreateUserForm { common: CommonComponentParts, form: yew_form::Form, } #[derive(Model, Validate, PartialEq, Eq, Clone, Default)] pub struct CreateUserModel { #[validate(length(min = 1, message = "Username is required"))] username: String, #[validate(email(message = "A valid email is required"))] email: String, display_name: String, first_name: String, last_name: String, #[validate(custom( function = "empty_or_long", message = "Password should be longer than 8 characters (or left empty)" ))] password: String, #[validate(must_match(other = "password", message = "Passwords must match"))] confirm_password: String, } fn empty_or_long(value: &str) -> Result<(), validator::ValidationError> { if value.is_empty() || value.len() >= 8 { Ok(()) } else { Err(validator::ValidationError::new("")) } } pub enum Msg { Update, SubmitForm, CreateUserResponse(Result), SuccessfulCreation, RegistrationStartResponse( ( opaque::client::registration::ClientRegistration, Result>, ), ), RegistrationFinishResponse(Result<()>), } impl CommonComponent for CreateUserForm { fn handle_msg( &mut self, ctx: &Context, msg: ::Message, ) -> Result { match msg { Msg::Update => Ok(true), Msg::SubmitForm => { if !self.form.validate() { bail!("Check the form for errors"); } let model = self.form.model(); let to_option = |s: String| if s.is_empty() { None } else { Some(s) }; let req = create_user::Variables { user: create_user::CreateUserInput { id: model.username, email: model.email, displayName: to_option(model.display_name), firstName: to_option(model.first_name), lastName: to_option(model.last_name), avatar: None, }, }; self.common.call_graphql::( ctx, req, Msg::CreateUserResponse, "Error trying to create user", ); Ok(true) } Msg::CreateUserResponse(r) => { match r { Err(e) => return Err(e), Ok(r) => log!(&format!( "Created user '{}' at '{}'", &r.create_user.id, &r.create_user.creation_date )), }; let model = self.form.model(); let user_id = model.username; let password = model.password; if !password.is_empty() { // User was successfully created, let's register the password. let mut rng = rand::rngs::OsRng; let opaque::client::registration::ClientRegistrationStartResult { state, message, } = opaque::client::registration::start_registration(&password, &mut rng)?; let req = registration::ClientRegistrationStartRequest { username: user_id, registration_start_request: message, }; self.common .call_backend(ctx, HostService::register_start(req), move |r| { Msg::RegistrationStartResponse((state, r)) }); } else { self.update(ctx, Msg::SuccessfulCreation); } Ok(false) } Msg::RegistrationStartResponse((registration_start, response)) => { let response = response?; let mut rng = rand::rngs::OsRng; let registration_upload = opaque::client::registration::finish_registration( registration_start, response.registration_response, &mut rng, )?; let req = registration::ClientRegistrationFinishRequest { server_data: response.server_data, registration_upload: registration_upload.message, }; self.common.call_backend( ctx, HostService::register_finish(req), Msg::RegistrationFinishResponse, ); Ok(false) } Msg::RegistrationFinishResponse(response) => { response?; self.handle_msg(ctx, Msg::SuccessfulCreation) } Msg::SuccessfulCreation => { ctx.link().history().unwrap().push(AppRoute::ListUsers); Ok(true) } } } fn mut_common(&mut self) -> &mut CommonComponentParts { &mut self.common } } impl Component for CreateUserForm { type Message = Msg; type Properties = (); fn create(_: &Context) -> Self { Self { common: CommonComponentParts::::create(), form: yew_form::Form::::new(CreateUserModel::default()), } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { CommonComponentParts::::update(self, ctx, msg) } fn view(&self, ctx: &Context) -> Html { let link = &ctx.link(); type Field = yew_form::Field; html! {
{"Create a user"}
{&self.form.field_message("username")}
{&self.form.field_message("email")}
{&self.form.field_message("display_name")}
{&self.form.field_message("first_name")}
{&self.form.field_message("last_name")}
{&self.form.field_message("password")}
{&self.form.field_message("confirm_password")}
{ if let Some(e) = &self.common.error { html! {
{e.to_string() }
} } else { html! {} } }
} } }