oauth2_client/device_authorization_grant/
device_access_token_endpoint.rs1use 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#[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 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 #[error("SerRequestBodyFailed {0}")]
187 SerRequestBodyFailed(SerdeUrlencodedSerError),
188 #[error("MakeRequestFailed {0}")]
189 MakeRequestFailed(HttpError),
190 #[error("CustomParsingResponseFailed {0}")]
192 CustomParsingResponseFailed(Box<dyn std::error::Error + Send + Sync>),
193 #[error("DeResponseBodyFailed {0}")]
195 DeResponseBodyFailed(SerdeJsonError),
196}