mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
app: Implement the second part of password reset
This commit is contained in:
parent
9fb4afcf60
commit
4a5abfd395
@ -8,6 +8,7 @@ use crate::{
|
|||||||
login::LoginForm,
|
login::LoginForm,
|
||||||
logout::LogoutButton,
|
logout::LogoutButton,
|
||||||
reset_password_step1::ResetPasswordStep1Form,
|
reset_password_step1::ResetPasswordStep1Form,
|
||||||
|
reset_password_step2::ResetPasswordStep2Form,
|
||||||
router::{AppRoute, Link, NavButton},
|
router::{AppRoute, Link, NavButton},
|
||||||
user_details::UserDetails,
|
user_details::UserDetails,
|
||||||
user_table::UserTable,
|
user_table::UserTable,
|
||||||
@ -193,6 +194,9 @@ impl App {
|
|||||||
AppRoute::StartResetPassword => html! {
|
AppRoute::StartResetPassword => html! {
|
||||||
<ResetPasswordStep1Form />
|
<ResetPasswordStep1Form />
|
||||||
},
|
},
|
||||||
|
AppRoute::FinishResetPassword(token) => html! {
|
||||||
|
<ResetPasswordStep2Form token=token />
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ pub mod login;
|
|||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod remove_user_from_group;
|
pub mod remove_user_from_group;
|
||||||
pub mod reset_password_step1;
|
pub mod reset_password_step1;
|
||||||
|
pub mod reset_password_step2;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod select;
|
pub mod select;
|
||||||
pub mod user_details;
|
pub mod user_details;
|
||||||
|
232
app/src/components/reset_password_step2.rs
Normal file
232
app/src/components/reset_password_step2.rs
Normal file
@ -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<Self>,
|
||||||
|
form: Form<FormModel>,
|
||||||
|
username: Option<String>,
|
||||||
|
opaque_data: Option<opaque::client::registration::ClientRegistration>,
|
||||||
|
route_dispatcher: RouteAgentDispatcher,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
ValidateTokenResponse(Result<String>),
|
||||||
|
FormUpdate,
|
||||||
|
Submit,
|
||||||
|
RegistrationStartResponse(Result<Box<registration::ServerRegistrationStartResponse>>),
|
||||||
|
RegistrationFinishResponse(Result<()>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommonComponent<ResetPasswordStep2Form> for ResetPasswordStep2Form {
|
||||||
|
fn handle_msg(&mut self, msg: <Self as Component>::Message) -> Result<bool> {
|
||||||
|
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<Self> {
|
||||||
|
&mut self.common
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ResetPasswordStep2Form {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
|
let mut component = ResetPasswordStep2Form {
|
||||||
|
common: CommonComponentParts::<Self>::create(props, link),
|
||||||
|
form: yew_form::Form::<FormModel>::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::<Self>::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! {
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{e.to_string() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
type Field = yew_form::Field<FormModel>;
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<h2>{"Reset your password"}</h2>
|
||||||
|
<form
|
||||||
|
class="form">
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="new_password"
|
||||||
|
class="form-label col-sm-2 col-form-label">
|
||||||
|
{"New password*:"}
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<Field
|
||||||
|
form=&self.form
|
||||||
|
field_name="password"
|
||||||
|
class="form-control"
|
||||||
|
class_invalid="is-invalid has-error"
|
||||||
|
class_valid="has-success"
|
||||||
|
autocomplete="new-password"
|
||||||
|
input_type="password"
|
||||||
|
oninput=self.common.callback(|_| Msg::FormUpdate) />
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{&self.form.field_message("password")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="confirm_password"
|
||||||
|
class="form-label col-sm-2 col-form-label">
|
||||||
|
{"Confirm password*:"}
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<Field
|
||||||
|
form=&self.form
|
||||||
|
field_name="confirm_password"
|
||||||
|
class="form-control"
|
||||||
|
class_invalid="is-invalid has-error"
|
||||||
|
class_valid="has-success"
|
||||||
|
autocomplete="new-password"
|
||||||
|
input_type="password"
|
||||||
|
oninput=self.common.callback(|_| Msg::FormUpdate) />
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{&self.form.field_message("confirm_password")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row mt-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary col-sm-1 col-form-label"
|
||||||
|
type="submit"
|
||||||
|
disabled=self.common.is_task_running()
|
||||||
|
onclick=self.common.callback(|e: MouseEvent| {e.prevent_default(); Msg::Submit})>
|
||||||
|
{"Submit"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{ if let Some(e) = &self.common.error {
|
||||||
|
html! {
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{e.to_string() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else { html! {} }
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ pub enum AppRoute {
|
|||||||
Login,
|
Login,
|
||||||
#[to = "/reset-password/step1"]
|
#[to = "/reset-password/step1"]
|
||||||
StartResetPassword,
|
StartResetPassword,
|
||||||
|
#[to = "/reset-password/step2/{token}"]
|
||||||
|
FinishResetPassword(String),
|
||||||
#[to = "/users/create"]
|
#[to = "/users/create"]
|
||||||
CreateUser,
|
CreateUser,
|
||||||
#[to = "/users"]
|
#[to = "/users"]
|
||||||
|
@ -241,7 +241,19 @@ impl HostService {
|
|||||||
&format!("/auth/reset/step1/{}", username),
|
&format!("/auth/reset/step1/{}", username),
|
||||||
yew::format::Nothing,
|
yew::format::Nothing,
|
||||||
callback,
|
callback,
|
||||||
"Could not logout",
|
"Could not initiate password reset",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_password_step2(
|
||||||
|
token: &str,
|
||||||
|
callback: Callback<Result<String>>,
|
||||||
|
) -> Result<FetchTask> {
|
||||||
|
call_server_json_with_error_message(
|
||||||
|
&format!("/auth/reset/step2/{}", token),
|
||||||
|
yew::format::Nothing,
|
||||||
|
callback,
|
||||||
|
"Could not validate token",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user