headless_lms_server/domain/oauth/
errors.rs1use crate::domain::error::{ControllerError, ControllerErrorType, OAuthErrorCode, OAuthErrorData};
2use crate::prelude::BackendError;
3use dpop_verifier::DpopError;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum TokenGrantError {
9 #[error("Invalid grant: {0}")]
11 InvalidGrant(String),
12
13 #[error("Invalid client: {0}")]
15 InvalidClient(String),
16
17 #[error("Invalid scope: {0}")]
20 InvalidScope(String),
21
22 #[error("PKCE verification failed")]
24 PkceVerificationFailed,
25
26 #[error("Unsupported grant type")]
28 UnsupportedGrantType,
29
30 #[error("DPoP JKT mismatch")]
32 DpopMismatch,
33
34 #[error("Authorization pending")]
37 AuthorizationPending,
38
39 #[error("Slow down")]
41 SlowDown,
42
43 #[error("Device code expired")]
45 ExpiredToken,
46
47 #[error("Access denied")]
49 AccessDenied,
50
51 #[error("Server error: {0}")]
53 ServerError(String),
54}
55
56impl From<TokenGrantError> for ControllerError {
57 fn from(err: TokenGrantError) -> Self {
58 let data = match &err {
59 TokenGrantError::InvalidGrant(msg) => OAuthErrorData {
60 error: OAuthErrorCode::InvalidGrant.as_str().into(),
61 error_description: msg.clone(),
62 redirect_uri: None,
63 state: None,
64 nonce: None,
65 },
66 TokenGrantError::InvalidClient(msg) => OAuthErrorData {
67 error: OAuthErrorCode::InvalidClient.as_str().into(),
68 error_description: msg.clone(),
69 redirect_uri: None,
70 state: None,
71 nonce: None,
72 },
73 TokenGrantError::InvalidScope(msg) => OAuthErrorData {
74 error: OAuthErrorCode::InvalidScope.as_str().into(),
75 error_description: msg.clone(),
76 redirect_uri: None,
77 state: None,
78 nonce: None,
79 },
80 TokenGrantError::PkceVerificationFailed => OAuthErrorData {
81 error: OAuthErrorCode::InvalidGrant.as_str().into(),
82 error_description: "PKCE verification failed".into(),
83 redirect_uri: None,
84 state: None,
85 nonce: None,
86 },
87 TokenGrantError::UnsupportedGrantType => OAuthErrorData {
88 error: OAuthErrorCode::UnsupportedGrantType.as_str().into(),
89 error_description: "unsupported grant type".into(),
90 redirect_uri: None,
91 state: None,
92 nonce: None,
93 },
94 TokenGrantError::DpopMismatch => OAuthErrorData {
95 error: OAuthErrorCode::InvalidToken.as_str().into(),
96 error_description: "DPoP JKT mismatch".into(),
97 redirect_uri: None,
98 state: None,
99 nonce: None,
100 },
101 TokenGrantError::AuthorizationPending => OAuthErrorData {
102 error: OAuthErrorCode::AuthorizationPending.as_str().into(),
103 error_description: "authorization request is still pending".into(),
104 redirect_uri: None,
105 state: None,
106 nonce: None,
107 },
108 TokenGrantError::SlowDown => OAuthErrorData {
109 error: OAuthErrorCode::SlowDown.as_str().into(),
110 error_description: "polling too frequently; slow down".into(),
111 redirect_uri: None,
112 state: None,
113 nonce: None,
114 },
115 TokenGrantError::ExpiredToken => OAuthErrorData {
116 error: OAuthErrorCode::ExpiredToken.as_str().into(),
117 error_description: "device code has expired".into(),
118 redirect_uri: None,
119 state: None,
120 nonce: None,
121 },
122 TokenGrantError::AccessDenied => OAuthErrorData {
123 error: OAuthErrorCode::AccessDenied.as_str().into(),
124 error_description: "authorization request was denied".into(),
125 redirect_uri: None,
126 state: None,
127 nonce: None,
128 },
129 TokenGrantError::ServerError(msg) => OAuthErrorData {
130 error: OAuthErrorCode::ServerError.as_str().into(),
131 error_description: msg.clone(),
132 redirect_uri: None,
133 state: None,
134 nonce: None,
135 },
136 };
137
138 ControllerError::new(
139 ControllerErrorType::OAuthError(Box::new(data)),
140 err.to_string(),
141 Some(anyhow::anyhow!(err)),
142 )
143 }
144}
145
146impl From<DpopError> for TokenGrantError {
147 fn from(err: DpopError) -> Self {
148 match err {
149 DpopError::AthMismatch => TokenGrantError::DpopMismatch,
150 _ => TokenGrantError::ServerError(format!("DPoP error: {}", err)),
151 }
152 }
153}