app: improve error message for wrong/expired reset token

This commit is contained in:
Valentin Tolmer 2023-02-15 14:21:51 +01:00 committed by nitnelave
parent 193a0fd710
commit bebb00aa2e
4 changed files with 78 additions and 69 deletions

View File

@ -32,7 +32,7 @@ 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, password_reset_enabled: Option<bool>,
task: Option<FetchTask>, task: Option<FetchTask>,
} }
@ -64,7 +64,7 @@ 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, password_reset_enabled: None,
task: None, task: None,
}; };
app.task = Some( app.task = Some(
@ -95,22 +95,21 @@ impl Component for App {
Msg::Logout => { Msg::Logout => {
self.user_info = None; self.user_info = None;
self.redirect_to = None; self.redirect_to = None;
self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
} }
Msg::PasswordResetProbeFinished(Ok(enabled)) => { Msg::PasswordResetProbeFinished(Ok(enabled)) => {
self.task = None; self.task = None;
self.password_reset_enabled = enabled; self.password_reset_enabled = Some(enabled);
} }
Msg::PasswordResetProbeFinished(Err(err)) => { Msg::PasswordResetProbeFinished(Err(err)) => {
self.task = None; self.task = None;
self.password_reset_enabled = Some(false);
ConsoleService::error(&format!( ConsoleService::error(&format!(
"Could not probe for password reset support: {err:#}" "Could not probe for password reset support: {err:#}"
)); ));
} }
} }
if self.user_info.is_none() {
self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
}
true true
} }
@ -160,7 +159,7 @@ 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 { if self.password_reset_enabled == Some(false) {
self.route_dispatcher self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
} }
@ -195,11 +194,11 @@ impl App {
switch: AppRoute, switch: AppRoute,
link: &ComponentLink<Self>, link: &ComponentLink<Self>,
is_admin: bool, is_admin: bool,
password_reset_enabled: bool, password_reset_enabled: Option<bool>,
) -> Html { ) -> Html {
match switch { match switch {
AppRoute::Login => html! { AppRoute::Login => html! {
<LoginForm on_logged_in=link.callback(Msg::Login) password_reset_enabled=password_reset_enabled/> <LoginForm on_logged_in=link.callback(Msg::Login) password_reset_enabled=password_reset_enabled.unwrap_or(false)/>
}, },
AppRoute::CreateUser => html! { AppRoute::CreateUser => html! {
<CreateUserForm/> <CreateUserForm/>
@ -234,23 +233,20 @@ 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 => { AppRoute::StartResetPassword => match password_reset_enabled {
if password_reset_enabled { Some(true) => html! { <ResetPasswordStep1Form /> },
html! { Some(false) => {
<ResetPasswordStep1Form />
}
} else {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled) App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
} }
}
AppRoute::FinishResetPassword(token) => html! { None => html! {},
if password_reset_enabled { },
html! { AppRoute::FinishResetPassword(token) => match password_reset_enabled {
<ResetPasswordStep2Form token=token /> Some(true) => html! { <ResetPasswordStep2Form token=token /> },
} Some(false) => {
} else {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled) App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
} }
None => html! {},
}, },
} }
} }
@ -287,50 +283,48 @@ impl App {
} } else { html!{} } } } } else { html!{} } }
</ul> </ul>
<div class="dropdown text-end"> {
<a href="#" if let Some((user_id, _)) = &self.user_info {
class="d-block link-dark text-decoration-none dropdown-toggle" html! {
id="dropdownUser" <div class="dropdown text-end">
data-bs-toggle="dropdown" <a href="#"
aria-expanded="false"> class="d-block link-dark text-decoration-none dropdown-toggle"
<svg xmlns="http://www.w3.org/2000/svg" id="dropdownUser"
width="32" data-bs-toggle="dropdown"
height="32" aria-expanded="false">
fill="currentColor" <svg xmlns="http://www.w3.org/2000/svg"
class="bi bi-person-circle" width="32"
viewBox="0 0 16 16"> height="32"
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/> fill="currentColor"
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/> class="bi bi-person-circle"
</svg> viewBox="0 0 16 16">
{ <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
if let Some((user_id, _)) = &self.user_info { <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
html! { </svg>
<span class="ms-2"> <span class="ms-2">
{user_id} {user_id}
</span> </span>
} </a>
} else { html!{} } <ul
class="dropdown-menu text-small dropdown-menu-lg-end"
aria-labelledby="dropdownUser1"
style="">
<li>
<Link
classes="dropdown-item"
route=AppRoute::UserDetails(user_id.clone())>
{"View details"}
</Link>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
</li>
</ul>
</div>
} }
</a> } else { html!{} }
{if let Some((user_id, _)) = &self.user_info { html! { }
<ul
class="dropdown-menu text-small dropdown-menu-lg-end"
aria-labelledby="dropdownUser1"
style="">
<li>
<Link
classes="dropdown-item"
route=AppRoute::UserDetails(user_id.clone())>
{"View details"}
</Link>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
</li>
</ul>
} } else { html!{} } }
</div>
</div> </div>
</div> </div>
</header> </header>

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
components::router::AppRoute, components::router::{AppRoute, NavButton},
infra::{ infra::{
api::HostService, api::HostService,
common_component::{CommonComponent, CommonComponentParts}, common_component::{CommonComponent, CommonComponentParts},
@ -158,9 +158,17 @@ impl Component for ResetPasswordStep2Form {
} }
(None, Some(e)) => { (None, Some(e)) => {
return html! { return html! {
<div class="alert alert-danger"> <>
{e.to_string() } <div class="alert alert-danger">
</div> {e.to_string() }
</div>
<NavButton
classes="btn-link btn"
disabled=self.common.is_task_running()
route=AppRoute::Login>
{"Back"}
</NavButton>
</>
} }
} }
_ => (), _ => (),

View File

@ -214,11 +214,15 @@ where
let token = request let token = request
.match_info() .match_info()
.get("token") .get("token")
.ok_or_else(|| TcpError::BadRequest("Missing reset token".to_string()))?; .ok_or_else(|| TcpError::BadRequest("Missing reset token".to_owned()))?;
let user_id = data let user_id = data
.backend_handler .backend_handler
.get_user_id_for_password_reset_token(token) .get_user_id_for_password_reset_token(token)
.await?; .await
.map_err(|e| {
debug!("Reset token error: {e:#}");
TcpError::NotFoundError("Wrong or expired reset token".to_owned())
})?;
let _ = data let _ = data
.backend_handler .backend_handler
.delete_password_reset_token(token) .delete_password_reset_token(token)

View File

@ -37,6 +37,8 @@ pub enum TcpError {
BadRequest(String), BadRequest(String),
#[error("Internal server error: `{0}`")] #[error("Internal server error: `{0}`")]
InternalServerError(String), InternalServerError(String),
#[error("Not found: `{0}`")]
NotFoundError(String),
#[error("Unauthorized: `{0}`")] #[error("Unauthorized: `{0}`")]
UnauthorizedError(String), UnauthorizedError(String),
} }
@ -57,6 +59,7 @@ pub(crate) fn error_to_http_response(error: TcpError) -> HttpResponse {
| DomainError::EntityNotFound(_) => HttpResponse::BadRequest(), | DomainError::EntityNotFound(_) => HttpResponse::BadRequest(),
}, },
TcpError::BadRequest(_) => HttpResponse::BadRequest(), TcpError::BadRequest(_) => HttpResponse::BadRequest(),
TcpError::NotFoundError(_) => HttpResponse::NotFound(),
TcpError::InternalServerError(_) => HttpResponse::InternalServerError(), TcpError::InternalServerError(_) => HttpResponse::InternalServerError(),
TcpError::UnauthorizedError(_) => HttpResponse::Unauthorized(), TcpError::UnauthorizedError(_) => HttpResponse::Unauthorized(),
} }