openid_client/client/
device_flow_handle.rs1use 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#[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 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 pub fn expires_at(&self) -> i64 {
52 self.expires_at
53 }
54
55 pub fn expires_in(&self) -> i64 {
57 max((Wrapping(self.expires_at) - Wrapping((self.now)())).0, 0)
58 }
59
60 pub fn expired(&self) -> bool {
62 self.expires_in() == 0
63 }
64
65 pub fn interval(&self) -> f64 {
67 self.interval
68 }
69
70 pub fn increase_interval(&mut self, by: f64) {
72 self.interval += by;
73 }
74
75 pub fn client(&self) -> &Client {
77 &self.client
78 }
79
80 pub fn device_code(&self) -> &str {
82 &self.response.device_code
83 }
84
85 pub fn user_code(&self) -> &str {
87 &self.response.user_code
88 }
89
90 pub fn verification_uri(&self) -> &str {
92 &self.response.verification_uri
93 }
94
95 pub fn verification_uri_complete(&self) -> Option<&String> {
97 self.response.verification_uri_complete.as_ref()
98 }
99
100 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}