2026-04-24 19:22:25 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
2026-04-21 20:30:41 +01:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
use crate::{api, query};
|
2026-04-24 19:22:25 +01:00
|
|
|
|
|
|
|
|
pub(crate) const DEVICE_LOGIN_FLOW_URL: &str = "https://github.com/login/device";
|
2026-04-21 20:30:41 +01:00
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2026-04-22 12:41:33 +01:00
|
|
|
pub struct CreateDeviceCode;
|
2026-04-21 20:30:41 +01:00
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
2026-04-24 19:22:25 +01:00
|
|
|
pub(crate) struct DeviceCodeResponse {
|
|
|
|
|
pub device_code: String,
|
|
|
|
|
pub user_code: String,
|
|
|
|
|
pub vertification_uri: Option<String>,
|
|
|
|
|
pub expires_in: u16,
|
2026-04-25 00:49:50 +01:00
|
|
|
|
|
|
|
|
// minimum number of seconds between polling for access token
|
2026-04-24 19:22:25 +01:00
|
|
|
pub interval: u16,
|
2026-04-21 20:30:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl query::QueryFn for CreateDeviceCode {
|
|
|
|
|
type Data = DeviceCodeResponse;
|
|
|
|
|
type Error = api::Error;
|
|
|
|
|
type Context = api::QueryContext;
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
fn key(&self) -> query::Key {
|
|
|
|
|
"auth/device_code".into()
|
2026-04-21 20:30:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
|
2026-04-24 19:22:25 +01:00
|
|
|
let res = c
|
2026-04-21 20:30:41 +01:00
|
|
|
.http
|
|
|
|
|
.post(format!(
|
|
|
|
|
"https://github.com/login/device/code?client_id={}",
|
|
|
|
|
c.github.client_id
|
|
|
|
|
))
|
2026-04-24 19:22:25 +01:00
|
|
|
.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;
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
fn key(&self) -> query::Key {
|
|
|
|
|
"auth.access_token".into()
|
2026-04-24 19:22:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
|
|
|
|
|
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
|
|
|
|
|
))
|
2026-04-25 00:49:50 +01:00
|
|
|
.header("Accept", "application/json")
|
2026-04-24 19:22:25 +01:00
|
|
|
.form(¶ms)
|
2026-04-21 20:30:41 +01:00
|
|
|
.send()
|
|
|
|
|
.await?;
|
2026-04-25 00:49:50 +01:00
|
|
|
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::<api::GithubError>(json)?;
|
|
|
|
|
Err(api::Error::Github(error))
|
|
|
|
|
} else {
|
|
|
|
|
let res = serde_json::from_value::<RequestAccessTokenResponse>(json)?;
|
|
|
|
|
Ok(res)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
serde_json::from_slice::<api::GithubError>(&data)
|
|
|
|
|
.map_err(|e| e.into())
|
|
|
|
|
.and_then(|e| Err(api::Error::Github(e)))
|
|
|
|
|
}
|
2026-04-21 20:30:41 +01:00
|
|
|
}
|
|
|
|
|
}
|