Skip to main content

rs_auth_core/oauth/
github.rs

1use crate::error::{AuthError, OAuthError};
2use crate::oauth::{OAuthProviderConfig, OAuthUserInfo};
3use serde::Deserialize;
4
5#[derive(Deserialize)]
6struct GitHubUserResponse {
7    id: i64,
8    login: String,
9    name: Option<String>,
10    avatar_url: Option<String>,
11    email: Option<String>,
12}
13
14#[derive(Deserialize)]
15struct GitHubEmail {
16    email: String,
17    primary: bool,
18    verified: bool,
19}
20
21pub async fn fetch_user_info(
22    config: &OAuthProviderConfig,
23    access_token: &str,
24) -> Result<OAuthUserInfo, AuthError> {
25    let client = reqwest::Client::new();
26    let user_response = client
27        .get(&config.userinfo_url)
28        .bearer_auth(access_token)
29        .header("User-Agent", "rs-auth")
30        .header("Accept", "application/vnd.github+json")
31        .send()
32        .await
33        .map_err(|_| AuthError::OAuth(OAuthError::UserInfoFailed))?;
34
35    if !user_response.status().is_success() {
36        return Err(AuthError::OAuth(OAuthError::UserInfoFailed));
37    }
38
39    let user: GitHubUserResponse = user_response
40        .json()
41        .await
42        .map_err(|_| AuthError::OAuth(OAuthError::UserInfoMalformed))?;
43
44    let email = if let Some(email) = user.email {
45        email
46    } else {
47        let emails_response = client
48            .get("https://api.github.com/user/emails")
49            .bearer_auth(access_token)
50            .header("User-Agent", "rs-auth")
51            .header("Accept", "application/vnd.github+json")
52            .send()
53            .await
54            .map_err(|_| AuthError::OAuth(OAuthError::UserInfoFailed))?;
55
56        if !emails_response.status().is_success() {
57            return Err(AuthError::OAuth(OAuthError::UserInfoFailed));
58        }
59
60        let emails: Vec<GitHubEmail> = emails_response
61            .json()
62            .await
63            .map_err(|_| AuthError::OAuth(OAuthError::UserInfoMalformed))?;
64        emails
65            .into_iter()
66            .find(|e| e.primary && e.verified)
67            .map(|e| e.email)
68            .ok_or(AuthError::OAuth(OAuthError::MissingEmail))?
69    };
70
71    Ok(OAuthUserInfo {
72        provider_id: "github".to_string(),
73        account_id: user.id.to_string(),
74        email,
75        name: user.name.or(Some(user.login)),
76        image: user.avatar_url,
77    })
78}