use crate::{ components::router::AppRoute, infra::{ api::HostService, common_component::{CommonComponent, CommonComponentParts}, }, }; use anyhow::{bail, Context, Result}; use graphql_client::GraphQLQuery; use lldap_auth::{opaque, registration}; use validator_derive::Validate; use yew::prelude::*; use yew::services::ConsoleService; use yew_form_derive::Model; use yew_router::{ agent::{RouteAgentDispatcher, RouteRequest}, route::Route, }; #[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, route_dispatcher: RouteAgentDispatcher, 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, 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::( req, Msg::CreateUserResponse, "Error trying to create user", ); Ok(true) } Msg::CreateUserResponse(r) => { match r { Err(e) => return Err(e), Ok(r) => ConsoleService::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(HostService::register_start, req, move |r| { Msg::RegistrationStartResponse((state, r)) }) .context("Error trying to create user")?; } else { self.update(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( HostService::register_finish, req, Msg::RegistrationFinishResponse, ) .context("Error trying to register user")?; Ok(false) } Msg::RegistrationFinishResponse(response) => { response?; self.handle_msg(Msg::SuccessfulCreation) } Msg::SuccessfulCreation => { self.route_dispatcher .send(RouteRequest::ChangeRoute(Route::from(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(props: Self::Properties, link: ComponentLink) -> Self { Self { common: CommonComponentParts::::create(props, link), route_dispatcher: RouteAgentDispatcher::new(), form: yew_form::Form::::new(CreateUserModel::default()), } } fn update(&mut self, msg: Self::Message) -> ShouldRender { CommonComponentParts::::update(self, msg) } fn change(&mut self, props: Self::Properties) -> ShouldRender { self.common.change(props) } fn view(&self) -> Html { let link = &self.common; 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! {} } }
} } }