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)>,
redirect_to: Option<AppRoute>,
route_dispatcher: RouteAgentDispatcher,
password_reset_enabled: bool,
password_reset_enabled: Option<bool>,
task: Option<FetchTask>,
}
@ -64,7 +64,7 @@ impl Component for App {
}),
redirect_to: Self::get_redirect_route(),
route_dispatcher: RouteAgentDispatcher::new(),
password_reset_enabled: false,
password_reset_enabled: None,
task: None,
};
app.task = Some(
@ -95,22 +95,21 @@ impl Component for App {
Msg::Logout => {
self.user_info = None;
self.redirect_to = None;
self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
}
Msg::PasswordResetProbeFinished(Ok(enabled)) => {
self.task = None;
self.password_reset_enabled = enabled;
self.password_reset_enabled = Some(enabled);
}
Msg::PasswordResetProbeFinished(Err(err)) => {
self.task = None;
self.password_reset_enabled = Some(false);
ConsoleService::error(&format!(
"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
}
@ -160,7 +159,7 @@ impl App {
let route_service = RouteService::<()>::new();
let current_route = route_service.get_path();
if current_route.contains("reset-password") {
if !self.password_reset_enabled {
if self.password_reset_enabled == Some(false) {
self.route_dispatcher
.send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login)));
}
@ -195,11 +194,11 @@ impl App {
switch: AppRoute,
link: &ComponentLink<Self>,
is_admin: bool,
password_reset_enabled: bool,
password_reset_enabled: Option<bool>,
) -> Html {
match switch {
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! {
<CreateUserForm/>
@ -234,23 +233,20 @@ impl App {
AppRoute::ChangePassword(username) => html! {
<ChangePasswordForm username=username is_admin=is_admin />
},
AppRoute::StartResetPassword => {
if password_reset_enabled {
html! {
<ResetPasswordStep1Form />
}
} else {
AppRoute::StartResetPassword => match password_reset_enabled {
Some(true) => html! { <ResetPasswordStep1Form /> },
Some(false) => {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
}
}
AppRoute::FinishResetPassword(token) => html! {
if password_reset_enabled {
html! {
<ResetPasswordStep2Form token=token />
}
} else {
None => html! {},
},
AppRoute::FinishResetPassword(token) => match password_reset_enabled {
Some(true) => html! { <ResetPasswordStep2Form token=token /> },
Some(false) => {
App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled)
}
None => html! {},
},
}
}
@ -287,50 +283,48 @@ impl App {
} } else { html!{} } }
</ul>
<div class="dropdown text-end">
<a href="#"
class="d-block link-dark text-decoration-none dropdown-toggle"
id="dropdownUser"
data-bs-toggle="dropdown"
aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="currentColor"
class="bi bi-person-circle"
viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
<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"/>
</svg>
{
if let Some((user_id, _)) = &self.user_info {
html! {
{
if let Some((user_id, _)) = &self.user_info {
html! {
<div class="dropdown text-end">
<a href="#"
class="d-block link-dark text-decoration-none dropdown-toggle"
id="dropdownUser"
data-bs-toggle="dropdown"
aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="currentColor"
class="bi bi-person-circle"
viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
<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"/>
</svg>
<span class="ms-2">
{user_id}
</span>
}
} else { html!{} }
</a>
<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>
{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>
} else { html!{} }
}
</div>
</div>
</header>

View File

@ -1,5 +1,5 @@
use crate::{
components::router::AppRoute,
components::router::{AppRoute, NavButton},
infra::{
api::HostService,
common_component::{CommonComponent, CommonComponentParts},
@ -158,9 +158,17 @@ impl Component for ResetPasswordStep2Form {
}
(None, Some(e)) => {
return html! {
<div class="alert alert-danger">
{e.to_string() }
</div>
<>
<div class="alert alert-danger">
{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
.match_info()
.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
.backend_handler
.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
.backend_handler
.delete_password_reset_token(token)

View File

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