armature_auth/providers/
github.rs1use crate::error::AuthError;
4use crate::oauth2::OAuth2Config;
5use serde::{Deserialize, Serialize};
6
7const AUTH_URL: &str = "https://github.com/login/oauth/authorize";
8const TOKEN_URL: &str = "https://github.com/login/oauth/access_token";
9const USER_INFO_URL: &str = "https://api.github.com/user";
10const USER_EMAIL_URL: &str = "https://api.github.com/user/emails";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct GitHubUser {
15 pub id: u64,
16 pub login: String,
17 pub name: Option<String>,
18 pub email: Option<String>,
19 pub avatar_url: Option<String>,
20 pub bio: Option<String>,
21 pub company: Option<String>,
22 pub location: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27struct GitHubEmail {
28 email: String,
29 primary: bool,
30 verified: bool,
31}
32
33pub struct GitHubProvider;
35
36impl GitHubProvider {
37 pub fn config(client_id: String, client_secret: String, redirect_url: String) -> OAuth2Config {
39 OAuth2Config::new(
40 client_id,
41 client_secret,
42 AUTH_URL.to_string(),
43 TOKEN_URL.to_string(),
44 redirect_url,
45 )
46 .with_scopes(vec!["user:email".to_string()])
47 .with_user_info_url(USER_INFO_URL.to_string())
48 }
49
50 pub async fn get_user_info(access_token: &str) -> Result<GitHubUser, AuthError> {
52 let client = reqwest::Client::new();
53
54 let mut user: GitHubUser = client
56 .get(USER_INFO_URL)
57 .header("Authorization", format!("token {}", access_token))
58 .header("User-Agent", "Armature-Auth")
59 .send()
60 .await
61 .map_err(|e| AuthError::HttpRequest(e.to_string()))?
62 .json()
63 .await
64 .map_err(|e| AuthError::InvalidResponse(e.to_string()))?;
65
66 if user.email.is_none() {
68 let emails: Vec<GitHubEmail> = client
69 .get(USER_EMAIL_URL)
70 .header("Authorization", format!("token {}", access_token))
71 .header("User-Agent", "Armature-Auth")
72 .send()
73 .await
74 .map_err(|e| AuthError::HttpRequest(e.to_string()))?
75 .json()
76 .await
77 .map_err(|e| AuthError::InvalidResponse(e.to_string()))?;
78
79 user.email = emails
81 .iter()
82 .find(|e| e.primary && e.verified)
83 .or_else(|| emails.first())
84 .map(|e| e.email.clone());
85 }
86
87 Ok(user)
88 }
89}