From 9fb4afcf60d7b689a1399446a86843d6c07456df Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Mon, 22 Nov 2021 23:04:09 +0100 Subject: [PATCH] app: Implement the first screen of password reset --- app/src/components/app.rs | 85 +++++++------ app/src/components/login.rs | 17 ++- app/src/components/mod.rs | 1 + app/src/components/reset_password_step1.rs | 140 +++++++++++++++++++++ app/src/components/router.rs | 2 + app/src/infra/api.rs | 12 ++ 6 files changed, 218 insertions(+), 39 deletions(-) create mode 100644 app/src/components/reset_password_step1.rs diff --git a/app/src/components/app.rs b/app/src/components/app.rs index ebc50c6..613e583 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -7,6 +7,7 @@ use crate::{ group_table::GroupTable, login::LoginForm, logout::LogoutButton, + reset_password_step1::ResetPasswordStep1Form, router::{AppRoute, Link, NavButton}, user_details::UserDetails, user_table::UserTable, @@ -101,40 +102,7 @@ impl Component for App {
- render = Router::render(move |switch: AppRoute| { - match switch { - AppRoute::Login => html! { - - }, - AppRoute::CreateUser => html! { - - }, - AppRoute::Index | AppRoute::ListUsers => html! { -
- - {"Create a user"} -
- }, - AppRoute::CreateGroup => html! { - - }, - AppRoute::ListGroups => html! { -
- - {"Create a group"} -
- }, - AppRoute::GroupDetails(group_id) => html! { - - }, - AppRoute::UserDetails(username) => html! { - - }, - AppRoute::ChangePassword(username) => html! { - - } - } - }) + render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin)) />
@@ -147,7 +115,11 @@ impl App { fn get_redirect_route() -> Option { let route_service = RouteService::<()>::new(); let current_route = route_service.get_path(); - if current_route.is_empty() || current_route == "/" || current_route.contains("login") { + if current_route.is_empty() + || current_route == "/" + || current_route.contains("login") + || current_route.contains("reset-password") + { None } else { use yew_router::Switch; @@ -156,6 +128,11 @@ impl App { } fn apply_initial_redirections(&mut self) { + let route_service = RouteService::<()>::new(); + let current_route = route_service.get_path(); + if current_route.contains("reset-password") { + return; + } match &self.user_info { None => { self.route_dispatcher @@ -181,6 +158,44 @@ impl App { } } + fn dispatch_route(switch: AppRoute, link: &ComponentLink, is_admin: bool) -> Html { + match switch { + AppRoute::Login => html! { + + }, + AppRoute::CreateUser => html! { + + }, + AppRoute::Index | AppRoute::ListUsers => html! { +
+ + {"Create a user"} +
+ }, + AppRoute::CreateGroup => html! { + + }, + AppRoute::ListGroups => html! { +
+ + {"Create a group"} +
+ }, + AppRoute::GroupDetails(group_id) => html! { + + }, + AppRoute::UserDetails(username) => html! { + + }, + AppRoute::ChangePassword(username) => html! { + + }, + AppRoute::StartResetPassword => html! { + + }, + } + } + fn view_banner(&self) -> Html { html! {
diff --git a/app/src/components/login.rs b/app/src/components/login.rs index 9c86a22..ff42133 100644 --- a/app/src/components/login.rs +++ b/app/src/components/login.rs @@ -1,6 +1,9 @@ -use crate::infra::{ - api::HostService, - common_component::{CommonComponent, CommonComponentParts}, +use crate::{ + components::router::{AppRoute, NavButton}, + infra::{ + api::HostService, + common_component::{CommonComponent, CommonComponentParts}, + }, }; use anyhow::{anyhow, bail, Context, Result}; use lldap_auth::*; @@ -160,7 +163,7 @@ impl Component for LoginForm { placeholder="Password" autocomplete="current-password" /> -
+
+ + {"Forgot your password?"} +
{ if let Some(e) = &self.common.error { diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index 8bcd281..970a08a 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -11,6 +11,7 @@ pub mod group_table; pub mod login; pub mod logout; pub mod remove_user_from_group; +pub mod reset_password_step1; pub mod router; pub mod select; pub mod user_details; diff --git a/app/src/components/reset_password_step1.rs b/app/src/components/reset_password_step1.rs new file mode 100644 index 0000000..6181152 --- /dev/null +++ b/app/src/components/reset_password_step1.rs @@ -0,0 +1,140 @@ +use crate::{ + components::router::{AppRoute, NavButton}, + infra::{ + api::HostService, + common_component::{CommonComponent, CommonComponentParts}, + }, +}; +use anyhow::{bail, Result}; +use validator_derive::Validate; +use yew::prelude::*; +use yew_form::Form; +use yew_form_derive::Model; + +pub struct ResetPasswordStep1Form { + common: CommonComponentParts, + form: Form, + just_succeeded: bool, +} + +/// The fields of the form, with the constraints. +#[derive(Model, Validate, PartialEq, Clone, Default)] +pub struct FormModel { + #[validate(length(min = 1, message = "Missing username"))] + username: String, +} + +pub enum Msg { + Update, + Submit, + PasswordResetResponse(Result<()>), +} + +impl CommonComponent for ResetPasswordStep1Form { + fn handle_msg(&mut self, msg: ::Message) -> Result { + match msg { + Msg::Update => Ok(true), + Msg::Submit => { + if !self.form.validate() { + bail!("Check the form for errors"); + } + let FormModel { username } = self.form.model(); + self.common.call_backend( + HostService::reset_password_step1, + &username, + Msg::PasswordResetResponse, + )?; + Ok(true) + } + Msg::PasswordResetResponse(response) => { + response?; + self.just_succeeded = true; + Ok(true) + } + } + } + + fn mut_common(&mut self) -> &mut CommonComponentParts { + &mut self.common + } +} + +impl Component for ResetPasswordStep1Form { + type Message = Msg; + type Properties = (); + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + ResetPasswordStep1Form { + common: CommonComponentParts::::create(props, link), + form: Form::::new(FormModel::default()), + just_succeeded: false, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + self.just_succeeded = false; + CommonComponentParts::::update(self, msg) + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + type Field = yew_form::Field; + html! { +
+
+
+ + + +
+ +
+ { if self.just_succeeded { + html! { + {"A reset token has been sent to your email."} + } + } else { + html! { +
+ + + {"Back"} + +
+ } + }} +
+ { if let Some(e) = &self.common.error { + html! { +
+ {e.to_string() } +
+ } + } else { html! {} } + } +
+ + } + } +} diff --git a/app/src/components/router.rs b/app/src/components/router.rs index 7831fa5..4a1c488 100644 --- a/app/src/components/router.rs +++ b/app/src/components/router.rs @@ -7,6 +7,8 @@ use yew_router::{ pub enum AppRoute { #[to = "/login"] Login, + #[to = "/reset-password/step1"] + StartResetPassword, #[to = "/users/create"] CreateUser, #[to = "/users"] diff --git a/app/src/infra/api.rs b/app/src/infra/api.rs index c63203f..6319023 100644 --- a/app/src/infra/api.rs +++ b/app/src/infra/api.rs @@ -232,4 +232,16 @@ impl HostService { "Could not logout", ) } + + pub fn reset_password_step1( + username: &str, + callback: Callback>, + ) -> Result { + call_server_empty_response_with_error_message( + &format!("/auth/reset/step1/{}", username), + yew::format::Nothing, + callback, + "Could not logout", + ) + } }