openid_client/client/
device_flow_handle.rs

1use std::{cmp::max, collections::HashMap, num::Wrapping};
2
3use crate::types::{
4    grant_params::GrantParams, DeviceAuthorizationExtras, DeviceAuthorizationResponse,
5    DeviceFlowGrantResponse, GrantExtras, OidcClientError, OidcHttpClient, OidcReturnType,
6};
7
8use super::Client;
9
10/// # DeviceFlowHandle
11/// Handle used for Device Grant
12#[derive(Debug)]
13pub struct DeviceFlowHandle {
14    client: Client,
15    extras: Option<DeviceAuthorizationExtras>,
16    expires_at: i64,
17    interval: f64,
18    max_age: Option<u64>,
19    response: DeviceAuthorizationResponse,
20    last_requested: i64,
21    pub(crate) now: fn() -> i64,
22}
23
24impl DeviceFlowHandle {
25    /// Creates a new Device Flow Handle
26    ///
27    /// `client` - See [Client]
28    /// `response` - [DeviceAuthorizationResponse] from the Device Authorization Endpoint
29    /// `extras` - See [DeviceAuthorizationExtras]
30    /// `max_age` - Maximum allowed age of the token
31    pub fn new(
32        client: Client,
33        response: DeviceAuthorizationResponse,
34        extras: Option<DeviceAuthorizationExtras>,
35        max_age: Option<u64>,
36    ) -> Self {
37        let now = client.now;
38        Self {
39            client,
40            extras,
41            expires_at: now() + response.expires_in,
42            interval: response.interval.unwrap_or(5.0),
43            max_age,
44            response,
45            last_requested: 0,
46            now,
47        }
48    }
49
50    /// Gets the timestamp in seconds of when the device code expires
51    pub fn expires_at(&self) -> i64 {
52        self.expires_at
53    }
54
55    /// Gets the seconds in which the device code expires
56    pub fn expires_in(&self) -> i64 {
57        max((Wrapping(self.expires_at) - Wrapping((self.now)())).0, 0)
58    }
59
60    /// Gets wether the device code is expired or not
61    pub fn expired(&self) -> bool {
62        self.expires_in() == 0
63    }
64
65    /// Gets the polling interval
66    pub fn interval(&self) -> f64 {
67        self.interval
68    }
69
70    /// Increase the interval by `by` seconds
71    pub fn increase_interval(&mut self, by: f64) {
72        self.interval += by;
73    }
74
75    /// Gets the inner client
76    pub fn client(&self) -> &Client {
77        &self.client
78    }
79
80    /// Gets the Device Code
81    pub fn device_code(&self) -> &str {
82        &self.response.device_code
83    }
84
85    /// Gets the User Code
86    pub fn user_code(&self) -> &str {
87        &self.response.user_code
88    }
89
90    /// Gets verification uri
91    pub fn verification_uri(&self) -> &str {
92        &self.response.verification_uri
93    }
94
95    /// Gets the complete verification uri
96    pub fn verification_uri_complete(&self) -> Option<&String> {
97        self.response.verification_uri_complete.as_ref()
98    }
99
100    /// ## Device Flow Grant
101    ///
102    /// Performs grant request at the token endpoint. This method will
103    /// not poll itself. It is left to the implementor to create that logic.
104    ///
105    /// See [DeviceFlowGrantResponse] for the possible responses that will be obtained from the grant.
106    pub async fn grant_async<T>(
107        &mut self,
108        http_client: &T,
109    ) -> OidcReturnType<DeviceFlowGrantResponse>
110    where
111        T: OidcHttpClient,
112    {
113        if self.expired() {
114            return Err(Box::new(OidcClientError::new_rp_error(&format!("the device code {} has expired and the device authorization session has concluded", self.device_code()), None)));
115        }
116
117        if (((self.now)() - self.last_requested) as f64) < self.interval {
118            return Ok(DeviceFlowGrantResponse::Debounced);
119        }
120
121        let extras = GrantExtras {
122            client_assertion_payload: self
123                .extras
124                .as_ref()
125                .and_then(|x| x.client_assertion_payload.to_owned()),
126            dpop: self.extras.as_ref().and_then(|x| x.dpop.as_ref()),
127            ..Default::default()
128        };
129
130        let mut body = HashMap::new();
131
132        if let Some(eb) = &self.extras.as_ref().and_then(|x| x.exchange_body.clone()) {
133            for (k, v) in eb {
134                body.insert(k.to_owned(), v.to_owned());
135            }
136        }
137
138        body.insert(
139            "grant_type".to_string(),
140            "urn:ietf:params:oauth:grant-type:device_code".to_owned(),
141        );
142
143        body.insert("device_code".to_string(), self.device_code().to_owned());
144
145        self.last_requested = (self.now)();
146
147        let mut token_set = match self
148            .client
149            .grant_async(
150                http_client,
151                GrantParams {
152                    body,
153                    extras,
154                    retry: true,
155                },
156            )
157            .await
158        {
159            Ok(t) => t,
160            Err(e) => {
161                match e.as_ref() {
162                    OidcClientError::OPError(sbe, _) => {
163                        if sbe.error == "slow_down" {
164                            self.increase_interval(5.0);
165                            return Ok(DeviceFlowGrantResponse::SlowDown);
166                        }
167                        if sbe.error == "authorization_pending" {
168                            return Ok(DeviceFlowGrantResponse::AuthorizationPending);
169                        }
170
171                        return Err(e);
172                    }
173                    OidcClientError::Error(_, _)
174                    | OidcClientError::TypeError(_, _)
175                    | OidcClientError::RPError(_, _) => return Err(e),
176                };
177            }
178        };
179
180        if token_set.get_id_token().is_some() {
181            token_set = self.client.decrypt_id_token(token_set)?;
182            token_set = self
183                .client
184                .validate_id_token_async(token_set, None, "token", self.max_age, None, http_client)
185                .await?;
186        }
187
188        Ok(DeviceFlowGrantResponse::Successful(Box::new(token_set)))
189    }
190}