openid_client/client/
device_flow_handle.rs

1use std::collections::HashMap;
2
3use crate::types::{
4    grant_params::GrantParams, DeviceAuthorizationExtras, DeviceAuthorizationResponse,
5    DeviceFlowGrantResponse, GrantExtras, OidcClientError, OidcHttpClient, OidcReturnType,
6};
7
8use super::{validate_id_token_params::ValidateIdTokenParams, 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: u64,
17    interval: u64,
18    max_age: Option<u64>,
19    response: DeviceAuthorizationResponse,
20    last_requested: u64,
21    pub(crate) now: fn() -> u64,
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),
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) -> u64 {
52        self.expires_at
53    }
54
55    /// Gets the seconds in which the device code expires
56    pub fn expires_in(&self) -> u64 {
57        let now = (self.now)();
58        if now >= self.expires_at {
59            return 0;
60        }
61
62        self.expires_at - now
63    }
64
65    /// Gets wether the device code is expired or not
66    pub fn expired(&self) -> bool {
67        self.expires_in() == 0
68    }
69
70    /// Gets the polling interval
71    pub fn interval(&self) -> u64 {
72        self.interval
73    }
74
75    /// Increase the interval by `by` seconds
76    pub fn increase_interval(&mut self, by: u64) {
77        self.interval += by;
78    }
79
80    /// Gets the inner client
81    pub fn client(&self) -> &Client {
82        &self.client
83    }
84
85    /// Gets the Device Code
86    pub fn device_code(&self) -> &str {
87        &self.response.device_code
88    }
89
90    /// Gets the User Code
91    pub fn user_code(&self) -> &str {
92        &self.response.user_code
93    }
94
95    /// Gets verification uri
96    pub fn verification_uri(&self) -> &str {
97        &self.response.verification_uri
98    }
99
100    /// Gets the complete verification uri
101    pub fn verification_uri_complete(&self) -> Option<&String> {
102        self.response.verification_uri_complete.as_ref()
103    }
104
105    /// ## Device Flow Grant
106    ///
107    /// Performs grant request at the token endpoint. This method will
108    /// not poll itself. It is left to the implementor to create that logic.
109    ///
110    /// See [DeviceFlowGrantResponse] for the possible responses that will be obtained from the grant.
111    pub async fn grant_async<T>(
112        &mut self,
113        http_client: &T,
114    ) -> OidcReturnType<DeviceFlowGrantResponse>
115    where
116        T: OidcHttpClient,
117    {
118        if self.expired() {
119            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)));
120        }
121
122        if ((self.now)() - self.last_requested) < self.interval {
123            return Ok(DeviceFlowGrantResponse::Debounced);
124        }
125
126        let extras = GrantExtras {
127            client_assertion_payload: self
128                .extras
129                .as_ref()
130                .and_then(|x| x.client_assertion_payload.to_owned()),
131            dpop: self.extras.as_ref().and_then(|x| x.dpop.as_ref()),
132            ..Default::default()
133        };
134
135        let mut body = HashMap::new();
136
137        if let Some(eb) = &self.extras.as_ref().and_then(|x| x.exchange_body.clone()) {
138            for (k, v) in eb {
139                body.insert(k.to_owned(), v.to_owned());
140            }
141        }
142
143        body.insert(
144            "grant_type".to_string(),
145            "urn:ietf:params:oauth:grant-type:device_code".to_owned(),
146        );
147
148        body.insert("device_code".to_string(), self.device_code().to_owned());
149
150        self.last_requested = (self.now)();
151
152        let mut token_set = match self
153            .client
154            .grant_async(
155                http_client,
156                GrantParams {
157                    body,
158                    extras,
159                    retry: true,
160                },
161            )
162            .await
163        {
164            Ok(t) => t,
165            Err(e) => {
166                match e.as_ref() {
167                    OidcClientError::OPError(sbe, _) => {
168                        if sbe.error == "slow_down" {
169                            self.increase_interval(5);
170                            return Ok(DeviceFlowGrantResponse::SlowDown);
171                        }
172                        if sbe.error == "authorization_pending" {
173                            return Ok(DeviceFlowGrantResponse::AuthorizationPending);
174                        }
175
176                        return Err(e);
177                    }
178                    OidcClientError::Error(_, _)
179                    | OidcClientError::TypeError(_, _)
180                    | OidcClientError::RPError(_, _) => return Err(e),
181                };
182            }
183        };
184
185        if token_set.get_id_token().is_some() {
186            token_set = self.client.decrypt_id_token(token_set)?;
187
188            let mut params = ValidateIdTokenParams::new(token_set, "token", http_client);
189
190            if let Some(max_age) = self.max_age {
191                params = params.max_age(max_age);
192            }
193
194            token_set = self.client.validate_id_token_async(params).await?;
195        }
196
197        Ok(DeviceFlowGrantResponse::Successful(Box::new(token_set)))
198    }
199}