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