auth_framework/
errors.rs

1//! Comprehensive error types for the AuthFramework.
2//!
3//! This module defines all error types used throughout the authentication framework,
4//! providing detailed error information for debugging, logging, and user feedback.
5//! All errors implement standard Rust error traits and provide contextual information
6//! to help diagnose issues.
7//!
8//! # Error Categories
9//!
10//! - **Authentication Errors**: Credential validation and method failures
11//! - **Authorization Errors**: Permission and access control failures
12//! - **Token Errors**: JWT creation, validation, and lifecycle issues
13//! - **Configuration Errors**: Setup and configuration problems
14//! - **Storage Errors**: Database and persistence layer issues
15//! - **Network Errors**: External service communication failures
16//! - **Cryptographic Errors**: Security operation failures
17//!
18//! # Error Handling Patterns
19//!
20//! The framework uses structured error handling with:
21//! - Contextual error messages with relevant details
22//! - Error chaining to preserve root cause information
23//! - Categorized errors for appropriate response handling
24//! - Security-safe error messages that don't leak sensitive data
25//!
26//! # Example Error Handling
27//!
28//! ```rust
29//! use auth_framework::{AuthFramework, AuthError};
30//!
31//! match auth_framework.authenticate("password", credential, metadata).await {
32//!     Ok(result) => handle_success(result),
33//!     Err(AuthError::InvalidCredential { credential_type, message }) => {
34//!         log::warn!("Invalid {} credential: {}", credential_type, message);
35//!         respond_with_auth_failure()
36//!     },
37//!     Err(AuthError::RateLimited { retry_after, .. }) => {
38//!         respond_with_rate_limit(retry_after)
39//!     },
40//!     Err(e) => {
41//!         log::error!("Authentication system error: {}", e);
42//!         respond_with_system_error()
43//!     }
44//! }
45//! ```
46//!
47//! # Security Considerations
48//!
49//! Error messages are designed to:
50//! - Provide useful debugging information for developers
51//! - Avoid exposing sensitive information to potential attackers
52//! - Enable proper security monitoring and alerting
53//! - Support compliance requirements for audit logging
54
55use thiserror::Error;
56
57/// Type alias for Results in the authentication framework.
58///
59/// This alias simplifies error handling throughout the framework by defaulting
60/// to `AuthError` as the error type while allowing flexibility for other error
61/// types when needed.
62pub type Result<T, E = AuthError> = std::result::Result<T, E>;
63
64/// Comprehensive error type covering all authentication and authorization failures.
65///
66/// `AuthError` provides detailed error information for all aspects of the authentication
67/// framework, from configuration issues to runtime failures. Each error variant includes
68/// contextual information to aid in debugging and provide appropriate user feedback.
69///
70/// This enhanced error type provides:
71/// - **Actionable error messages** with specific suggestions for fixes
72/// - **Documentation links** to relevant guides and troubleshooting
73/// - **Contextual help** that guides users to solutions
74/// - **Security-aware messaging** that doesn't leak sensitive information
75///
76/// # Error Categories
77///
78/// ## Configuration Errors
79/// Errors that occur during framework setup and configuration validation.
80///
81/// ## Authentication Errors
82/// Errors related to credential validation and authentication method execution.
83///
84/// ## Authorization Errors
85/// Errors related to permission checking and access control.
86///
87/// ## Token Errors
88/// JWT token creation, validation, expiration, and lifecycle issues.
89///
90/// ## Storage Errors
91/// Database connectivity, query failures, and data persistence issues.
92///
93/// ## Network Errors
94/// External service communication, timeouts, and connectivity problems.
95///
96/// ## Cryptographic Errors
97/// Encryption, decryption, signing, and other security operation failures.
98///
99/// # Enhanced Error Handling
100///
101/// ```rust
102/// use auth_framework::AuthError;
103///
104/// // Enhanced error handling with contextual help
105/// match auth_result {
106///     Err(AuthError::Configuration { message, help, docs_url, .. }) => {
107///         eprintln!("❌ Configuration Error: {}", message);
108///         if let Some(help) = help {
109///             eprintln!("💡 Help: {}", help);
110///         }
111///         if let Some(docs) = docs_url {
112///             eprintln!("📖 See: {}", docs);
113///         }
114///     },
115///     Err(AuthError::InvalidCredential { credential_type, message, suggested_fix, .. }) => {
116///         eprintln!("🔐 Invalid {}: {}", credential_type, message);
117///         if let Some(fix) = suggested_fix {
118///             eprintln!("🔧 Suggested fix: {}", fix);
119///         }
120///     },
121///     // ... handle other error types
122/// }
123/// ```
124///
125/// # Security Notes
126///
127/// Error messages are carefully crafted to:
128/// - Provide sufficient detail for debugging and monitoring
129/// - Avoid exposing sensitive information that could aid attackers
130/// - Enable security teams to identify potential threats
131/// - Support compliance and audit requirements
132/// - Guide users to secure solutions and best practices
133#[derive(Error, Debug)]
134pub enum AuthError {
135    /// Configuration validation and setup errors.
136    ///
137    /// These errors occur when the authentication framework is misconfigured
138    /// or when configuration validation fails during startup.
139    #[error("Configuration error: {message}")]
140    Configuration {
141        message: String,
142        #[source]
143        source: Option<Box<dyn std::error::Error + Send + Sync>>,
144        /// Helpful guidance for fixing the issue
145        help: Option<String>,
146        /// Link to relevant documentation
147        docs_url: Option<String>,
148        /// Specific fix suggestion with commands or code
149        suggested_fix: Option<String>,
150    },
151
152    /// Authentication method execution errors.
153    ///
154    /// These errors occur when a specific authentication method fails to
155    /// execute properly, such as OAuth provider communication failures.
156    #[error("Authentication method '{method}' error: {message}")]
157    AuthMethod {
158        method: String,
159        message: String,
160        /// Helpful guidance for fixing the issue
161        help: Option<String>,
162        /// Link to relevant documentation
163        docs_url: Option<String>,
164        /// Specific fix suggestion
165        suggested_fix: Option<String>,
166    },
167
168    /// Token-related errors
169    #[error("Token error: {0}")]
170    Token(#[from] TokenError),
171
172    /// Permission-related errors
173    #[error("Permission error: {0}")]
174    Permission(#[from] PermissionError),
175
176    /// Storage-related errors
177    #[error("Storage error: {0}")]
178    Storage(#[from] StorageError),
179
180    /// Network/HTTP errors
181    #[error("Network error: {0}")]
182    Network(#[from] reqwest::Error),
183
184    /// JSON parsing errors
185    #[error("JSON error: {0}")]
186    Json(#[from] serde_json::Error),
187
188    /// JWT errors
189    #[error("JWT error: {0}")]
190    Jwt(#[from] jsonwebtoken::errors::Error),
191
192    /// YAML parsing errors
193    #[error("YAML error: {0}")]
194    Yaml(#[from] serde_yaml::Error),
195
196    /// TOML parsing errors
197    #[error("TOML error: {0}")]
198    Toml(#[from] toml::ser::Error),
199
200    /// Prometheus metrics errors
201    #[cfg(feature = "prometheus")]
202    #[error("Metrics error: {0}")]
203    Metrics(#[from] prometheus::Error),
204
205    /// IO errors
206    #[error("IO error: {0}")]
207    Io(#[from] std::io::Error),
208
209    /// CLI interaction errors
210    #[error("CLI error: {0}")]
211    Cli(String),
212
213    /// System time errors
214    #[error("System time error: {0}")]
215    SystemTime(#[from] std::time::SystemTimeError),
216
217    /// Rate limiting errors
218    #[error("Rate limit exceeded: {message}")]
219    RateLimit { message: String },
220
221    /// Session-related errors
222    #[error("Too many concurrent sessions for user")]
223    TooManyConcurrentSessions,
224
225    /// MFA-related errors
226    #[error("MFA error: {0}")]
227    Mfa(#[from] MfaError),
228
229    /// Device flow errors
230    #[error("Device flow error: {0}")]
231    DeviceFlow(#[from] DeviceFlowError),
232
233    /// OAuth provider errors
234    #[error("OAuth provider error: {0}")]
235    OAuthProvider(#[from] OAuthProviderError),
236
237    /// Password verification errors
238    #[error("Password verification failed: {0}")]
239    PasswordVerification(String),
240
241    /// Password hashing errors
242    #[error("Password hashing failed: {0}")]
243    PasswordHashing(String),
244
245    /// User not found error
246    #[error("User not found")]
247    UserNotFound,
248
249    /// Invalid input error
250    #[error("Invalid input: {0}")]
251    InvalidInput(String),
252
253    /// Hardware token errors
254    #[error("Hardware token error: {0}")]
255    HardwareToken(String),
256
257    /// Backup code verification errors
258    #[error("Backup code verification failed: {0}")]
259    BackupCodeVerification(String),
260
261    /// Backup code hashing errors
262    #[error("Backup code hashing failed: {0}")]
263    BackupCodeHashing(String),
264
265    /// Invalid secret error
266    #[error("Invalid secret format")]
267    InvalidSecret,
268
269    /// User profile errors
270    #[error("User profile error: {message}")]
271    UserProfile { message: String },
272
273    /// Credential validation errors
274    #[error("Invalid credential: {credential_type} - {message}")]
275    InvalidCredential {
276        credential_type: String,
277        message: String,
278    },
279
280    /// Authentication timeout
281    #[error("Authentication timeout after {timeout_seconds} seconds")]
282    Timeout { timeout_seconds: u64 },
283
284    /// Provider configuration missing
285    #[error("Provider '{provider}' is not configured or supported")]
286    ProviderNotConfigured { provider: String },
287
288    /// Cryptography errors
289    #[error("Cryptography error: {message}")]
290    Crypto { message: String },
291
292    /// Validation errors
293    #[error("Validation error: {message}")]
294    Validation { message: String },
295
296    /// Generic internal errors
297    #[error("Internal error: {message}")]
298    Internal { message: String },
299
300    /// Invalid request error
301    #[error("Invalid request: {0}")]
302    InvalidRequest(String),
303
304    /// Step-up authentication required
305    #[error(
306        "Step-up authentication required: current level '{current_level}', required level '{required_level}'"
307    )]
308    StepUpRequired {
309        current_level: String,
310        required_level: String,
311        step_up_url: String,
312    },
313
314    /// Session error
315    #[error("Session error: {0}")]
316    SessionError(String),
317
318    /// Unauthorized access
319    #[error("Unauthorized: {0}")]
320    Unauthorized(String),
321
322    /// Token generation error
323    #[error("Token generation error: {0}")]
324    TokenGeneration(String),
325
326    /// Invalid token error
327    #[error("Invalid token: {0}")]
328    InvalidToken(String),
329
330    /// Unsupported provider error
331    #[error("Unsupported provider: {0}")]
332    UnsupportedProvider(String),
333
334    /// Network error with custom message
335    #[error("Network error: {0}")]
336    NetworkError(String),
337
338    /// Parse error with custom message
339    #[error("Parse error: {0}")]
340    ParseError(String),
341
342    /// Configuration error with custom message
343    #[error("Configuration error: {0}")]
344    ConfigurationError(String),
345}
346
347/// Token-specific errors
348#[derive(Error, Debug)]
349pub enum TokenError {
350    #[error("Token has expired")]
351    Expired,
352
353    #[error("Token is invalid: {message}")]
354    Invalid { message: String },
355
356    #[error("Token not found")]
357    NotFound,
358
359    #[error("Token is missing")]
360    Missing,
361
362    #[error("Token creation failed: {message}")]
363    CreationFailed { message: String },
364
365    #[error("Token refresh failed: {message}")]
366    RefreshFailed { message: String },
367
368    #[error("Token revocation failed: {message}")]
369    RevocationFailed { message: String },
370}
371
372/// Permission-specific errors
373#[derive(Error, Debug)]
374pub enum PermissionError {
375    #[error("Access denied: missing permission '{permission}' for resource '{resource}'")]
376    AccessDenied {
377        permission: String,
378        resource: String,
379    },
380
381    #[error("Role '{role}' not found")]
382    RoleNotFound { role: String },
383
384    #[error("Permission '{permission}' not found")]
385    PermissionNotFound { permission: String },
386
387    #[error("Invalid permission format: {message}")]
388    InvalidFormat { message: String },
389
390    #[error("Permission denied: {message}")]
391    Denied {
392        action: String,
393        resource: String,
394        message: String,
395    },
396}
397
398/// Storage-specific errors
399#[derive(Error, Debug)]
400pub enum StorageError {
401    #[error("Connection failed: {message}")]
402    ConnectionFailed { message: String },
403
404    #[error("Operation failed: {message}")]
405    OperationFailed { message: String },
406
407    #[error("Serialization error: {message}")]
408    Serialization { message: String },
409
410    #[error("Storage backend not available")]
411    BackendUnavailable,
412}
413
414/// MFA-specific errors
415#[derive(Error, Debug)]
416pub enum MfaError {
417    #[error("MFA challenge expired")]
418    ChallengeExpired,
419
420    #[error("Invalid MFA code")]
421    InvalidCode,
422
423    #[error("MFA method not supported: {method}")]
424    MethodNotSupported { method: String },
425
426    #[error("MFA setup required")]
427    SetupRequired,
428
429    #[error("MFA verification failed: {message}")]
430    VerificationFailed { message: String },
431}
432
433/// Device flow specific errors
434#[derive(Error, Debug)]
435pub enum DeviceFlowError {
436    #[error("Authorization pending - user has not yet completed authorization")]
437    AuthorizationPending,
438
439    #[error("Slow down - polling too frequently")]
440    SlowDown,
441
442    #[error("Device code expired")]
443    ExpiredToken,
444
445    #[error("Access denied by user")]
446    AccessDenied,
447
448    #[error("Invalid device code")]
449    InvalidDeviceCode,
450
451    #[error("Unsupported grant type")]
452    UnsupportedGrantType,
453}
454
455/// OAuth provider specific errors
456#[derive(Error, Debug)]
457pub enum OAuthProviderError {
458    #[error("Invalid authorization code")]
459    InvalidAuthorizationCode,
460
461    #[error("Invalid redirect URI")]
462    InvalidRedirectUri,
463
464    #[error("Invalid client credentials")]
465    InvalidClientCredentials,
466
467    #[error("Insufficient scope: required '{required}', granted '{granted}'")]
468    InsufficientScope { required: String, granted: String },
469
470    #[error("Provider '{provider}' does not support '{feature}'")]
471    UnsupportedFeature { provider: String, feature: String },
472
473    #[error("Rate limited by provider: {message}")]
474    RateLimited { message: String },
475}
476
477impl AuthError {
478    /// Create a new configuration error
479    pub fn config(message: impl Into<String>) -> Self {
480        Self::Configuration {
481            message: message.into(),
482            source: None,
483            help: None,
484            docs_url: None,
485            suggested_fix: None,
486        }
487    }
488
489    /// Create a configuration error with helpful context
490    pub fn config_with_help(
491        message: impl Into<String>,
492        help: impl Into<String>,
493        suggested_fix: Option<String>,
494    ) -> Self {
495        Self::Configuration {
496            message: message.into(),
497            source: None,
498            help: Some(help.into()),
499            docs_url: Some(
500                "https://docs.rs/auth-framework/latest/auth_framework/config/".to_string(),
501            ),
502            suggested_fix,
503        }
504    }
505
506    /// Create a JWT secret validation error with helpful guidance
507    pub fn jwt_secret_too_short(current_length: usize) -> Self {
508        Self::Configuration {
509            message: format!(
510                "JWT secret too short (got {} characters, need 32+ for security)",
511                current_length
512            ),
513            source: None,
514            help: Some("Use a cryptographically secure random string of at least 32 characters".to_string()),
515            docs_url: Some("https://docs.rs/auth-framework/latest/auth_framework/config/struct.SecurityConfig.html".to_string()),
516            suggested_fix: Some("Generate a secure secret: `openssl rand -hex 32`".to_string()),
517        }
518    }
519
520    /// Create a production environment error with guidance
521    pub fn production_memory_storage() -> Self {
522        Self::Configuration {
523            message: "Memory storage is not suitable for production environments".to_string(),
524            source: None,
525            help: Some("Use a persistent storage backend like PostgreSQL or Redis".to_string()),
526            docs_url: Some("https://docs.rs/auth-framework/latest/auth_framework/storage/".to_string()),
527            suggested_fix: Some("Configure PostgreSQL: .with_postgres(\"postgresql://...\") or Redis: .with_redis(\"redis://...\")".to_string()),
528        }
529    }
530
531    /// Create a new auth method error
532    pub fn auth_method(method: impl Into<String>, message: impl Into<String>) -> Self {
533        Self::AuthMethod {
534            method: method.into(),
535            message: message.into(),
536            help: None,
537            docs_url: None,
538            suggested_fix: None,
539        }
540    }
541
542    /// Create an auth method error with helpful context
543    pub fn auth_method_with_help(
544        method: impl Into<String>,
545        message: impl Into<String>,
546        help: impl Into<String>,
547        suggested_fix: Option<String>,
548    ) -> Self {
549        Self::AuthMethod {
550            method: method.into(),
551            message: message.into(),
552            help: Some(help.into()),
553            docs_url: Some(
554                "https://docs.rs/auth-framework/latest/auth_framework/methods/".to_string(),
555            ),
556            suggested_fix,
557        }
558    }
559
560    /// Create a new rate limit error
561    pub fn rate_limit(message: impl Into<String>) -> Self {
562        Self::RateLimit {
563            message: message.into(),
564        }
565    }
566
567    /// Create a new crypto error
568    pub fn crypto(message: impl Into<String>) -> Self {
569        Self::Crypto {
570            message: message.into(),
571        }
572    }
573
574    /// Create a new validation error
575    pub fn validation(message: impl Into<String>) -> Self {
576        Self::Validation {
577            message: message.into(),
578        }
579    }
580
581    /// Create a new internal error
582    pub fn internal(message: impl Into<String>) -> Self {
583        Self::Internal {
584            message: message.into(),
585        }
586    }
587
588    /// Create an authorization error
589    pub fn authorization(message: impl Into<String>) -> Self {
590        Self::Permission(PermissionError::Denied {
591            action: "authorize".to_string(),
592            resource: "resource".to_string(),
593            message: message.into(),
594        })
595    }
596
597    /// Create an access denied error
598    pub fn access_denied(message: impl Into<String>) -> Self {
599        Self::Permission(PermissionError::Denied {
600            action: "access".to_string(),
601            resource: "resource".to_string(),
602            message: message.into(),
603        })
604    }
605
606    /// Create a token error
607    pub fn token(message: impl Into<String>) -> Self {
608        Self::Token(TokenError::Invalid {
609            message: message.into(),
610        })
611    }
612
613    /// Create a device flow error
614    pub fn device_flow(error: DeviceFlowError) -> Self {
615        Self::DeviceFlow(error)
616    }
617
618    /// Create an OAuth provider error
619    pub fn oauth_provider(error: OAuthProviderError) -> Self {
620        Self::OAuthProvider(error)
621    }
622
623    /// Create a user profile error
624    pub fn user_profile(message: impl Into<String>) -> Self {
625        Self::UserProfile {
626            message: message.into(),
627        }
628    }
629
630    /// Create an invalid credential error
631    pub fn invalid_credential(
632        credential_type: impl Into<String>,
633        message: impl Into<String>,
634    ) -> Self {
635        Self::InvalidCredential {
636            credential_type: credential_type.into(),
637            message: message.into(),
638        }
639    }
640
641    /// Create a timeout error
642    pub fn timeout(timeout_seconds: u64) -> Self {
643        Self::Timeout { timeout_seconds }
644    }
645
646    /// Create a provider not configured error
647    pub fn provider_not_configured(provider: impl Into<String>) -> Self {
648        Self::ProviderNotConfigured {
649            provider: provider.into(),
650        }
651    }
652
653    /// Create a rate limited error
654    pub fn rate_limited(message: impl Into<String>) -> Self {
655        Self::RateLimit {
656            message: message.into(),
657        }
658    }
659
660    /// Create a configuration error
661    pub fn configuration(message: impl Into<String>) -> Self {
662        Self::Configuration {
663            message: message.into(),
664            source: None,
665            help: None,
666            docs_url: None,
667            suggested_fix: None,
668        }
669    }
670}
671
672impl TokenError {
673    /// Create a new token creation failed error
674    pub fn creation_failed(message: impl Into<String>) -> Self {
675        Self::CreationFailed {
676            message: message.into(),
677        }
678    }
679
680    /// Create a new token refresh failed error
681    pub fn refresh_failed(message: impl Into<String>) -> Self {
682        Self::RefreshFailed {
683            message: message.into(),
684        }
685    }
686
687    /// Create a new token revocation failed error
688    pub fn revocation_failed(message: impl Into<String>) -> Self {
689        Self::RevocationFailed {
690            message: message.into(),
691        }
692    }
693}
694
695impl PermissionError {
696    /// Create a new access denied error
697    pub fn access_denied(permission: impl Into<String>, resource: impl Into<String>) -> Self {
698        Self::AccessDenied {
699            permission: permission.into(),
700            resource: resource.into(),
701        }
702    }
703
704    /// Create a new role not found error
705    pub fn role_not_found(role: impl Into<String>) -> Self {
706        Self::RoleNotFound { role: role.into() }
707    }
708
709    /// Create a new permission not found error
710    pub fn permission_not_found(permission: impl Into<String>) -> Self {
711        Self::PermissionNotFound {
712            permission: permission.into(),
713        }
714    }
715
716    /// Create a new invalid format error
717    pub fn invalid_format(message: impl Into<String>) -> Self {
718        Self::InvalidFormat {
719            message: message.into(),
720        }
721    }
722}
723
724impl StorageError {
725    /// Create a new connection failed error
726    pub fn connection_failed(message: impl Into<String>) -> Self {
727        Self::ConnectionFailed {
728            message: message.into(),
729        }
730    }
731
732    /// Create a new operation failed error
733    pub fn operation_failed(message: impl Into<String>) -> Self {
734        Self::OperationFailed {
735            message: message.into(),
736        }
737    }
738
739    /// Create a new serialization error
740    pub fn serialization(message: impl Into<String>) -> Self {
741        Self::Serialization {
742            message: message.into(),
743        }
744    }
745}
746
747impl MfaError {
748    /// Create a new method not supported error
749    pub fn method_not_supported(method: impl Into<String>) -> Self {
750        Self::MethodNotSupported {
751            method: method.into(),
752        }
753    }
754
755    /// Create a new verification failed error
756    pub fn verification_failed(message: impl Into<String>) -> Self {
757        Self::VerificationFailed {
758            message: message.into(),
759        }
760    }
761}
762
763// Actix-web ResponseError implementation
764#[cfg(feature = "actix-integration")]
765impl actix_web::ResponseError for AuthError {
766    fn error_response(&self) -> actix_web::HttpResponse {
767        match self {
768            AuthError::Token(_) => {
769                actix_web::HttpResponse::Unauthorized().json(serde_json::json!({
770                    "error": "invalid_token",
771                    "error_description": self.to_string()
772                }))
773            }
774            AuthError::Permission(_) => {
775                actix_web::HttpResponse::Forbidden().json(serde_json::json!({
776                    "error": "insufficient_permissions",
777                    "error_description": self.to_string()
778                }))
779            }
780            AuthError::RateLimit { .. } => {
781                actix_web::HttpResponse::TooManyRequests().json(serde_json::json!({
782                    "error": "rate_limit_exceeded",
783                    "error_description": self.to_string()
784                }))
785            }
786            AuthError::Configuration { .. }
787            | AuthError::Storage(_)
788            | AuthError::Internal { .. } => {
789                actix_web::HttpResponse::InternalServerError().json(serde_json::json!({
790                    "error": "internal_error",
791                    "error_description": "An internal error occurred"
792                }))
793            }
794            _ => actix_web::HttpResponse::BadRequest().json(serde_json::json!({
795                "error": "bad_request",
796                "error_description": self.to_string()
797            })),
798        }
799    }
800
801    fn status_code(&self) -> actix_web::http::StatusCode {
802        match self {
803            AuthError::Token(_) => actix_web::http::StatusCode::UNAUTHORIZED,
804            AuthError::Permission(_) => actix_web::http::StatusCode::FORBIDDEN,
805            AuthError::RateLimit { .. } => actix_web::http::StatusCode::TOO_MANY_REQUESTS,
806            AuthError::Configuration { .. } | AuthError::Storage(_) => {
807                actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
808            }
809            _ => actix_web::http::StatusCode::BAD_REQUEST,
810        }
811    }
812}
813
814// Additional From implementations for admin tools
815impl From<Box<dyn std::error::Error + Send + Sync>> for AuthError {
816    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
817        AuthError::Cli(format!("Admin tool error: {}", error))
818    }
819}
820
821impl From<Box<dyn std::error::Error>> for AuthError {
822    fn from(error: Box<dyn std::error::Error>) -> Self {
823        AuthError::Cli(format!("Admin tool error: {}", error))
824    }
825}
826
827#[cfg(test)]
828mod tests {
829    use super::*;
830    use std::error::Error;
831
832    #[test]
833    fn test_auth_error_creation() {
834        let token_error = AuthError::token("Invalid JWT signature");
835        assert!(matches!(token_error, AuthError::Token(_)));
836        assert!(token_error.to_string().contains("Invalid JWT signature"));
837
838        let permission_error = AuthError::access_denied("Access denied");
839        assert!(matches!(permission_error, AuthError::Permission(_)));
840        assert!(permission_error.to_string().contains("Access denied"));
841
842        let config_error = AuthError::config("Database connection failed");
843        assert!(matches!(config_error, AuthError::Configuration { .. }));
844        assert!(
845            config_error
846                .to_string()
847                .contains("Database connection failed")
848        );
849    }
850
851    #[test]
852    fn test_auth_error_categorization() {
853        // Test various error types maintain their category
854        let errors = vec![
855            (AuthError::token("test"), "Token"),
856            (AuthError::access_denied("test"), "Permission"),
857            (AuthError::config("test"), "Configuration"),
858            (AuthError::crypto("test"), "Crypto"),
859            (AuthError::validation("test"), "Validation"),
860        ];
861
862        for (error, expected_category) in errors {
863            let error_string = format!("{:?}", error);
864            assert!(
865                error_string.contains(expected_category),
866                "Error {:?} should contain category {}",
867                error,
868                expected_category
869            );
870        }
871    }
872
873    #[test]
874    fn test_rate_limit_error() {
875        let rate_limit_error = AuthError::rate_limit("Too many requests");
876
877        match rate_limit_error {
878            AuthError::RateLimit { message } => {
879                assert_eq!(message, "Too many requests");
880            }
881            _ => panic!("Expected RateLimit error"),
882        }
883    }
884
885    #[test]
886    fn test_validation_error() {
887        let validation_error = AuthError::validation("username must not be empty");
888
889        match validation_error {
890            AuthError::Validation { message } => {
891                assert_eq!(message, "username must not be empty");
892            }
893            _ => panic!("Expected Validation error"),
894        }
895    }
896
897    #[test]
898    fn test_configuration_error() {
899        let config_error = AuthError::config("jwt_secret is required");
900
901        match config_error {
902            AuthError::Configuration { message, .. } => {
903                assert_eq!(message, "jwt_secret is required");
904            }
905            _ => panic!("Expected Configuration error"),
906        }
907    }
908
909    #[test]
910    fn test_error_chain() {
911        let root_cause = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
912        let auth_error = AuthError::internal(format!("Config file error: {}", root_cause));
913
914        // Test that error information is preserved
915        assert!(auth_error.to_string().contains("File not found"));
916        assert!(auth_error.to_string().contains("Config file error"));
917    }
918
919    #[test]
920    fn test_error_source() {
921        let token_error = AuthError::token("JWT parsing failed");
922
923        // AuthError implements Error trait
924        // Token error wraps TokenError, so it should have a source
925        assert!(token_error.source().is_some());
926
927        // Test error display
928        let error_msg = format!("{}", token_error);
929        assert!(error_msg.contains("JWT parsing failed"));
930    }
931
932    #[test]
933    fn test_from_conversions() {
934        // Test conversion from std::io::Error
935        let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
936        let auth_error: AuthError = io_error.into();
937        assert!(matches!(auth_error, AuthError::Io(_)));
938
939        // Test conversion from serde_json::Error
940        let json_str = r#"{"invalid": json"#;
941        let json_error: serde_json::Error =
942            serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
943        let auth_error: AuthError = json_error.into();
944        assert!(matches!(auth_error, AuthError::Json(_)));
945    }
946
947    #[test]
948    fn test_error_equality() {
949        let error1 = AuthError::token("Same message");
950        let error2 = AuthError::token("Same message");
951        let error3 = AuthError::token("Different message");
952
953        // Test Debug representation for consistency
954        assert_eq!(format!("{:?}", error1), format!("{:?}", error2));
955        assert_ne!(format!("{:?}", error1), format!("{:?}", error3));
956    }
957
958    #[test]
959    fn test_actix_web_integration() {
960        #[cfg(feature = "actix-web")]
961        {
962            use actix_web::ResponseError;
963
964            // Test status codes
965            assert_eq!(
966                AuthError::token("test").status_code(),
967                actix_web::http::StatusCode::UNAUTHORIZED
968            );
969            assert_eq!(
970                AuthError::access_denied("test").status_code(),
971                actix_web::http::StatusCode::FORBIDDEN
972            );
973            assert_eq!(
974                AuthError::rate_limit("test").status_code(),
975                actix_web::http::StatusCode::TOO_MANY_REQUESTS
976            );
977            assert_eq!(
978                AuthError::internal("test").status_code(),
979                actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
980            );
981        }
982    }
983
984    #[test]
985    fn test_error_message_safety() {
986        // Ensure error messages don't leak sensitive information
987        let sensitive_data = "password123";
988        let safe_error = AuthError::token("Invalid credentials");
989
990        // Error message should not contain sensitive data
991        assert!(!safe_error.to_string().contains(sensitive_data));
992
993        // Test that we can create errors without exposing internals
994        let config_error = AuthError::config("connection failed");
995        assert!(!config_error.to_string().contains("password"));
996        assert!(!config_error.to_string().contains("secret"));
997    }
998
999    #[test]
1000    fn test_cli_error_conversion() {
1001        let boxed_error: Box<dyn std::error::Error + Send + Sync> = "CLI operation failed".into();
1002        let auth_error: AuthError = boxed_error.into();
1003
1004        assert!(matches!(auth_error, AuthError::Cli(_)));
1005        assert!(auth_error.to_string().contains("CLI operation failed"));
1006    }
1007
1008    #[test]
1009    fn test_error_variants_coverage() {
1010        // Ensure all error variants can be created and have proper messages
1011        let test_errors = vec![
1012            AuthError::token("token error"),
1013            AuthError::access_denied("permission error"),
1014            AuthError::internal("internal error"),
1015            AuthError::crypto("crypto error"),
1016            AuthError::Cli("cli error".to_string()),
1017            AuthError::validation("validation error"),
1018            AuthError::config("config error"),
1019            AuthError::rate_limit("rate limit error"),
1020        ];
1021
1022        for error in test_errors {
1023            // All errors should have non-empty messages
1024            assert!(
1025                !error.to_string().is_empty(),
1026                "Error should have message: {:?}",
1027                error
1028            );
1029
1030            // All errors should implement Debug
1031            let debug_repr = format!("{:?}", error);
1032            assert!(
1033                !debug_repr.is_empty(),
1034                "Error should have debug representation: {:?}",
1035                error
1036            );
1037        }
1038    }
1039
1040    #[test]
1041    fn test_oauth_specific_errors() {
1042        // Test OAuth-specific error creation using auth_method
1043        let invalid_client = AuthError::auth_method("oauth", "Client authentication failed");
1044        assert!(
1045            invalid_client
1046                .to_string()
1047                .contains("Client authentication failed")
1048        );
1049
1050        let invalid_grant = AuthError::auth_method("oauth", "Authorization code expired");
1051        assert!(
1052            invalid_grant
1053                .to_string()
1054                .contains("Authorization code expired")
1055        );
1056    }
1057
1058    #[test]
1059    fn test_error_context_preservation() {
1060        // Test that errors maintain context through transformations
1061        let original_msg = "Original error message";
1062        let context_msg = "Additional context";
1063
1064        let base_error = AuthError::internal(original_msg);
1065        let contextual_error = AuthError::internal(format!("{}: {}", context_msg, base_error));
1066
1067        assert!(contextual_error.to_string().contains(original_msg));
1068        assert!(contextual_error.to_string().contains(context_msg));
1069    }
1070
1071    #[test]
1072    fn test_error_serialization() {
1073        // Test that errors can be converted to JSON for API responses
1074        let error = AuthError::validation("email invalid format");
1075
1076        // Should be able to include in structured responses
1077        let error_response = serde_json::json!({
1078            "error": "validation_failed",
1079            "message": error.to_string(),
1080            "field": "email"
1081        });
1082
1083        assert!(
1084            error_response["message"]
1085                .as_str()
1086                .unwrap()
1087                .contains("invalid format")
1088        );
1089    }
1090
1091    #[test]
1092    fn test_concurrent_error_creation() {
1093        use std::thread;
1094
1095        let handles: Vec<_> = (0..10)
1096            .map(|i| {
1097                thread::spawn(move || {
1098                    let error = AuthError::token(format!("Concurrent error {}", i));
1099                    assert!(
1100                        error
1101                            .to_string()
1102                            .contains(&format!("Concurrent error {}", i))
1103                    );
1104                    error
1105                })
1106            })
1107            .collect();
1108
1109        // Wait for all threads and verify errors
1110        for handle in handles {
1111            let error = handle.join().unwrap();
1112            assert!(!error.to_string().is_empty());
1113        }
1114    }
1115}