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}