oauth2_client/device_authorization_grant/
device_access_token_endpoint.rs

1use core::{cmp::max, time::Duration};
2
3use http_api_client_endpoint::{
4    Body, Request, Response, RetryableEndpoint, RetryableEndpointRetry,
5};
6use oauth2_core::{
7    access_token_request::{
8        Body as REQ_Body, BodyWithDeviceAuthorizationGrant, CONTENT_TYPE as REQ_CONTENT_TYPE,
9        METHOD as REQ_METHOD,
10    },
11    access_token_response::{
12        ErrorBodyError, CONTENT_TYPE as RES_CONTENT_TYPE, GENERAL_ERROR_BODY_KEY_ERROR,
13    },
14    device_authorization_grant::{
15        device_access_token_response::{
16            ErrorBody as RES_ErrorBody, SuccessfulBody as RES_SuccessfulBody,
17        },
18        device_authorization_response::{
19            SuccessfulBody as DA_RES_SuccessfulBody, INTERVAL_DEFAULT,
20        },
21    },
22    http::{
23        header::{ACCEPT, CONTENT_TYPE},
24        Error as HttpError,
25    },
26    serde::{de::DeserializeOwned, Serialize},
27    types::Scope,
28};
29use serde_json::{Error as SerdeJsonError, Map, Value};
30use serde_urlencoded::ser::Error as SerdeUrlencodedSerError;
31
32use crate::ProviderExtDeviceAuthorizationGrant;
33
34//
35#[derive(Clone)]
36pub struct DeviceAccessTokenEndpoint<'a, SCOPE>
37where
38    SCOPE: Scope,
39{
40    provider: &'a (dyn ProviderExtDeviceAuthorizationGrant<Scope = SCOPE> + Send + Sync),
41    device_authorization_response_successful_body: DA_RES_SuccessfulBody,
42    interval: Duration,
43}
44impl<'a, SCOPE> DeviceAccessTokenEndpoint<'a, SCOPE>
45where
46    SCOPE: Scope,
47{
48    pub fn new(
49        provider: &'a (dyn ProviderExtDeviceAuthorizationGrant<Scope = SCOPE> + Send + Sync),
50        device_authorization_response_successful_body: DA_RES_SuccessfulBody,
51    ) -> Self {
52        let interval = max(
53            device_authorization_response_successful_body.interval(),
54            Duration::from_secs(INTERVAL_DEFAULT as u64),
55        );
56        Self {
57            provider,
58            device_authorization_response_successful_body,
59            interval,
60        }
61    }
62}
63
64impl<'a, SCOPE> RetryableEndpoint for DeviceAccessTokenEndpoint<'a, SCOPE>
65where
66    SCOPE: Scope + Serialize + DeserializeOwned,
67{
68    type RetryReason = DeviceAccessTokenEndpointRetryReason;
69
70    type RenderRequestError = DeviceAccessTokenEndpointError;
71
72    type ParseResponseOutput = Result<RES_SuccessfulBody<SCOPE>, RES_ErrorBody>;
73    type ParseResponseError = DeviceAccessTokenEndpointError;
74
75    fn render_request(
76        &self,
77        _retry: Option<&RetryableEndpointRetry<Self::RetryReason>>,
78    ) -> Result<Request<Body>, Self::RenderRequestError> {
79        let mut body = BodyWithDeviceAuthorizationGrant::new(
80            self.device_authorization_response_successful_body
81                .device_code
82                .to_owned(),
83            self.provider.client_id().cloned(),
84            self.provider.client_secret().cloned(),
85        );
86        if let Some(extra) = self.provider.device_access_token_request_body_extra(
87            &body,
88            &self.device_authorization_response_successful_body,
89        ) {
90            body.set_extra(extra);
91        }
92
93        if let Some(request_ret) = self.provider.device_access_token_request_rendering(
94            &body,
95            &self.device_authorization_response_successful_body,
96        ) {
97            let request = request_ret
98                .map_err(|err| DeviceAccessTokenEndpointError::CustomRenderingRequestFailed(err))?;
99
100            return Ok(request);
101        }
102
103        let body = REQ_Body::<SCOPE>::DeviceAuthorizationGrant(body);
104
105        let body_str = serde_urlencoded::to_string(body)
106            .map_err(DeviceAccessTokenEndpointError::SerRequestBodyFailed)?;
107
108        let request = Request::builder()
109            .method(REQ_METHOD)
110            .uri(self.provider.token_endpoint_url().as_str())
111            .header(CONTENT_TYPE, REQ_CONTENT_TYPE.to_string())
112            .header(ACCEPT, RES_CONTENT_TYPE.to_string())
113            .body(body_str.as_bytes().to_vec())
114            .map_err(DeviceAccessTokenEndpointError::MakeRequestFailed)?;
115
116        Ok(request)
117    }
118
119    fn parse_response(
120        &self,
121        response: Response<Body>,
122        _retry: Option<&RetryableEndpointRetry<Self::RetryReason>>,
123    ) -> Result<Result<Self::ParseResponseOutput, Self::RetryReason>, Self::ParseResponseError>
124    {
125        if let Some(body_ret_ret) = self
126            .provider
127            .device_access_token_response_parsing(&response)
128        {
129            let body_ret = body_ret_ret
130                .map_err(|err| DeviceAccessTokenEndpointError::CustomParsingResponseFailed(err))?;
131
132            return Ok(body_ret);
133        }
134
135        if response.status().is_success() {
136            let map = serde_json::from_slice::<Map<String, Value>>(response.body())
137                .map_err(DeviceAccessTokenEndpointError::DeResponseBodyFailed)?;
138            if !map.contains_key(GENERAL_ERROR_BODY_KEY_ERROR) {
139                let body = serde_json::from_slice::<RES_SuccessfulBody<SCOPE>>(response.body())
140                    .map_err(DeviceAccessTokenEndpointError::DeResponseBodyFailed)?;
141
142                return Ok(Ok(Ok(body)));
143            }
144        }
145
146        let body = serde_json::from_slice::<RES_ErrorBody>(response.body())
147            .map_err(DeviceAccessTokenEndpointError::DeResponseBodyFailed)?;
148        match body.error {
149            ErrorBodyError::AuthorizationPending => {
150                return Ok(Err(
151                    DeviceAccessTokenEndpointRetryReason::AuthorizationPending,
152                ))
153            }
154            ErrorBodyError::SlowDown => {
155                return Ok(Err(DeviceAccessTokenEndpointRetryReason::SlowDown))
156            }
157            _ => {}
158        }
159        Ok(Ok(Err(body)))
160    }
161
162    fn next_retry_in(&self, retry: &RetryableEndpointRetry<Self::RetryReason>) -> Duration {
163        match retry.reason {
164            DeviceAccessTokenEndpointRetryReason::AuthorizationPending => self.interval,
165            DeviceAccessTokenEndpointRetryReason::SlowDown => self.interval,
166        }
167    }
168
169    fn max_retry_count(&self) -> usize {
170        // 1800 / 5
171        360
172    }
173}
174
175#[derive(Debug, Copy, Clone, PartialEq, Eq)]
176pub enum DeviceAccessTokenEndpointRetryReason {
177    AuthorizationPending,
178    SlowDown,
179}
180
181#[derive(thiserror::Error, Debug)]
182pub enum DeviceAccessTokenEndpointError {
183    #[error("CustomRenderingRequestFailed {0}")]
184    CustomRenderingRequestFailed(Box<dyn std::error::Error + Send + Sync>),
185    //
186    #[error("SerRequestBodyFailed {0}")]
187    SerRequestBodyFailed(SerdeUrlencodedSerError),
188    #[error("MakeRequestFailed {0}")]
189    MakeRequestFailed(HttpError),
190    //
191    #[error("CustomParsingResponseFailed {0}")]
192    CustomParsingResponseFailed(Box<dyn std::error::Error + Send + Sync>),
193    //
194    #[error("DeResponseBodyFailed {0}")]
195    DeResponseBodyFailed(SerdeJsonError),
196}