refactor(logging): log errors in auth and payload parsing

- Log bearer token lookup and user lookup failures in AuthenticatedUser extractor
- Log session lookup and user lookup failures in session-based authentication
- Log fire-and-forget token update_last_used errors
- Log JSON and form payload parse failures in FlexiblePayload
This commit is contained in:
Jon Seager 2026-02-05 18:13:34 +00:00
parent 06384265d3
commit 5ee89549a3
No known key found for this signature in database
2 changed files with 43 additions and 13 deletions

View file

@ -4,6 +4,7 @@ use axum::{
http::{StatusCode, header, request::Parts},
};
use tower_cookies::Cookies;
use tracing::warn;
use crate::application::server::AppState;
use crate::domain::users::User;
@ -41,7 +42,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
.get(header::AUTHORIZATION)
.ok_or(StatusCode::UNAUTHORIZED)?;
let auth_str = auth_header.to_str().map_err(|_| StatusCode::UNAUTHORIZED)?;
let auth_str = auth_header.to_str().map_err(|err| {
warn!(error = %err, "authorization header contains invalid characters");
StatusCode::UNAUTHORIZED
})?;
// Check for "Bearer <token>" format
let token = auth_str
@ -56,7 +60,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
.token_repo
.get_by_token_hash(&token_hash)
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
.map_err(|err| {
warn!(error = %err, "bearer token lookup failed");
StatusCode::UNAUTHORIZED
})?;
// Check if token is revoked
if token_record.is_revoked() {
@ -67,7 +74,9 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
let token_repo = state.token_repo.clone();
let token_id = token_record.id;
tokio::spawn(async move {
let _ = token_repo.update_last_used(token_id).await;
if let Err(err) = token_repo.update_last_used(token_id).await {
warn!(error = %err, %token_id, "failed to update token last_used");
}
});
// Get the user
@ -75,7 +84,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
.user_repo
.get(token_record.user_id)
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
.map_err(|err| {
warn!(error = %err, user_id = %token_record.user_id, "user lookup failed for valid token");
StatusCode::UNAUTHORIZED
})?;
Ok(AuthenticatedUser(user))
}
@ -88,18 +100,30 @@ async fn authenticate_via_session(state: &AppState, cookies: &Cookies) -> Option
let session_token_hash = hash_token(session_token);
// Check if session exists and is valid
let session = state
let session = match state
.session_repo
.get_by_token_hash(&session_token_hash)
.await
.ok()?;
{
Ok(s) => s,
Err(err) => {
warn!(error = %err, "session lookup failed during authentication");
return None;
}
};
if session.is_expired() {
return None;
}
// Get the user
state.user_repo.get(session.user_id).await.ok()
match state.user_repo.get(session.user_id).await {
Ok(user) => Some(user),
Err(err) => {
warn!(error = %err, user_id = %session.user_id, "user lookup failed for valid session");
None
}
}
}
/// Helper to extract authenticated user from request extensions

View file

@ -4,6 +4,7 @@ use axum::extract::{Form, FromRequest, Json as JsonPayload, Request};
use axum::http::{HeaderMap, HeaderValue, header::CONTENT_TYPE};
use axum::response::{Html, IntoResponse, Response};
use serde::Deserialize;
use tracing::warn;
use crate::application::errors::{ApiError, AppError};
use crate::application::server::AppState;
@ -183,9 +184,13 @@ where
.to_ascii_lowercase();
if content_type.starts_with("application/json") {
let JsonPayload(payload) = JsonPayload::<T>::from_request(req, state)
let JsonPayload(payload) =
JsonPayload::<T>::from_request(req, state)
.await
.map_err(|_| ApiError::from(AppError::validation("invalid JSON payload")))?;
.map_err(|_| {
warn!("failed to parse JSON payload");
ApiError::from(AppError::validation("invalid JSON payload"))
})?;
return Ok(Self {
inner: payload,
@ -195,9 +200,10 @@ where
if content_type.is_empty() || content_type.starts_with("application/x-www-form-urlencoded")
{
let Form(payload) = Form::<T>::from_request(req, state)
.await
.map_err(|_| ApiError::from(AppError::validation("invalid form payload")))?;
let Form(payload) = Form::<T>::from_request(req, state).await.map_err(|_| {
warn!("failed to parse form payload");
ApiError::from(AppError::validation("invalid form payload"))
})?;
return Ok(Self {
inner: payload,