rs_auth_core/oauth/
github.rs1use 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}