From 4a5abfd395fb585ff3eabb856e74ae24ec2ba709 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Mon, 22 Nov 2021 23:30:12 +0100 Subject: [PATCH] app: Implement the second part of password reset --- app/src/components/app.rs | 4 + app/src/components/mod.rs | 1 + app/src/components/reset_password_step2.rs | 232 +++++++++++++++++++++ app/src/components/router.rs | 2 + app/src/infra/api.rs | 14 +- 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 app/src/components/reset_password_step2.rs diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 613e583..9a8f080 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -8,6 +8,7 @@ use crate::{ login::LoginForm, logout::LogoutButton, reset_password_step1::ResetPasswordStep1Form, + reset_password_step2::ResetPasswordStep2Form, router::{AppRoute, Link, NavButton}, user_details::UserDetails, user_table::UserTable, @@ -193,6 +194,9 @@ impl App { AppRoute::StartResetPassword => html! { }, + AppRoute::FinishResetPassword(token) => html! { + + }, } } diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index 970a08a..f78dcf9 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -12,6 +12,7 @@ pub mod login; pub mod logout; pub mod remove_user_from_group; pub mod reset_password_step1; +pub mod reset_password_step2; pub mod router; pub mod select; pub mod user_details; diff --git a/app/src/components/reset_password_step2.rs b/app/src/components/reset_password_step2.rs new file mode 100644 index 0000000..45b59e1 --- /dev/null +++ b/app/src/components/reset_password_step2.rs @@ -0,0 +1,232 @@ +use crate::{ + components::router::AppRoute, + infra::{ + api::HostService, + common_component::{CommonComponent, CommonComponentParts}, + }, +}; +use anyhow::{bail, Context, Result}; +use lldap_auth::*; +use validator_derive::Validate; +use yew::prelude::*; +use yew_form::Form; +use yew_form_derive::Model; +use yew_router::{ + agent::{RouteAgentDispatcher, RouteRequest}, + route::Route, +}; + +/// The fields of the form, with the constraints. +#[derive(Model, Validate, PartialEq, Clone, Default)] +pub struct FormModel { + #[validate(length(min = 8, message = "Invalid password. Min length: 8"))] + password: String, + #[validate(must_match(other = "password", message = "Passwords must match"))] + confirm_password: String, +} + +pub struct ResetPasswordStep2Form { + common: CommonComponentParts, + form: Form, + username: Option, + opaque_data: Option, + route_dispatcher: RouteAgentDispatcher, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct Props { + pub token: String, +} + +pub enum Msg { + ValidateTokenResponse(Result), + FormUpdate, + Submit, + RegistrationStartResponse(Result>), + RegistrationFinishResponse(Result<()>), +} + +impl CommonComponent for ResetPasswordStep2Form { + fn handle_msg(&mut self, msg: ::Message) -> Result { + match msg { + Msg::ValidateTokenResponse(response) => { + self.username = Some(response?); + self.common.cancel_task(); + Ok(true) + } + Msg::FormUpdate => Ok(true), + Msg::Submit => { + if !self.form.validate() { + bail!("Check the form for errors"); + } + let mut rng = rand::rngs::OsRng; + let new_password = self.form.model().password; + let registration_start_request = + opaque::client::registration::start_registration(&new_password, &mut rng) + .context("Could not initiate password change")?; + let req = registration::ClientRegistrationStartRequest { + username: self.username.clone().unwrap(), + registration_start_request: registration_start_request.message, + }; + self.opaque_data = Some(registration_start_request.state); + self.common.call_backend( + HostService::register_start, + req, + Msg::RegistrationStartResponse, + )?; + Ok(true) + } + Msg::RegistrationStartResponse(res) => { + let res = res.context("Could not initiate password change")?; + let registration = self.opaque_data.take().expect("Missing registration data"); + let mut rng = rand::rngs::OsRng; + let registration_finish = opaque::client::registration::finish_registration( + registration, + res.registration_response, + &mut rng, + ) + .context("Error during password change")?; + let req = registration::ClientRegistrationFinishRequest { + server_data: res.server_data, + registration_upload: registration_finish.message, + }; + self.common.call_backend( + HostService::register_finish, + req, + Msg::RegistrationFinishResponse, + )?; + Ok(false) + } + Msg::RegistrationFinishResponse(response) => { + self.common.cancel_task(); + if response.is_ok() { + self.route_dispatcher + .send(RouteRequest::ChangeRoute(Route::from(AppRoute::Login))); + } + response?; + Ok(true) + } + } + } + + fn mut_common(&mut self) -> &mut CommonComponentParts { + &mut self.common + } +} + +impl Component for ResetPasswordStep2Form { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + let mut component = ResetPasswordStep2Form { + common: CommonComponentParts::::create(props, link), + form: yew_form::Form::::new(FormModel::default()), + opaque_data: None, + route_dispatcher: RouteAgentDispatcher::new(), + username: None, + }; + let token = component.common.token.clone(); + component + .common + .call_backend( + HostService::reset_password_step2, + &token, + Msg::ValidateTokenResponse, + ) + .unwrap(); + component + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + CommonComponentParts::::update(self, msg) + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + match (&self.username, &self.common.error) { + (None, None) => { + return html! { + {"Validating token"} + } + } + (None, Some(e)) => { + return html! { +
+ {e.to_string() } +
+ } + } + _ => (), + }; + type Field = yew_form::Field; + html! { + <> +

{"Reset your password"}

+
+
+ +
+ +
+ {&self.form.field_message("password")} +
+
+
+
+ +
+ +
+ {&self.form.field_message("confirm_password")} +
+
+
+
+ +
+
+ { 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 4a1c488..25dd2f2 100644 --- a/app/src/components/router.rs +++ b/app/src/components/router.rs @@ -9,6 +9,8 @@ pub enum AppRoute { Login, #[to = "/reset-password/step1"] StartResetPassword, + #[to = "/reset-password/step2/{token}"] + FinishResetPassword(String), #[to = "/users/create"] CreateUser, #[to = "/users"] diff --git a/app/src/infra/api.rs b/app/src/infra/api.rs index 6319023..663e3e8 100644 --- a/app/src/infra/api.rs +++ b/app/src/infra/api.rs @@ -241,7 +241,19 @@ impl HostService { &format!("/auth/reset/step1/{}", username), yew::format::Nothing, callback, - "Could not logout", + "Could not initiate password reset", + ) + } + + pub fn reset_password_step2( + token: &str, + callback: Callback>, + ) -> Result { + call_server_json_with_error_message( + &format!("/auth/reset/step2/{}", token), + yew::format::Nothing, + callback, + "Could not validate token", ) } }