hi_auth/github/
mod.rs

1use async_trait::async_trait;
2use oauth2::{
3    AuthorizationCode,
4    AuthUrl,
5    basic::BasicClient,
6    ClientId,
7    ClientSecret,
8    RequestTokenError,
9    reqwest::async_http_client,
10    reqwest::Error,
11    TokenResponse,
12    TokenUrl,
13};
14use reqwest::header::HeaderMap;
15use serde::Deserialize;
16
17pub struct Client {
18    cli: BasicClient,
19    http_cli: reqwest::Client,
20}
21
22#[derive(Debug, Deserialize)]
23pub struct User {
24    pub id: i64,
25    pub login: String,
26    pub name: String,
27}
28
29#[derive(Debug, Deserialize)]
30pub struct Organization {
31    pub id: i64,
32    pub login: String,
33}
34
35impl Client {
36    pub fn new(client_id: &str, client_secret: &str) -> Self {
37        let cli = BasicClient::new(
38            ClientId::new(client_id.to_string()),
39            Some(ClientSecret::new(client_secret.to_string())),
40            AuthUrl::new("https://github.com/login/oauth/access_token".to_string()).unwrap(),
41            Some(TokenUrl::new("https://github.com/login/oauth/access_token".to_string()).unwrap()),
42        );
43
44        let mut headers = HeaderMap::new();
45        headers.insert("User-Agent", "HiAuth".parse().unwrap());
46        let http_cli = reqwest::Client::builder()
47            .default_headers(headers)
48            .build()
49            .unwrap();
50        Self { cli, http_cli }
51    }
52
53    pub async fn login(&self, code: &str) -> super::Result<String> {
54        Ok(self
55            .cli
56            .exchange_code(AuthorizationCode::new(code.to_string()))
57            .request_async(async_http_client)
58            .await
59            .map(|resp| resp.access_token().secret().to_string())
60            .map_err(|e| {
61                match e {
62                    RequestTokenError::ServerResponse(e) => {
63                        super::Error(e.to_string())
64                    }
65                    RequestTokenError::Request(r) => {
66                        match r {
67                            Error::Reqwest(e) => {
68                                super::Error(e.to_string())
69                            }
70                            e => {
71                                super::Error(e.to_string())
72                            }
73                        }
74                    }
75                    e => {
76                        super::Error(e.to_string())
77                    }
78                }
79            })?)
80    }
81
82    pub async fn user(&self, access_token: &str) -> super::Result<User> {
83        Ok(self
84            .http_cli
85            .get("https://api.github.com/user")
86            .bearer_auth(&access_token)
87            .send()
88            .await?
89            .json::<User>()
90            .await?)
91    }
92    pub async fn orgs(&self, access_token: &str) -> super::Result<Vec<Organization>> {
93        Ok(self
94            .http_cli
95            .get("https://api.github.com/user/orgs")
96            .bearer_auth(&access_token)
97            .send()
98            .await?
99            .json::<Vec<Organization>>()
100            .await?)
101    }
102}
103
104#[async_trait]
105impl super::Profile for Client {
106    async fn userinfo(&self, code: &str) -> crate::Result<super::Userinfo> {
107        let at = self.login(code).await?;
108        let user = self.user(&at).await?;
109        let orgs = self.orgs(&at).await?;
110        Ok(super::Userinfo {
111            unique_id: user.id.to_string(),
112            name: user.name,
113            account: user.login,
114            email: None,
115            organization: Some(orgs.into_iter().map(|e| super::Organization {
116                unique_id: e.id.to_string(),
117                name: e.login.clone(),
118                account: e.login,
119            }).collect()),
120        })
121    }
122}