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},
|
http::{StatusCode, header, request::Parts},
|
||||||
};
|
};
|
||||||
use tower_cookies::Cookies;
|
use tower_cookies::Cookies;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::users::User;
|
use crate::domain::users::User;
|
||||||
|
|
@ -41,7 +42,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
|
||||||
.get(header::AUTHORIZATION)
|
.get(header::AUTHORIZATION)
|
||||||
.ok_or(StatusCode::UNAUTHORIZED)?;
|
.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
|
// Check for "Bearer <token>" format
|
||||||
let token = auth_str
|
let token = auth_str
|
||||||
|
|
@ -56,7 +60,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
|
||||||
.token_repo
|
.token_repo
|
||||||
.get_by_token_hash(&token_hash)
|
.get_by_token_hash(&token_hash)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::UNAUTHORIZED)?;
|
.map_err(|err| {
|
||||||
|
warn!(error = %err, "bearer token lookup failed");
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
})?;
|
||||||
|
|
||||||
// Check if token is revoked
|
// Check if token is revoked
|
||||||
if token_record.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_repo = state.token_repo.clone();
|
||||||
let token_id = token_record.id;
|
let token_id = token_record.id;
|
||||||
tokio::spawn(async move {
|
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
|
// Get the user
|
||||||
|
|
@ -75,7 +84,10 @@ impl FromRequestParts<AppState> for AuthenticatedUser {
|
||||||
.user_repo
|
.user_repo
|
||||||
.get(token_record.user_id)
|
.get(token_record.user_id)
|
||||||
.await
|
.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))
|
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);
|
let session_token_hash = hash_token(session_token);
|
||||||
|
|
||||||
// Check if session exists and is valid
|
// Check if session exists and is valid
|
||||||
let session = state
|
let session = match state
|
||||||
.session_repo
|
.session_repo
|
||||||
.get_by_token_hash(&session_token_hash)
|
.get_by_token_hash(&session_token_hash)
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
{
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = %err, "session lookup failed during authentication");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if session.is_expired() {
|
if session.is_expired() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user
|
// 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
|
/// 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::http::{HeaderMap, HeaderValue, header::CONTENT_TYPE};
|
||||||
use axum::response::{Html, IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::application::errors::{ApiError, AppError};
|
use crate::application::errors::{ApiError, AppError};
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
|
|
@ -183,9 +184,13 @@ where
|
||||||
.to_ascii_lowercase();
|
.to_ascii_lowercase();
|
||||||
|
|
||||||
if content_type.starts_with("application/json") {
|
if content_type.starts_with("application/json") {
|
||||||
let JsonPayload(payload) = JsonPayload::<T>::from_request(req, state)
|
let JsonPayload(payload) =
|
||||||
.await
|
JsonPayload::<T>::from_request(req, state)
|
||||||
.map_err(|_| ApiError::from(AppError::validation("invalid JSON payload")))?;
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
warn!("failed to parse JSON payload");
|
||||||
|
ApiError::from(AppError::validation("invalid JSON payload"))
|
||||||
|
})?;
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
inner: payload,
|
inner: payload,
|
||||||
|
|
@ -195,9 +200,10 @@ where
|
||||||
|
|
||||||
if content_type.is_empty() || content_type.starts_with("application/x-www-form-urlencoded")
|
if content_type.is_empty() || content_type.starts_with("application/x-www-form-urlencoded")
|
||||||
{
|
{
|
||||||
let Form(payload) = Form::<T>::from_request(req, state)
|
let Form(payload) = Form::<T>::from_request(req, state).await.map_err(|_| {
|
||||||
.await
|
warn!("failed to parse form payload");
|
||||||
.map_err(|_| ApiError::from(AppError::validation("invalid form payload")))?;
|
ApiError::from(AppError::validation("invalid form payload"))
|
||||||
|
})?;
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
inner: payload,
|
inner: payload,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue