oauth2_client/authorization_code_grant/
flow.rs

1use http_api_client::{Client, ClientRespondEndpointError};
2use http_api_client_endpoint::Endpoint as _;
3use oauth2_core::{
4    authorization_code_grant::{
5        access_token_response::{
6            ErrorBody as AT_RES_ErrorBody, SuccessfulBody as AT_RES_SuccessfulBody,
7        },
8        authorization_response::ErrorQuery as A_RES_ErrorQuery,
9    },
10    serde::{de::DeserializeOwned, Serialize},
11    types::{Code, CodeChallenge, CodeChallengeMethod, CodeVerifier, Nonce, Scope, State},
12    url::{ParseError as UrlParseError, Url},
13};
14
15use crate::ProviderExtAuthorizationCodeGrant;
16
17use super::{
18    parse_redirect_uri_query, AccessTokenEndpoint, AccessTokenEndpointError, AuthorizationEndpoint,
19    AuthorizationEndpointError, ParseRedirectUriQueryError,
20};
21
22//
23//
24//
25#[derive(Debug, Clone)]
26pub struct Flow<C>
27where
28    C: Client,
29{
30    pub client_with_token: C,
31}
32impl<C> Flow<C>
33where
34    C: Client,
35{
36    pub fn new(client_with_token: C) -> Self {
37        Self { client_with_token }
38    }
39}
40
41impl<'a, C> Flow<C>
42where
43    C: Client,
44{
45    /// Don't require state if for Mobile & Desktop Apps
46    pub fn build_authorization_url<SCOPE>(
47        &self,
48        provider: &'a dyn ProviderExtAuthorizationCodeGrant<Scope = SCOPE>,
49        scopes: impl Into<Option<Vec<SCOPE>>>,
50        config: impl Into<Option<FlowBuildAuthorizationUrlConfiguration>>,
51    ) -> Result<Url, FlowBuildAuthorizationUrlError>
52    where
53        SCOPE: Scope + Serialize,
54    {
55        // Step 1
56        build_authorization_url(provider, scopes, config)
57    }
58}
59
60impl<C> Flow<C>
61where
62    C: Client + Send + Sync,
63{
64    pub async fn handle_callback_by_query<SCOPE>(
65        &self,
66        provider: &(dyn ProviderExtAuthorizationCodeGrant<Scope = SCOPE> + Send + Sync),
67        query: impl AsRef<str>,
68        config: impl Into<Option<FlowHandleCallbackByQueryConfiguration>>,
69    ) -> Result<AT_RES_SuccessfulBody<SCOPE>, FlowHandleCallbackError>
70    where
71        SCOPE: Scope + Serialize + DeserializeOwned + Send + Sync,
72    {
73        // Step 3
74        let query = parse_redirect_uri_query(query.as_ref())
75            .map_err(FlowHandleCallbackError::ParseRedirectUriQueryError)?;
76
77        let query = query.map_err(FlowHandleCallbackError::AuthorizationFailed)?;
78
79        let config: FlowHandleCallbackByQueryConfiguration = config.into().unwrap_or_default();
80
81        if let Some(ref state) = &config.state {
82            if let Some(query_state) = &query.state {
83                if state != query_state {
84                    return Err(FlowHandleCallbackError::StateMismatch);
85                }
86            } else {
87                return Err(FlowHandleCallbackError::StateMissing);
88            }
89        }
90
91        self.handle_callback(provider, query.code, Some(config.into()))
92            .await
93    }
94
95    pub async fn handle_callback<SCOPE>(
96        &self,
97        provider: &(dyn ProviderExtAuthorizationCodeGrant<Scope = SCOPE> + Send + Sync),
98        code: Code,
99        config: impl Into<Option<FlowHandleCallbackConfiguration>>,
100    ) -> Result<AT_RES_SuccessfulBody<SCOPE>, FlowHandleCallbackError>
101    where
102        SCOPE: Scope + Serialize + DeserializeOwned + Send + Sync,
103    {
104        let config: FlowHandleCallbackConfiguration = config.into().unwrap_or_default();
105
106        let mut access_token_endpoint = AccessTokenEndpoint::new(provider, code);
107
108        if let Some(code_verifier) = &config.code_verifier {
109            access_token_endpoint.set_code_verifier(code_verifier.to_owned());
110        }
111
112        let access_token_ret = self
113            .client_with_token
114            .respond_endpoint(&access_token_endpoint)
115            .await
116            .map_err(|err| match err {
117                ClientRespondEndpointError::RespondFailed(err) => {
118                    FlowHandleCallbackError::AccessTokenEndpointRespondFailed(Box::new(err))
119                }
120                ClientRespondEndpointError::EndpointRenderRequestFailed(err) => {
121                    FlowHandleCallbackError::AccessTokenEndpointError(err)
122                }
123                ClientRespondEndpointError::EndpointParseResponseFailed(err) => {
124                    FlowHandleCallbackError::AccessTokenEndpointError(err)
125                }
126            })?;
127
128        let access_token_successful_body =
129            access_token_ret.map_err(FlowHandleCallbackError::AccessTokenFailed)?;
130
131        Ok(access_token_successful_body)
132    }
133}
134
135#[derive(thiserror::Error, Debug)]
136pub enum FlowHandleCallbackError {
137    #[error("ParseRedirectUriQueryError {0}")]
138    ParseRedirectUriQueryError(ParseRedirectUriQueryError),
139    //
140    #[error("AuthorizationFailed {0:?}")]
141    AuthorizationFailed(A_RES_ErrorQuery),
142    #[error("StateMismatch")]
143    StateMismatch,
144    #[error("StateMissing")]
145    StateMissing,
146    //
147    #[error("AccessTokenEndpointRespondFailed {0}")]
148    AccessTokenEndpointRespondFailed(Box<dyn std::error::Error + Send + Sync>),
149    #[error("AccessTokenEndpointError {0}")]
150    AccessTokenEndpointError(AccessTokenEndpointError),
151    #[error("AccessTokenFailed {0:?}")]
152    AccessTokenFailed(AT_RES_ErrorBody),
153}
154
155//
156//
157//
158#[derive(Debug, Clone, Default)]
159pub struct FlowBuildAuthorizationUrlConfiguration {
160    pub state: Option<State>,
161    pub code_challenge: Option<(CodeChallenge, CodeChallengeMethod)>,
162    pub nonce: Option<Nonce>,
163}
164impl FlowBuildAuthorizationUrlConfiguration {
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    pub fn configure<F>(mut self, mut f: F) -> Self
170    where
171        F: FnMut(&mut Self),
172    {
173        f(&mut self);
174        self
175    }
176
177    pub fn set_state(&mut self, state: State) {
178        self.state = Some(state);
179    }
180
181    pub fn set_code_challenge(
182        &mut self,
183        code_challenge: CodeChallenge,
184        code_challenge_method: CodeChallengeMethod,
185    ) {
186        self.code_challenge = Some((code_challenge, code_challenge_method));
187    }
188
189    pub fn set_nonce(&mut self, nonce: Nonce) {
190        self.nonce = Some(nonce);
191    }
192}
193
194//
195#[derive(Debug, Clone, Default)]
196pub struct FlowHandleCallbackByQueryConfiguration {
197    pub state: Option<State>,
198    pub code_verifier: Option<CodeVerifier>,
199}
200impl FlowHandleCallbackByQueryConfiguration {
201    pub fn new() -> Self {
202        Self::default()
203    }
204
205    pub fn configure<F>(mut self, mut f: F) -> Self
206    where
207        F: FnMut(&mut Self),
208    {
209        f(&mut self);
210        self
211    }
212
213    pub fn set_state(&mut self, state: State) {
214        self.state = Some(state);
215    }
216
217    pub fn set_code_verifier(&mut self, code_verifier: CodeVerifier) {
218        self.code_verifier = Some(code_verifier);
219    }
220}
221
222//
223#[derive(Debug, Clone, Default)]
224pub struct FlowHandleCallbackConfiguration {
225    pub code_verifier: Option<CodeVerifier>,
226}
227impl FlowHandleCallbackConfiguration {
228    pub fn new() -> Self {
229        Self::default()
230    }
231
232    pub fn configure<F>(mut self, mut f: F) -> Self
233    where
234        F: FnMut(&mut Self),
235    {
236        f(&mut self);
237        self
238    }
239
240    pub fn set_code_verifier(&mut self, code_verifier: CodeVerifier) {
241        self.code_verifier = Some(code_verifier);
242    }
243}
244
245impl From<FlowHandleCallbackByQueryConfiguration> for FlowHandleCallbackConfiguration {
246    fn from(c: FlowHandleCallbackByQueryConfiguration) -> Self {
247        Self {
248            code_verifier: c.code_verifier,
249        }
250    }
251}
252
253//
254//
255//
256pub fn build_authorization_url<SCOPE>(
257    provider: &dyn ProviderExtAuthorizationCodeGrant<Scope = SCOPE>,
258    scopes: impl Into<Option<Vec<SCOPE>>>,
259    config: impl Into<Option<FlowBuildAuthorizationUrlConfiguration>>,
260) -> Result<Url, FlowBuildAuthorizationUrlError>
261where
262    SCOPE: Scope + Serialize,
263{
264    let scopes = scopes.into().or_else(|| provider.scopes_default());
265
266    let config: FlowBuildAuthorizationUrlConfiguration = config.into().unwrap_or_default();
267
268    let mut authorization_endpoint = AuthorizationEndpoint::new(provider, scopes);
269
270    if let Some(state) = &config.state {
271        authorization_endpoint.set_state(state.to_owned());
272    }
273
274    if let Some((code_challenge, code_challenge_method)) = &config.code_challenge {
275        authorization_endpoint
276            .set_code_challenge(code_challenge.to_owned(), code_challenge_method.to_owned());
277    }
278
279    if let Some(nonce) = &config.nonce {
280        authorization_endpoint.set_nonce(nonce.to_owned());
281    }
282
283    let authorization_endpoint_request = authorization_endpoint
284        .render_request()
285        .map_err(FlowBuildAuthorizationUrlError::AuthorizationEndpointError)?;
286
287    let url = authorization_endpoint_request.uri();
288
289    let url = Url::parse(url.to_string().as_str())
290        .map_err(FlowBuildAuthorizationUrlError::ToUrlFailed)?;
291
292    Ok(url)
293}
294
295#[derive(thiserror::Error, Debug)]
296pub enum FlowBuildAuthorizationUrlError {
297    #[error("AuthorizationEndpointError {0}")]
298    AuthorizationEndpointError(AuthorizationEndpointError),
299    #[error("ToUrlFailed {0}")]
300    ToUrlFailed(UrlParseError),
301}