axum_gate/authn/
errors.rs

1//! Authentication-category native errors.
2//!
3//! This module defines category-native errors for authentication (authn) flows
4//! (login, logout, session renewal) used directly in handlers, services, and middleware.
5//! It reuses the existing `AuthenticationError` variants as the leaf error kinds.
6//!
7//! # Overview
8//!
9//! - `AuthnError`: category-native error enum for authentication flows
10//! - `AuthenticationError`: reused leaf error variants describing authn failures
11//!
12//! # Examples
13//!
14//! Basic construction and user-facing message extraction:
15//!
16//! ```rust
17//! use axum_gate::authn::{AuthnError, AuthenticationError};
18//! use axum_gate::errors::UserFriendlyError;
19//!
20//! let err = AuthnError::from_authentication(AuthenticationError::InvalidCredentials, Some("login form//! ".into()));
21//! assert!(err.user_message().contains("username or password"));
22//! assert!(err.developer_message().contains("Authentication failure"));
23//! assert!(err.support_code().starts_with("AUTHN-"));
24//! ```
25//!
26//! Convenience constructors:
27//!
28//! ```rust
29//! use axum_gate::authn::AuthnError;
30//!
31//! let _ = AuthnError::invalid_credentials(Some("signin".into()));
32//! ```
33
34use crate::errors::{ErrorSeverity, UserFriendlyError};
35
36use thiserror::Error;
37
38/// Leaf authentication error variants reused by the authn category.
39/// Specific authentication error types for authentication flows.
40#[derive(Debug, thiserror::Error)]
41#[non_exhaustive]
42pub enum AuthenticationError {
43    /// Invalid credentials provided
44    #[error("Invalid credentials provided")]
45    InvalidCredentials,
46}
47
48/// Category-native authentication error.
49///
50/// Wraps `AuthenticationError` and provides category-oriented constructors,
51/// user-friendly messaging, support codes, severity, and retryability.
52#[derive(Debug, Error)]
53#[non_exhaustive]
54pub enum AuthnError {
55    /// Authentication flow failure (e.g., invalid credentials, expired session).
56    #[error("Authentication error: {error}")]
57    Authentication {
58        /// The specific authentication failure kind.
59        #[source]
60        error: AuthenticationError,
61        /// Optional context about where/how the failure occurred (non-sensitive).
62        context: Option<String>,
63    },
64}
65
66impl AuthnError {
67    /// Construct from a leaf `AuthenticationError` with optional context.
68    pub fn from_authentication(error: AuthenticationError, context: Option<String>) -> Self {
69        AuthnError::Authentication { error, context }
70    }
71
72    /// Invalid credentials were provided.
73    pub fn invalid_credentials(context: Option<String>) -> Self {
74        Self::from_authentication(AuthenticationError::InvalidCredentials, context)
75    }
76
77    fn support_code_inner(&self) -> String {
78        match self {
79            AuthnError::Authentication { error, .. } => match error {
80                AuthenticationError::InvalidCredentials => "AUTHN-INVALID-CREDS".to_string(),
81            },
82        }
83    }
84}
85
86impl UserFriendlyError for AuthnError {
87    fn user_message(&self) -> String {
88        match self {
89            AuthnError::Authentication { error, .. } => match error {
90                AuthenticationError::InvalidCredentials => {
91                    "The username or password you entered is incorrect. Please check your credentials and try again.".to_string()
92                }
93            },
94        }
95    }
96
97    fn developer_message(&self) -> String {
98        match self {
99            AuthnError::Authentication { error, context } => {
100                let context_info = context
101                    .as_ref()
102                    .map(|c| format!(" Context: {}", c))
103                    .unwrap_or_default();
104                format!("Authentication failure: {}.{}", error, context_info)
105            }
106        }
107    }
108
109    fn support_code(&self) -> String {
110        self.support_code_inner()
111    }
112
113    fn severity(&self) -> ErrorSeverity {
114        match self {
115            AuthnError::Authentication { error, .. } => match error {
116                AuthenticationError::InvalidCredentials => ErrorSeverity::Warning,
117            },
118        }
119    }
120
121    fn suggested_actions(&self) -> Vec<String> {
122        match self {
123            AuthnError::Authentication { error, .. } => match error {
124                AuthenticationError::InvalidCredentials => vec![
125                    "Double-check your username and password for typos".to_string(),
126                    "Ensure Caps Lock is not accidentally enabled".to_string(),
127                    "Use the 'Forgot Password' link if you can't remember your password"
128                        .to_string(),
129                    "Contact support if you're sure your credentials are correct".to_string(),
130                ],
131            },
132        }
133    }
134
135    fn is_retryable(&self) -> bool {
136        match self {
137            AuthnError::Authentication { error, .. } => match error {
138                AuthenticationError::InvalidCredentials => true,
139            },
140        }
141    }
142}