headless_lms_server/domain/oauth/
errors.rs

1use crate::domain::error::{ControllerError, ControllerErrorType, OAuthErrorCode, OAuthErrorData};
2use crate::prelude::BackendError;
3use dpop_verifier::DpopError;
4use thiserror::Error;
5
6/// Domain-specific errors for OAuth token grant processing.
7#[derive(Debug, Error)]
8pub enum TokenGrantError {
9    /// Invalid grant (e.g., invalid authorization code or refresh token)
10    #[error("Invalid grant: {0}")]
11    InvalidGrant(String),
12
13    /// Invalid client (e.g., client ID mismatch or missing DPoP header)
14    #[error("Invalid client: {0}")]
15    InvalidClient(String),
16
17    /// PKCE verification failed
18    #[error("PKCE verification failed")]
19    PkceVerificationFailed,
20
21    /// Unsupported grant type
22    #[error("Unsupported grant type")]
23    UnsupportedGrantType,
24
25    /// DPoP JKT mismatch
26    #[error("DPoP JKT mismatch")]
27    DpopMismatch,
28
29    /// Server error (database or other internal error)
30    #[error("Server error: {0}")]
31    ServerError(String),
32}
33
34impl From<TokenGrantError> for ControllerError {
35    fn from(err: TokenGrantError) -> Self {
36        let data = match &err {
37            TokenGrantError::InvalidGrant(msg) => OAuthErrorData {
38                error: OAuthErrorCode::InvalidGrant.as_str().into(),
39                error_description: msg.clone(),
40                redirect_uri: None,
41                state: None,
42                nonce: None,
43            },
44            TokenGrantError::InvalidClient(msg) => OAuthErrorData {
45                error: OAuthErrorCode::InvalidClient.as_str().into(),
46                error_description: msg.clone(),
47                redirect_uri: None,
48                state: None,
49                nonce: None,
50            },
51            TokenGrantError::PkceVerificationFailed => OAuthErrorData {
52                error: OAuthErrorCode::InvalidGrant.as_str().into(),
53                error_description: "PKCE verification failed".into(),
54                redirect_uri: None,
55                state: None,
56                nonce: None,
57            },
58            TokenGrantError::UnsupportedGrantType => OAuthErrorData {
59                error: OAuthErrorCode::UnsupportedGrantType.as_str().into(),
60                error_description: "unsupported grant type".into(),
61                redirect_uri: None,
62                state: None,
63                nonce: None,
64            },
65            TokenGrantError::DpopMismatch => OAuthErrorData {
66                error: OAuthErrorCode::InvalidToken.as_str().into(),
67                error_description: "DPoP JKT mismatch".into(),
68                redirect_uri: None,
69                state: None,
70                nonce: None,
71            },
72            TokenGrantError::ServerError(msg) => OAuthErrorData {
73                error: OAuthErrorCode::ServerError.as_str().into(),
74                error_description: msg.clone(),
75                redirect_uri: None,
76                state: None,
77                nonce: None,
78            },
79        };
80
81        ControllerError::new(
82            ControllerErrorType::OAuthError(Box::new(data)),
83            err.to_string(),
84            Some(anyhow::anyhow!(err)),
85        )
86    }
87}
88
89impl From<DpopError> for TokenGrantError {
90    fn from(err: DpopError) -> Self {
91        match err {
92            DpopError::AthMismatch => TokenGrantError::DpopMismatch,
93            _ => TokenGrantError::ServerError(format!("DPoP error: {}", err)),
94        }
95    }
96}