use std::ops::Add; use std::time::Duration; use futures::TryFutureExt; use reqwest::Client; use serde::{Deserialize, Serialize}; use tokio::time::{Instant, MissedTickBehavior}; use super::AuthError; use crate::util::USER_AGENT; pub struct DeviceCodeAuthBuilder { client_id: Option, scope: Option, url: Option } #[derive(Serialize, Debug)] struct DeviceCodeRequest { client_id: String, scope: String, response_type: String } #[derive(Deserialize, Debug)] struct DeviceCodeResponse { device_code: String, user_code: String, verification_uri: String, expires_in: u64, interval: u64, message: Option } impl DeviceCodeAuthBuilder { pub fn new() -> DeviceCodeAuthBuilder { DeviceCodeAuthBuilder { client_id: None, scope: None, url: None } } pub fn client_id(mut self, client_id: &str) -> Self { self.client_id = Some(client_id.to_owned()); self } pub fn scope(mut self, scope: &str) -> Self { self.scope = Some(scope.to_owned()); self } pub fn url(mut self, url: &str) -> Self { self.url = Some(url.to_owned()); self } pub async fn begin(self, client: Client) -> Result { let scope = self.scope.expect("scope is not optional"); let client_id = self.client_id.expect("client_id is not optional"); let url = self.url.expect("url is not optional"); let device_code: DeviceCodeResponse = client.post(&url) .header(reqwest::header::USER_AGENT, USER_AGENT) .header(reqwest::header::ACCEPT, "application/json") .form(&DeviceCodeRequest { client_id, scope, response_type: "device_code".into() }) .send().await .and_then(|r| r.error_for_status()) .map_err(|e| AuthError::Request { what: "requesting device code auth", error: e })? .json().await.map_err(|e| AuthError::Request { what: "receiving device code auth", error: e })?; let now = Instant::now(); Ok(DeviceCodeAuth { client, start: now, interval: Duration::from_secs(device_code.interval + 1), expire_time: now.add(Duration::from_secs(device_code.expires_in)), info: dbg!(device_code) }) } } pub struct DeviceCodeAuth { client: Client, start: Instant, interval: Duration, expire_time: Instant, info: DeviceCodeResponse } impl DeviceCodeAuth { async fn drive(&self) { let mut i = tokio::time::interval_at(self.start, self.interval); i.set_missed_tick_behavior(MissedTickBehavior::Skip); while self.expire_time.elapsed().is_zero() { } } }