oauth_device_flows/
types.rs1use secrecy::{ExposeSecret, Secret};
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6use time::OffsetDateTime;
7use url::Url;
8
9#[derive(Debug, Clone)]
11pub struct AuthorizationResponse {
12 pub device_code: Secret<String>,
14
15 pub user_code: String,
17
18 pub verification_uri: Url,
20
21 pub verification_uri_complete: Option<Url>,
23
24 pub expires_in: u64,
26
27 pub interval: u64,
29}
30
31impl AuthorizationResponse {
32 pub fn device_code(&self) -> &str {
34 self.device_code.expose_secret()
35 }
36
37 pub fn user_code(&self) -> &str {
39 &self.user_code
40 }
41
42 pub fn verification_uri(&self) -> &Url {
44 &self.verification_uri
45 }
46
47 pub fn verification_uri_complete(&self) -> Option<&Url> {
49 self.verification_uri_complete.as_ref()
50 }
51
52 pub fn expires_in(&self) -> Duration {
54 Duration::from_secs(self.expires_in)
55 }
56
57 pub fn poll_interval(&self) -> Duration {
59 Duration::from_secs(self.interval)
60 }
61
62 #[cfg(feature = "qr-codes")]
64 pub fn generate_qr_code(&self) -> Result<String, crate::error::DeviceFlowError> {
65 use qrcode::{render::unicode, QrCode};
66
67 let uri = self
68 .verification_uri_complete
69 .as_ref()
70 .unwrap_or(&self.verification_uri);
71
72 let code = QrCode::new(uri.as_str())?;
73 Ok(code
74 .render::<unicode::Dense1x2>()
75 .dark_color(unicode::Dense1x2::Light)
76 .light_color(unicode::Dense1x2::Dark)
77 .build())
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct TokenResponse {
84 pub access_token: Secret<String>,
86
87 pub token_type: String,
89
90 pub expires_in: Option<u64>,
92
93 pub refresh_token: Option<Secret<String>>,
95
96 pub scope: Option<String>,
98
99 pub issued_at: OffsetDateTime,
101}
102
103impl TokenResponse {
104 pub fn access_token(&self) -> &str {
106 self.access_token.expose_secret()
107 }
108
109 pub fn refresh_token(&self) -> Option<&str> {
111 self.refresh_token
112 .as_ref()
113 .map(|t| t.expose_secret().as_str())
114 }
115
116 pub fn is_expired(&self) -> bool {
118 if let Some(expires_in) = self.expires_in {
119 let expiry = self.issued_at + time::Duration::seconds(expires_in as i64);
120 OffsetDateTime::now_utc() >= expiry
121 } else {
122 false
123 }
124 }
125
126 pub fn expires_at(&self) -> Option<OffsetDateTime> {
128 self.expires_in
129 .map(|expires_in| self.issued_at + time::Duration::seconds(expires_in as i64))
130 }
131
132 pub fn remaining_lifetime(&self) -> Option<Duration> {
134 self.expires_at().map(|expires_at| {
135 let remaining = expires_at - OffsetDateTime::now_utc();
136 Duration::from_secs(remaining.whole_seconds().max(0) as u64)
137 })
138 }
139
140 pub fn expires_within(&self, duration: Duration) -> bool {
142 if let Some(remaining) = self.remaining_lifetime() {
143 remaining <= duration
144 } else {
145 false
146 }
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ErrorResponse {
153 pub error: String,
155
156 pub error_description: Option<String>,
158
159 pub error_uri: Option<Url>,
161}
162
163#[derive(Debug, Clone, Serialize)]
165pub struct DeviceAuthorizationRequest {
166 pub client_id: String,
168
169 pub scope: Option<String>,
171}
172
173#[derive(Debug, Clone, Serialize)]
175pub struct DeviceTokenRequest {
176 pub grant_type: String,
178
179 pub device_code: String,
181
182 pub client_id: String,
184}
185
186#[derive(Debug, Clone, Serialize)]
188pub struct RefreshTokenRequest {
189 pub grant_type: String,
191
192 pub refresh_token: String,
194
195 pub client_id: String,
197
198 pub scope: Option<String>,
200}
201
202impl<'de> Deserialize<'de> for AuthorizationResponse {
204 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205 where
206 D: serde::Deserializer<'de>,
207 {
208 #[derive(Deserialize)]
209 struct AuthorizationResponseHelper {
210 device_code: String,
211 user_code: String,
212 verification_uri: Url,
213 verification_uri_complete: Option<Url>,
214 expires_in: u64,
215 interval: u64,
216 }
217
218 let helper = AuthorizationResponseHelper::deserialize(deserializer)?;
219 Ok(AuthorizationResponse {
220 device_code: Secret::new(helper.device_code),
221 user_code: helper.user_code,
222 verification_uri: helper.verification_uri,
223 verification_uri_complete: helper.verification_uri_complete,
224 expires_in: helper.expires_in,
225 interval: helper.interval,
226 })
227 }
228}
229
230impl<'de> Deserialize<'de> for TokenResponse {
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: serde::Deserializer<'de>,
235 {
236 #[derive(Deserialize)]
237 struct TokenResponseHelper {
238 access_token: String,
239 token_type: String,
240 expires_in: Option<u64>,
241 refresh_token: Option<String>,
242 scope: Option<String>,
243 }
244
245 let helper = TokenResponseHelper::deserialize(deserializer)?;
246 Ok(TokenResponse {
247 access_token: Secret::new(helper.access_token),
248 token_type: helper.token_type,
249 expires_in: helper.expires_in,
250 refresh_token: helper.refresh_token.map(Secret::new),
251 scope: helper.scope,
252 issued_at: OffsetDateTime::now_utc(),
253 })
254 }
255}