use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use crate::error::AuthError;
impl AuthError {
#[allow(clippy::cognitive_complexity)] fn response_parts(&self) -> (StatusCode, &'static str, String) {
match self {
Self::TokenExpired => {
(StatusCode::UNAUTHORIZED, "token_expired", "Authentication failed".to_string())
},
Self::InvalidSignature => (
StatusCode::UNAUTHORIZED,
"invalid_signature",
"Authentication failed".to_string(),
),
Self::InvalidToken { .. }
| Self::MissingClaim { .. }
| Self::InvalidClaimValue { .. }
| Self::MissingNonce
| Self::NonceMismatch
| Self::MissingAuthTime
| Self::SessionTooOld { .. }
| Self::TokenIssuedInFuture
| Self::TokenTooOld
| Self::TokenNotYetValid
| Self::ForbiddenAlgorithm { .. } => {
(StatusCode::UNAUTHORIZED, "invalid_token", "Authentication failed".to_string())
},
Self::TokenNotFound => {
(StatusCode::UNAUTHORIZED, "token_not_found", "Authentication failed".to_string())
},
Self::SessionRevoked => {
(StatusCode::UNAUTHORIZED, "session_revoked", "Authentication failed".to_string())
},
Self::InvalidState => {
(StatusCode::BAD_REQUEST, "invalid_state", "Authentication failed".to_string())
},
Self::Forbidden { .. } => {
(StatusCode::FORBIDDEN, "forbidden", "Permission denied".to_string())
},
Self::OAuthError { .. } => {
(StatusCode::UNAUTHORIZED, "oauth_error", "Authentication failed".to_string())
},
Self::SessionError { .. } => {
(StatusCode::UNAUTHORIZED, "session_error", "Authentication failed".to_string())
},
Self::DatabaseError { .. }
| Self::ConfigError { .. }
| Self::OidcMetadataError { .. }
| Self::Internal { .. }
| Self::SystemTimeError { .. } => (
StatusCode::INTERNAL_SERVER_ERROR,
"server_error",
"Service temporarily unavailable".to_string(),
),
Self::PkceError { .. } => {
(StatusCode::BAD_REQUEST, "pkce_error", "Authentication failed".to_string())
},
Self::RateLimited { retry_after_secs } => (
StatusCode::TOO_MANY_REQUESTS,
"rate_limited",
format!("Too many requests. Retry after {retry_after_secs} seconds"),
),
Self::InvalidCredentials => (
StatusCode::UNAUTHORIZED,
"invalid_credentials",
"Invalid email or password".to_string(),
),
Self::AccountDisabled => {
(StatusCode::FORBIDDEN, "account_disabled", "This account is disabled".to_string())
},
Self::AccountDeactivated => (
StatusCode::FORBIDDEN,
"account_deactivated",
"This account has been deactivated".to_string(),
),
Self::EmailAlreadyRegistered => (
StatusCode::CONFLICT,
"email_already_registered",
"An account already exists for this email".to_string(),
),
Self::EmailClaimedByAnotherAccount => (
StatusCode::CONFLICT,
"email_claimed_by_another_account",
"That email address is already verified on another account".to_string(),
),
Self::InvalidRegistration { .. } => (
StatusCode::BAD_REQUEST,
"invalid_registration",
"Invalid registration details".to_string(),
),
}
}
#[allow(clippy::cognitive_complexity)] fn log_security_details(&self) {
use tracing::warn;
match self {
Self::InvalidToken { reason } => warn!("Invalid token error: {reason}"),
Self::MissingClaim { claim } => warn!("Missing required claim: {claim}"),
Self::InvalidClaimValue { claim, reason } => {
warn!("Invalid claim value for '{claim}': {reason}");
},
Self::Forbidden { message } => warn!("Authorization denied: {message}"),
Self::OAuthError { message } => warn!("OAuth provider error: {message}"),
Self::SessionError { message } => warn!("Session error: {message}"),
Self::DatabaseError { message } => {
warn!("Database error (should not reach client): {message}");
},
Self::ConfigError { message } => {
warn!("Configuration error (should not reach client): {message}");
},
Self::OidcMetadataError { message } => warn!("OIDC metadata error: {message}"),
Self::PkceError { message } => warn!("PKCE error: {message}"),
Self::Internal { message } => {
warn!("Internal error (should not reach client): {message}");
},
Self::SystemTimeError { message } => {
warn!("System time error (should not reach client): {message}");
},
Self::MissingNonce | Self::NonceMismatch => {
warn!("OIDC nonce validation failed: {self}");
},
Self::MissingAuthTime | Self::SessionTooOld { .. } => {
warn!("OIDC auth_time validation failed: {self}");
},
Self::TokenIssuedInFuture | Self::TokenTooOld | Self::TokenNotYetValid => {
warn!("JWT temporal claim validation failed: {self}");
},
Self::ForbiddenAlgorithm { alg } => {
warn!("OIDC algorithm-substitution attack rejected: forbidden algorithm '{alg}'");
},
Self::InvalidRegistration { reason } => warn!("Local signup rejected: {reason}"),
Self::TokenExpired
| Self::InvalidSignature
| Self::TokenNotFound
| Self::SessionRevoked
| Self::InvalidState
| Self::RateLimited { .. }
| Self::InvalidCredentials
| Self::AccountDisabled
| Self::AccountDeactivated
| Self::EmailAlreadyRegistered
| Self::EmailClaimedByAnotherAccount => {},
}
}
}
impl IntoResponse for AuthError {
fn into_response(self) -> Response {
self.log_security_details();
let (status, error_code, sanitized_message) = self.response_parts();
let body = serde_json::json!({
"errors": [{
"message": sanitized_message,
"extensions": {
"code": error_code
}
}]
});
(status, axum::Json(body)).into_response()
}
}