use crate::error::AuthError;
use crate::oauth2::OAuth2Config;
use serde::{Deserialize, Serialize};
const AUTH_URL: &str = "https://github.com/login/oauth/authorize";
const TOKEN_URL: &str = "https://github.com/login/oauth/access_token";
const USER_INFO_URL: &str = "https://api.github.com/user";
const USER_EMAIL_URL: &str = "https://api.github.com/user/emails";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitHubUser {
pub id: u64,
pub login: String,
pub name: Option<String>,
pub email: Option<String>,
pub avatar_url: Option<String>,
pub bio: Option<String>,
pub company: Option<String>,
pub location: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GitHubEmail {
email: String,
primary: bool,
verified: bool,
}
pub struct GitHubProvider;
impl GitHubProvider {
pub fn config(client_id: String, client_secret: String, redirect_url: String) -> OAuth2Config {
OAuth2Config::new(
client_id,
client_secret,
AUTH_URL.to_string(),
TOKEN_URL.to_string(),
redirect_url,
)
.with_scopes(vec!["user:email".to_string()])
.with_user_info_url(USER_INFO_URL.to_string())
}
pub async fn get_user_info(access_token: &str) -> Result<GitHubUser, AuthError> {
let client = reqwest::Client::new();
let mut user: GitHubUser = client
.get(USER_INFO_URL)
.header("Authorization", format!("token {}", access_token))
.header("User-Agent", "Armature-Auth")
.send()
.await
.map_err(|e| AuthError::HttpRequest(e.to_string()))?
.json()
.await
.map_err(|e| AuthError::InvalidResponse(e.to_string()))?;
if user.email.is_none() {
let emails: Vec<GitHubEmail> = client
.get(USER_EMAIL_URL)
.header("Authorization", format!("token {}", access_token))
.header("User-Agent", "Armature-Auth")
.send()
.await
.map_err(|e| AuthError::HttpRequest(e.to_string()))?
.json()
.await
.map_err(|e| AuthError::InvalidResponse(e.to_string()))?;
user.email = emails
.iter()
.find(|e| e.primary && e.verified)
.or_else(|| emails.first())
.map(|e| e.email.clone());
}
Ok(user)
}
}