app: probe for password reset support

This commit is contained in:
Valentin Tolmer 2023-02-13 20:09:24 +01:00 committed by nitnelave
parent 562ad524c4
commit 62104b417a
3 changed files with 88 additions and 19 deletions

View File

@ -13,10 +13,13 @@ use crate::{
user_details::UserDetails, user_details::UserDetails,
user_table::UserTable, user_table::UserTable,
}, },
infra::cookies::get_cookie, infra::{api::HostService, cookies::get_cookie},
};
use yew::{
prelude::*,
services::{fetch::FetchTask, ConsoleService},
}; };
use yew::prelude::*;
use yew::services::ConsoleService;
use yew_router::{ use yew_router::{
agent::{RouteAgentDispatcher, RouteRequest}, agent::{RouteAgentDispatcher, RouteRequest},
route::Route, route::Route,
@ -29,11 +32,14 @@ pub struct App {
user_info: Option<(String, bool)>, user_info: Option<(String, bool)>,
redirect_to: Option<AppRoute>, redirect_to: Option<AppRoute>,
route_dispatcher: RouteAgentDispatcher, route_dispatcher: RouteAgentDispatcher,
password_reset_enabled: bool,
task: Option<FetchTask>,
} }
pub enum Msg { pub enum Msg {
Login((String, bool)), Login((String, bool)),
Logout, Logout,
PasswordResetProbeFinished(anyhow::Result<bool>),
} }
impl Component for App { impl Component for App {
@ -58,7 +64,15 @@ impl Component for App {
}), }),
redirect_to: Self::get_redirect_route(), redirect_to: Self::get_redirect_route(),
route_dispatcher: RouteAgentDispatcher::new(), route_dispatcher: RouteAgentDispatcher::new(),
password_reset_enabled: false,
task: None,
}; };
app.task = Some(
HostService::probe_password_reset(
app.link.callback_once(Msg::PasswordResetProbeFinished),
)
.unwrap(),
);
app.apply_initial_redirections(); app.apply_initial_redirections();
app app
} }
@ -82,6 +96,16 @@ impl Component for App {
self.user_info = None; self.user_info = None;
self.redirect_to = None; self.redirect_to = None;
} }
Msg::PasswordResetProbeFinished(Ok(enabled)) => {
self.task = None;
self.password_reset_enabled = enabled;
}
Msg::PasswordResetProbeFinished(Err(err)) => {
self.task = None;
ConsoleService::error(&format!(
"Could not probe for password reset support: {err:#}"
));
}
} }
if self.user_info.is_none() { if self.user_info.is_none() {
self.route_dispatcher self.route_dispatcher
@ -97,6 +121,7 @@ impl Component for App {
fn view(&self) -> Html { fn view(&self) -> Html {
let link = self.link.clone(); let link = self.link.clone();
let is_admin = self.is_admin(); let is_admin = self.is_admin();
let password_reset_enabled = self.password_reset_enabled;
html! { html! {
<div> <div>
{self.view_banner()} {self.view_banner()}
@ -104,7 +129,7 @@ impl Component for App {
<div class="row justify-content-center" style="padding-bottom: 80px;"> <div class="row justify-content-center" style="padding-bottom: 80px;">
<div class="py-3" style="max-width: 1000px"> <div class="py-3" style="max-width: 1000px">
<Router<AppRoute> <Router<AppRoute>
render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin)) render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin, password_reset_enabled))
/> />
</div> </div>
</div> </div>
@ -135,6 +160,10 @@ impl App {
let route_service = RouteService::<()>::new(); let route_service = RouteService::<()>::new();
let current_route = route_service.get_path(); let current_route = route_service.get_path();
if current_route.contains("reset-password") { if current_route.contains("reset-password") {
if !self.password_reset_enabled {
self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
}
return; return;
} }
match &self.user_info { match &self.user_info {
@ -162,10 +191,15 @@ impl App {
} }
} }
fn dispatch_route(switch: AppRoute, link: &ComponentLink<Self>, is_admin: bool) -> Html { fn dispatch_route(
switch: AppRoute,
link: &ComponentLink<Self>,
is_admin: bool,
password_reset_enabled: bool,
) -> Html {
match switch { match switch {
AppRoute::Login => html! { AppRoute::Login => html! {
<LoginForm on_logged_in=link.callback(Msg::Login)/> <LoginForm on_logged_in=link.callback(Msg::Login) password_reset_enabled=password_reset_enabled/>
}, },
AppRoute::CreateUser => html! { AppRoute::CreateUser => html! {
<CreateUserForm/> <CreateUserForm/>
@ -200,11 +234,23 @@ impl App {
AppRoute::ChangePassword(username) => html! { AppRoute::ChangePassword(username) => html! {
<ChangePasswordForm username=username is_admin=is_admin /> <ChangePasswordForm username=username is_admin=is_admin />
}, },
AppRoute::StartResetPassword => html! { AppRoute::StartResetPassword => {
<ResetPasswordStep1Form /> if password_reset_enabled {
}, html! {
<ResetPasswordStep1Form />
}
} else {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
}
}
AppRoute::FinishResetPassword(token) => html! { AppRoute::FinishResetPassword(token) => html! {
<ResetPasswordStep2Form token=token /> if password_reset_enabled {
html! {
<ResetPasswordStep2Form token=token />
}
} else {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
}
}, },
} }
} }

View File

@ -30,6 +30,7 @@ pub struct FormModel {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub on_logged_in: Callback<(String, bool)>, pub on_logged_in: Callback<(String, bool)>,
pub password_reset_enabled: bool,
} }
pub enum Msg { pub enum Msg {
@ -147,6 +148,7 @@ impl Component for LoginForm {
fn view(&self) -> Html { fn view(&self) -> Html {
type Field = yew_form::Field<FormModel>; type Field = yew_form::Field<FormModel>;
let password_reset_enabled = self.common.password_reset_enabled;
if self.refreshing { if self.refreshing {
html! { html! {
<div> <div>
@ -198,12 +200,18 @@ impl Component for LoginForm {
<i class="bi-box-arrow-in-right me-2"/> <i class="bi-box-arrow-in-right me-2"/>
{"Login"} {"Login"}
</button> </button>
<NavButton { if password_reset_enabled {
classes="btn-link btn" html! {
disabled=self.common.is_task_running() <NavButton
route=AppRoute::StartResetPassword> classes="btn-link btn"
{"Forgot your password?"} disabled=self.common.is_task_running()
</NavButton> route=AppRoute::StartResetPassword>
{"Forgot your password?"}
</NavButton>
}
} else {
html!{}
}}
</div> </div>
<div class="form-group"> <div class="form-group">
{ if let Some(e) = &self.common.error { { if let Some(e) = &self.common.error {

View File

@ -3,9 +3,11 @@ use anyhow::{anyhow, Context, Result};
use graphql_client::GraphQLQuery; use graphql_client::GraphQLQuery;
use lldap_auth::{login, registration, JWTClaims}; use lldap_auth::{login, registration, JWTClaims};
use yew::callback::Callback; use yew::{
use yew::format::Json; callback::Callback,
use yew::services::fetch::{Credentials, FetchOptions, FetchService, FetchTask, Request, Response}; format::Json,
services::fetch::{Credentials, FetchOptions, FetchService, FetchTask, Request, Response},
};
#[derive(Default)] #[derive(Default)]
pub struct HostService {} pub struct HostService {}
@ -286,4 +288,17 @@ impl HostService {
"Could not validate token", "Could not validate token",
) )
} }
pub fn probe_password_reset(callback: Callback<Result<bool>>) -> Result<FetchTask> {
let request = Request::get("/auth/reset/step1/lldap_unlikely_very_long_user_name")
.header("Content-Type", "application/json")
.body(yew::format::Nothing)?;
FetchService::fetch_with_options(
request,
get_default_options(),
create_handler(callback, move |status: http::StatusCode, _data: String| {
Ok(status != http::StatusCode::NOT_FOUND)
}),
)
}
} }