use crate::error::{AuthErrorOr, Error};
use std::pin::Pin;
use std::time::Duration;
use std::future::Future;
use time::{OffsetDateTime, UtcOffset};
#[derive(Clone, Debug, PartialEq)]
pub struct DeviceAuthResponse {
pub device_code: String,
pub user_code: String,
pub verification_uri: String,
pub expires_at: OffsetDateTime,
pub interval: Duration,
}
impl DeviceAuthResponse {
pub(crate) fn from_json(json_data: &[u8]) -> Result<Self, Error> {
Ok(serde_json::from_slice::<AuthErrorOr<_>>(json_data)?.into_result()?)
}
}
impl<'de> serde::Deserialize<'de> for DeviceAuthResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct RawDeviceAuthResponse {
device_code: String,
user_code: String,
verification_uri: Option<String>,
verification_url: Option<String>,
expires_in: i64,
interval: Option<u64>,
}
let RawDeviceAuthResponse {
device_code,
user_code,
verification_uri,
verification_url,
expires_in,
interval,
} = RawDeviceAuthResponse::deserialize(deserializer)?;
let verification_uri = verification_uri.or(verification_url).ok_or_else(|| {
serde::de::Error::custom("neither verification_uri nor verification_url specified")
})?;
let expires_at = OffsetDateTime::now_utc() + time::Duration::seconds(expires_in);
let interval = Duration::from_secs(interval.unwrap_or(5));
Ok(DeviceAuthResponse {
device_code,
user_code,
verification_uri,
expires_at,
interval,
})
}
}
pub trait DeviceFlowDelegate: Send + Sync {
fn present_user_code<'a>(
&'a self,
device_auth_resp: &'a DeviceAuthResponse,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(present_user_code(device_auth_resp))
}
}
async fn present_user_code(device_auth_resp: &DeviceAuthResponse) {
println!(
"Please enter {} at {} and grant access to this application",
device_auth_resp.user_code, device_auth_resp.verification_uri
);
println!("Do not close this application until you either denied or granted access.");
let printable_time = match UtcOffset::current_local_offset() {
Ok(offset) => device_auth_resp.expires_at.to_offset(offset),
Err(_) => device_auth_resp.expires_at, };
println!("You have time until {}.", printable_time);
}
pub trait InstalledFlowDelegate: Send + Sync {
fn redirect_uri(&self) -> Option<&str> {
None
}
fn present_user_url<'a>(
&'a self,
url: &'a str,
need_code: bool,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + 'a>> {
Box::pin(present_user_url(url, need_code))
}
}
async fn present_user_url(url: &str, need_code: bool) -> Result<String, String> {
use tokio::io::AsyncBufReadExt;
if need_code {
println!(
"Please direct your browser to {}, follow the instructions and enter the \
code displayed here: ",
url
);
let mut user_input = String::new();
tokio::io::BufReader::new(tokio::io::stdin())
.read_line(&mut user_input)
.await
.map_err(|e| format!("couldn't read code: {}", e))?;
user_input.truncate(user_input.trim_end().len());
Ok(user_input)
} else {
println!(
"Please direct your browser to {} and follow the instructions displayed \
there.",
url
);
Ok(String::new())
}
}
#[derive(Copy, Clone)]
pub struct DefaultDeviceFlowDelegate;
impl DeviceFlowDelegate for DefaultDeviceFlowDelegate {}
#[derive(Copy, Clone)]
pub struct DefaultInstalledFlowDelegate;
impl InstalledFlowDelegate for DefaultInstalledFlowDelegate {}
#[derive(Clone)]
pub struct DefaultInstalledFlowDelegateWithRedirectURI(pub String);
impl InstalledFlowDelegate for DefaultInstalledFlowDelegateWithRedirectURI {
fn redirect_uri(&self) -> Option<&str> {
Some(self.0.as_str())
}
}