use crate::{components::router::AppRoute, infra::api::HostService}; use anyhow::{bail, Context, Result}; use graphql_client::GraphQLQuery; use lldap_auth::{opaque, registration}; use validator_derive::Validate; use yew::prelude::*; use yew::services::{fetch::FetchTask, 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 { link: ComponentLink, route_dispatcher: RouteAgentDispatcher, form: yew_form::Form, error: Option, // Used to keep the request alive long enough. task: Option, } #[derive(Model, Validate, PartialEq, 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, #[validate(length(min = 1, message = "Display name is required"))] 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 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), }, }; self.task = Some(HostService::graphql_query::( req, self.link.callback(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.task = Some( HostService::register_start( req, self.link .callback_once(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.task = Some( HostService::register_finish( req, self.link.callback(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) } } } } impl Component for CreateUserForm { type Message = Msg; type Properties = (); fn create(_: Self::Properties, link: ComponentLink) -> Self { Self { link, route_dispatcher: RouteAgentDispatcher::new(), form: yew_form::Form::::new(CreateUserModel::default()), error: None, task: None, } } fn update(&mut self, msg: Self::Message) -> ShouldRender { self.error = None; match self.handle_msg(msg) { Err(e) => { ConsoleService::error(&e.to_string()); self.error = Some(e); self.task = None; true } Ok(b) => b, } } fn change(&mut self, _: Self::Properties) -> ShouldRender { false } fn view(&self) -> Html { type Field = yew_form::Field; html! { <>
{&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.error { html! {
{e.to_string() }
} } else { html! {} } } } } }