1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
pub use provider::*;
pub use request::*;
pub use response::*;
pub use validate::*;
use crate::{
common::frontend::{FrontendRequest, OAuthError},
manager::OAuthManager,
};
use std::time::Instant;
mod provider;
mod request;
mod response;
#[cfg(test)]
mod test;
mod validate;
impl<U: 'static, E: 'static, Ex: 'static> OAuthManager<U, E, Ex> {
/// Handle an incoming token request from a client.
/// This function will parse the incoming request, validate it, and then generate a token,
/// returning a [TokenResponse] that contains the information for the client to use.
///
/// # Parameters
/// - `req` - The unparsed incoming request from the client, represented by a [FrontendRequest]
///
/// # Returns
/// A [TokenResponse] that can be used to build a response to the client, which in turn
/// implements the [FrontendResponse] trait.
///
/// # Errors
/// This function can return an [OAuthError] if the request is invalid, or if the token
/// provider fails to generate the token.
///
/// # Example
/// ```
/// # use raos::test::{
/// # doctest::oauth_manager_from_application_state,
/// # mock::request_from_raw_http
/// # };
///
/// let manager = oauth_manager_from_application_state();
/// let req = request_from_raw_http(r#"
/// POST /token HTTP/1.1
/// Content-Type: application/x-www-form-urlencoded
///
/// grant_type=authorization_code&code=AUTHORIZATION_CODE&code_verifier=CODE_CHALLENGE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
/// "#);
///
/// # tokio_test::block_on(async {
/// let result = manager.handle_token_request(req).await;
/// assert!(result.is_ok());
/// # });
/// ```
pub async fn handle_token_request(
&self,
req: impl FrontendRequest,
) -> Result<TokenResponse, OAuthError<E>> {
// Take the raw frontend request parameters, and convert it into an AuthorizationRequest
let request = TokenRequest::try_from(&req as &dyn FrontendRequest)?;
self.handle_token(request).await
}
/// Handle an incoming token request from a client.
/// This function will validate it, and then generate a token,
/// returning a [TokenResponse] that contains the information for the client to use.
///
/// # Parameters
/// - `req` - The parsed incoming request from the client, represented by a [TokenRequest]
///
/// # Returns
/// A [TokenResponse] that can be used to build a response to the client, which in turn
/// implements the [FrontendResponse] trait.
///
/// # Errors
/// This function can return an [OAuthError] if the request is invalid, or if the token
/// provider fails to generate the token.
///
/// # Example
/// ```
/// # use raos::{
/// # test::{
/// # doctest::oauth_manager_from_application_state,
/// # mock::request_from_raw_http
/// # },
/// # token::{RequestedGrantType, TokenRequest}
/// # };
///
/// let manager = oauth_manager_from_application_state();
/// let req = TokenRequest {
/// client_id: "CLIENT_ID".to_string(),
/// client_secret: Some("CLIENT_SECRET".to_string()),
/// grant_type: RequestedGrantType::AuthorizationCode {
/// code: "AUTHORIZATION_CODE".to_string(),
/// code_verifier: "CODE_CHALLENGE".to_string(),
/// },
/// redirect_uri: None, // OAuth 2.0 compatibility, not required in OAuth v2.1
/// scope: None
/// };
///
/// # tokio_test::block_on(async {
/// let result = manager.handle_token(req).await;
/// assert!(result.is_ok());
/// # });
/// ```
pub async fn handle_token(&self, req: TokenRequest) -> Result<TokenResponse, OAuthError<E>> {
// Validate the input of the decoded request, following spec rules & provider validation
let validated = self.validate_token_request(req).await?;
let scope = match &validated.grant_type {
GrantType::AuthorizationCode { scope, .. }
| GrantType::RefreshToken(RefreshGrant { scope, .. }) => Some(scope.join(" ")),
GrantType::ClientCredentials => None,
};
let token = self
.token_provider
.token(&validated.client, validated.grant_type)
.await
.map_err(OAuthError::ProviderImplementationError)?;
Ok(TokenResponse {
access_token: token.token,
token_type: "Bearer".to_string(),
expires_in: token.valid_until.duration_since(Instant::now()).as_secs(),
refresh_token: token.refresh_token,
scope,
})
}
}