yup_oauth2/
authenticator_delegate.rs1use crate::error::{AuthErrorOr, Error};
3
4use std::pin::Pin;
5use std::time::Duration;
6
7use std::future::Future;
8use time::{OffsetDateTime, UtcOffset};
9
10#[derive(Clone, Debug, PartialEq)]
12pub struct DeviceAuthResponse {
13 pub device_code: String,
15 pub user_code: String,
17 pub verification_uri: String,
19 pub expires_at: OffsetDateTime,
22 pub interval: Duration,
25}
26
27impl DeviceAuthResponse {
28 pub(crate) fn from_json(json_data: &[u8]) -> Result<Self, Error> {
29 Ok(serde_json::from_slice::<AuthErrorOr<_>>(json_data)?.into_result()?)
30 }
31}
32
33impl<'de> serde::Deserialize<'de> for DeviceAuthResponse {
34 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35 where
36 D: serde::Deserializer<'de>,
37 {
38 #[derive(serde::Deserialize)]
39 struct RawDeviceAuthResponse {
40 device_code: String,
41 user_code: String,
42 verification_uri: Option<String>,
47 verification_url: Option<String>,
48 expires_in: i64,
49 interval: Option<u64>,
50 }
51
52 let RawDeviceAuthResponse {
53 device_code,
54 user_code,
55 verification_uri,
56 verification_url,
57 expires_in,
58 interval,
59 } = RawDeviceAuthResponse::deserialize(deserializer)?;
60
61 let verification_uri = verification_uri.or(verification_url).ok_or_else(|| {
62 serde::de::Error::custom("neither verification_uri nor verification_url specified")
63 })?;
64 let expires_at = OffsetDateTime::now_utc() + time::Duration::seconds(expires_in);
65 let interval = Duration::from_secs(interval.unwrap_or(5));
66 Ok(DeviceAuthResponse {
67 device_code,
68 user_code,
69 verification_uri,
70 expires_at,
71 interval,
72 })
73 }
74}
75
76pub trait DeviceFlowDelegate: Send + Sync {
79 fn present_user_code<'a>(
84 &'a self,
85 device_auth_resp: &'a DeviceAuthResponse,
86 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
87 Box::pin(present_user_code(device_auth_resp))
88 }
89}
90
91async fn present_user_code(device_auth_resp: &DeviceAuthResponse) {
92 println!(
93 "Please enter {} at {} and grant access to this application",
94 device_auth_resp.user_code, device_auth_resp.verification_uri
95 );
96 println!("Do not close this application until you either denied or granted access.");
97 let printable_time = match UtcOffset::current_local_offset() {
98 Ok(offset) => device_auth_resp.expires_at.to_offset(offset),
99 Err(_) => device_auth_resp.expires_at, };
101 println!("You have time until {}.", printable_time);
102}
103
104pub trait InstalledFlowDelegate: Send + Sync {
107 fn redirect_uri(&self) -> Option<&str> {
109 None
110 }
111
112 fn present_user_url<'a>(
116 &'a self,
117 url: &'a str,
118 need_code: bool,
119 ) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + 'a>> {
120 Box::pin(present_user_url(url, need_code))
121 }
122}
123
124async fn present_user_url(url: &str, need_code: bool) -> Result<String, String> {
125 use tokio::io::AsyncBufReadExt;
126 if need_code {
127 println!(
128 "Please direct your browser to {}, follow the instructions and enter the \
129 code displayed here: ",
130 url
131 );
132 let mut user_input = String::new();
133 tokio::io::BufReader::new(tokio::io::stdin())
134 .read_line(&mut user_input)
135 .await
136 .map_err(|e| format!("couldn't read code: {}", e))?;
137 user_input.truncate(user_input.trim_end().len());
139 Ok(user_input)
140 } else {
141 println!(
142 "Please direct your browser to {} and follow the instructions displayed \
143 there.",
144 url
145 );
146 Ok(String::new())
147 }
148}
149
150#[derive(Copy, Clone)]
152pub struct DefaultDeviceFlowDelegate;
153impl DeviceFlowDelegate for DefaultDeviceFlowDelegate {}
154
155#[derive(Copy, Clone)]
157pub struct DefaultInstalledFlowDelegate;
158impl InstalledFlowDelegate for DefaultInstalledFlowDelegate {}
159
160#[derive(Clone)]
163pub struct DefaultInstalledFlowDelegateWithRedirectURI(pub String);
164impl InstalledFlowDelegate for DefaultInstalledFlowDelegateWithRedirectURI {
165 fn redirect_uri(&self) -> Option<&str> {
166 Some(self.0.as_str())
167 }
168}