prism_mcp_rs/auth/
errors.rs

1//! Authorization Error Types
2//!
3//! Module defines error types specific to the OAuth 2.1 authorization flow.
4
5use std::fmt;
6
7/// Authorization-specific errors
8#[derive(Debug, Clone)]
9pub enum AuthError {
10    /// No authorization server found
11    NoAuthServer(String),
12
13    /// PKCE not supported by authorization server
14    PkceNotSupported,
15
16    /// Invalid or expired token
17    InvalidToken(String),
18
19    /// Token expired
20    TokenExpired,
21
22    /// Insufficient permissions/scopes
23    InsufficientScope(String),
24
25    /// Dynamic registration failed
26    RegistrationFailed(String),
27
28    /// Discovery failed
29    DiscoveryFailed(String),
30
31    /// Authorization denied by user
32    AuthorizationDenied,
33
34    /// Invalid authorization code
35    InvalidAuthorizationCode,
36
37    /// Invalid refresh token
38    InvalidRefreshToken,
39
40    /// OAuth error response
41    OAuthError {
42        error: String,
43        description: Option<String>,
44        uri: Option<String>,
45    },
46
47    /// HTTP error during authorization
48    HttpError(String),
49
50    /// Configuration error
51    ConfigError(String),
52
53    /// State mismatch (CSRF protection)
54    StateMismatch,
55
56    /// Resource indicator error
57    InvalidResource(String),
58}
59
60impl fmt::Display for AuthError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::NoAuthServer(msg) => write!(f, "No authorization server: {msg}"),
64            Self::PkceNotSupported => write!(
65                f,
66                "Authorization server does not support PKCE (required for MCP)"
67            ),
68            Self::InvalidToken(msg) => write!(f, "Invalid token: {msg}"),
69            Self::TokenExpired => write!(f, "Access token has expired"),
70            Self::InsufficientScope(scope) => write!(f, "Insufficient scope: {scope}"),
71            Self::RegistrationFailed(msg) => write!(f, "Client registration failed: {msg}"),
72            Self::DiscoveryFailed(msg) => write!(f, "Discovery failed: {msg}"),
73            Self::AuthorizationDenied => write!(f, "Authorization denied by user"),
74            Self::InvalidAuthorizationCode => write!(f, "Invalid or expired authorization code"),
75            Self::InvalidRefreshToken => write!(f, "Invalid or expired refresh token"),
76            Self::OAuthError {
77                error,
78                description,
79                uri,
80            } => {
81                write!(f, "OAuth error: {error}")?;
82                if let Some(desc) = description {
83                    write!(f, " - {desc}")?;
84                }
85                if let Some(uri) = uri {
86                    write!(f, " (see: {uri})")?;
87                }
88                Ok(())
89            }
90            Self::HttpError(msg) => write!(f, "HTTP error: {msg}"),
91            Self::ConfigError(msg) => write!(f, "Configuration error: {msg}"),
92            Self::StateMismatch => write!(f, "State parameter mismatch (possible CSRF attack)"),
93            Self::InvalidResource(msg) => write!(f, "Invalid resource indicator: {msg}"),
94        }
95    }
96}
97
98impl std::error::Error for AuthError {}
99
100/// Convert AuthError to McpError
101impl From<AuthError> for crate::core::error::McpError {
102    fn from(err: AuthError) -> Self {
103        crate::core::error::McpError::Auth(err.to_string())
104    }
105}
106
107/// Parse OAuth error from query parameters
108pub fn parse_oauth_error(params: &[(String, String)]) -> Option<AuthError> {
109    let error = params
110        .iter()
111        .find(|(k, _)| k == "error")
112        .map(|(_, v)| v.clone())?;
113
114    let description = params
115        .iter()
116        .find(|(k, _)| k == "error_description")
117        .map(|(_, v)| v.clone());
118
119    let uri = params
120        .iter()
121        .find(|(k, _)| k == "error_uri")
122        .map(|(_, v)| v.clone());
123
124    Some(AuthError::OAuthError {
125        error,
126        description,
127        uri,
128    })
129}
130
131/// Check if an error is recoverable (e.g., by refreshing token)
132pub fn is_recoverable_error(error: &AuthError) -> bool {
133    matches!(error, AuthError::TokenExpired | AuthError::InvalidToken(_))
134}