yup_oauth2/
authenticator_delegate.rs

1//! Module containing types related to delegates.
2use crate::error::{AuthErrorOr, Error};
3
4use std::pin::Pin;
5use std::time::Duration;
6
7use std::future::Future;
8use time::{OffsetDateTime, UtcOffset};
9
10/// Contains state of pending authentication requests
11#[derive(Clone, Debug, PartialEq)]
12pub struct DeviceAuthResponse {
13    /// The device verification code.
14    pub device_code: String,
15    /// Code the user must enter ...
16    pub user_code: String,
17    /// ... at the verification URI
18    pub verification_uri: String,
19    /// The `user_code` expires at the given time
20    /// It's the time the user has left to authenticate your application
21    pub expires_at: OffsetDateTime,
22    /// The interval in which we may poll for a status change
23    /// The server responds with errors of we poll too fast.
24    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            // The standard dictates that verification_uri is required, but
43            // sadly google uses verification_url currently. One of these two
44            // fields need to be set and verification_uri takes precedence if
45            // they both are set.
46            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
76/// DeviceFlowDelegate methods are called when a device flow needs to ask the
77/// application what to do in certain cases.
78pub trait DeviceFlowDelegate: Send + Sync {
79    /// The server has returned a `user_code` which must be shown to the user,
80    /// along with the `verification_uri`.
81    /// # Notes
82    /// * Will be called exactly once, provided we didn't abort during `request_code` phase.
83    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, // Fallback to printing in UTC
100    };
101    println!("You have time until {}.", printable_time);
102}
103
104/// InstalledFlowDelegate methods are called when an installed flow needs to ask
105/// the application what to do in certain cases.
106pub trait InstalledFlowDelegate: Send + Sync {
107    /// Configure a custom redirect uri if needed.
108    fn redirect_uri(&self) -> Option<&str> {
109        None
110    }
111
112    /// We need the user to navigate to a URL using their browser and potentially paste back a code
113    /// (or maybe not). Whether they have to enter a code depends on the InstalledFlowReturnMethod
114    /// used.
115    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        // remove trailing whitespace.
138        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/// Uses all default implementations in the DeviceFlowDelegate trait.
151#[derive(Copy, Clone)]
152pub struct DefaultDeviceFlowDelegate;
153impl DeviceFlowDelegate for DefaultDeviceFlowDelegate {}
154
155/// Uses all default implementations in the DeviceFlowDelegate trait.
156#[derive(Copy, Clone)]
157pub struct DefaultInstalledFlowDelegate;
158impl InstalledFlowDelegate for DefaultInstalledFlowDelegate {}
159
160/// The default installed-flow delegate (i.e.: show URL on stdout). Use this to specify
161/// a custom redirect URL.
162#[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}