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:
parent
06384265d3
commit
5ee89549a3
2 changed files with 43 additions and 13 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
.await
|
||||
.map_err(|_| ApiError::from(AppError::validation("invalid JSON payload")))?;
|
||||
let JsonPayload(payload) =
|
||||
JsonPayload::<T>::from_request(req, state)
|
||||
.await
|
||||
.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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue