From 248e05c2fe609f23f2c2d6a2ad3882dd237656ea Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Tue, 17 Jun 2025 20:13:52 -0500 Subject: [PATCH 1/2] Make sure that failed login attempts are returned as HTTP statuscode UNAUTHORIZED (401), not a 500 internal server error. --- domain/src/error.rs | 2 + web/src/controller/user_session_controller.rs | 38 ++++++++++++++++--- web/src/error.rs | 7 ++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/domain/src/error.rs b/domain/src/error.rs index bb8f675f..9b218a6c 100644 --- a/domain/src/error.rs +++ b/domain/src/error.rs @@ -40,6 +40,7 @@ pub enum InternalErrorKind { pub enum EntityErrorKind { NotFound, Invalid, + Unauthenticated, DbTransaction, Other(String), } @@ -71,6 +72,7 @@ impl From for Error { let entity_error_kind = match err.error_kind { EntityApiErrorKind::RecordNotFound => EntityErrorKind::NotFound, EntityApiErrorKind::InvalidQueryTerm => EntityErrorKind::Invalid, + EntityApiErrorKind::RecordUnauthenticated => EntityErrorKind::Unauthenticated, _ => EntityErrorKind::Other("EntityErrorKind".to_string()), }; diff --git a/web/src/controller/user_session_controller.rs b/web/src/controller/user_session_controller.rs index 6e03f14d..195f7043 100644 --- a/web/src/controller/user_session_controller.rs +++ b/web/src/controller/user_session_controller.rs @@ -1,4 +1,5 @@ use crate::controller::ApiResponse; +use crate::error::{Error as WebError, Result as WebResult}; use axum::{http::StatusCode, response::IntoResponse, Form, Json}; use domain::user::{AuthSession, Credentials}; use log::*; @@ -36,17 +37,44 @@ pub struct NextUrl { pub async fn login( mut auth_session: AuthSession, Form(creds): Form, -) -> impl IntoResponse { +) -> WebResult { debug!("UserSessionController::login()"); let user = match auth_session.authenticate(creds.clone()).await { Ok(Some(user)) => user, - Ok(None) => return Err(StatusCode::UNAUTHORIZED.into_response()), - Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR.into_response()), + Ok(None) => { + // No user found - this should also be treated as an authentication error + return Err(WebError::from(domain::error::Error { + source: None, + error_kind: domain::error::DomainErrorKind::Internal( + domain::error::InternalErrorKind::Entity( + domain::error::EntityErrorKind::Unauthenticated + ) + ), + })); + }, + Err(auth_error) => { + // axum_login errors contain our entity_api::Error in the error field + warn!("Authentication failed: {:?}", auth_error); + return Err(WebError::from(domain::error::Error { + source: Some(Box::new(auth_error)), + error_kind: domain::error::DomainErrorKind::Internal( + domain::error::InternalErrorKind::Entity( + domain::error::EntityErrorKind::Unauthenticated + ) + ), + })); + }, }; - if auth_session.login(&user).await.is_err() { - return Err(StatusCode::INTERNAL_SERVER_ERROR.into_response()); + if let Err(login_error) = auth_session.login(&user).await { + warn!("Session login failed: {:?}", login_error); + return Err(WebError::from(domain::error::Error { + source: Some(Box::new(login_error)), + error_kind: domain::error::DomainErrorKind::Internal( + domain::error::InternalErrorKind::Other("Session login failed".to_string()) + ), + })); } let user_session_json = json!({ diff --git a/web/src/error.rs b/web/src/error.rs index 829c3d7f..30ea38e2 100644 --- a/web/src/error.rs +++ b/web/src/error.rs @@ -88,6 +88,13 @@ impl Error { ); (StatusCode::NOT_FOUND, "NOT FOUND").into_response() } + EntityErrorKind::Unauthenticated => { + warn!( + "EntityErrorKind::Unauthenticated: Responding with 401 Unauthorized. Error: {:?}", + self + ); + (StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response() + } EntityErrorKind::DbTransaction => { warn!( "EntityErrorKind::DbTransaction: Responding with 500 Internal Server Error. Error: {:?}", From 432ccbca9245be0d7203293d3e0143d3f2b9709b Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Tue, 17 Jun 2025 20:18:32 -0500 Subject: [PATCH 2/2] Run cargo fmt --- web/src/controller/user_session_controller.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/controller/user_session_controller.rs b/web/src/controller/user_session_controller.rs index 195f7043..fdf816e2 100644 --- a/web/src/controller/user_session_controller.rs +++ b/web/src/controller/user_session_controller.rs @@ -48,11 +48,11 @@ pub async fn login( source: None, error_kind: domain::error::DomainErrorKind::Internal( domain::error::InternalErrorKind::Entity( - domain::error::EntityErrorKind::Unauthenticated - ) + domain::error::EntityErrorKind::Unauthenticated, + ), ), })); - }, + } Err(auth_error) => { // axum_login errors contain our entity_api::Error in the error field warn!("Authentication failed: {:?}", auth_error); @@ -60,11 +60,11 @@ pub async fn login( source: Some(Box::new(auth_error)), error_kind: domain::error::DomainErrorKind::Internal( domain::error::InternalErrorKind::Entity( - domain::error::EntityErrorKind::Unauthenticated - ) + domain::error::EntityErrorKind::Unauthenticated, + ), ), })); - }, + } }; if let Err(login_error) = auth_session.login(&user).await { @@ -72,7 +72,7 @@ pub async fn login( return Err(WebError::from(domain::error::Error { source: Some(Box::new(login_error)), error_kind: domain::error::DomainErrorKind::Internal( - domain::error::InternalErrorKind::Other("Session login failed".to_string()) + domain::error::InternalErrorKind::Other("Session login failed".to_string()), ), })); }