use std::collections::HashMap; use serde::Deserialize; use crate::{api, query}; pub(crate) const DEVICE_LOGIN_FLOW_URL: &str = "https://github.com/login/device"; #[derive(Clone)] pub struct CreateDeviceCode; #[derive(Deserialize)] pub(crate) struct DeviceCodeResponse { pub device_code: String, pub user_code: String, pub vertification_uri: Option, pub expires_in: u16, // minimum number of seconds between polling for access token pub interval: u16, } impl query::QueryFn for CreateDeviceCode { type Data = DeviceCodeResponse; type Error = api::Error; type Context = api::QueryContext; fn key(&self) -> query::Key { "auth/device_code".into() } async fn run(&self, c: &Self::Context) -> Result { let res = c .http .post(format!( "https://github.com/login/device/code?client_id={}", c.github.client_id )) .header("Accept", "application/json") .send() .await?; api::parse_response(res).await } } #[derive(Clone)] pub struct RequestAccessToken { pub device_code: String, } #[derive(Deserialize)] pub struct RequestAccessTokenResponse { pub access_token: String, pub token_type: String, pub scope: String, } impl query::QueryFn for RequestAccessToken { type Data = RequestAccessTokenResponse; type Error = api::Error; type Context = api::QueryContext; fn key(&self) -> query::Key { "auth.access_token".into() } async fn run(&self, c: &Self::Context) -> Result { let mut params = HashMap::new(); params.insert("client_id", c.github.client_id); params.insert("device_code", &self.device_code); params.insert("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); let res = c .http .post(format!( "https://github.com/login/oauth/access_token?client_id={}&device_code={}", c.github.client_id, self.device_code )) .header("Accept", "application/json") .form(¶ms) .send() .await?; let status = res.status(); let data = res.bytes().await?; println!("status: {:?}, data: {:?}", status, str::from_utf8(&data)); if status.is_success() { // for device code flow, github returns error with 200 response code // so the body is either a valid access token or an error let json: serde_json::Value = serde_json::from_slice(&data)?; let maybe_error = &json["error"]; if maybe_error.is_string() { let error = serde_json::from_value::(json)?; Err(api::Error::Github(error)) } else { let res = serde_json::from_value::(json)?; Ok(res) } } else { serde_json::from_slice::(&data) .map_err(|e| e.into()) .and_then(|e| Err(api::Error::Github(e))) } } }