• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Xevion / Pac-Man / 17862749257

19 Sep 2025 03:28PM UTC coverage: 66.315% (+0.4%) from 65.884%
17862749257

push

github

Xevion
ci: adjust timeouts for nextest given docker requirements

2514 of 3791 relevant lines covered (66.31%)

21873.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.83
/pacman-server/src/auth/github.rs
1
use axum::{response::IntoResponse, response::Redirect};
2
use axum_cookie::CookieManager;
3
use jsonwebtoken::EncodingKey;
4
use oauth2::{AuthorizationCode, CsrfToken, PkceCodeVerifier, Scope, TokenResponse};
5
use serde::{Deserialize, Serialize};
6

7
use std::sync::Arc;
8
use tracing::{trace, warn};
9

10
use crate::{
11
    auth::provider::{AuthUser, OAuthProvider},
12
    errors::ErrorResponse,
13
    session,
14
};
15

16
#[derive(Debug, Clone, Serialize, Deserialize)]
17
pub struct GitHubUser {
18
    pub id: u64,
19
    pub login: String,
20
    pub name: Option<String>,
21
    pub email: Option<String>,
22
    pub avatar_url: String,
23
    pub html_url: String,
24
}
25

26
#[derive(Debug, Serialize, Deserialize)]
27
pub struct GitHubEmail {
28
    pub email: String,
29
    pub primary: bool,
30
    pub verified: bool,
31
    pub visibility: Option<String>,
32
}
33

34
/// Fetch user information from GitHub API
35
pub async fn fetch_github_user(
×
36
    http_client: &reqwest::Client,
×
37
    access_token: &str,
×
38
) -> Result<GitHubUser, Box<dyn std::error::Error + Send + Sync>> {
×
39
    let response = http_client
×
40
        .get("https://api.github.com/user")
×
41
        .header("Authorization", format!("Bearer {}", access_token))
×
42
        .header("Accept", "application/vnd.github.v3+json")
×
43
        .header("User-Agent", crate::config::USER_AGENT)
×
44
        .send()
×
45
        .await?;
×
46

47
    if !response.status().is_success() {
×
48
        warn!(status = %response.status(), endpoint = "/user", "GitHub API returned an error");
×
49
        return Err(format!("GitHub API error: {}", response.status()).into());
×
50
    }
×
51

52
    let user: GitHubUser = response.json().await?;
×
53
    Ok(user)
×
54
}
×
55

56
pub struct GitHubProvider {
57
    pub client: super::OAuthClient,
58
    pub http: reqwest::Client,
59
}
60

61
impl GitHubProvider {
62
    pub fn new(client: super::OAuthClient, http: reqwest::Client) -> Arc<Self> {
16✔
63
        Arc::new(Self { client, http })
16✔
64
    }
16✔
65
}
66

67
#[async_trait::async_trait]
68
impl OAuthProvider for GitHubProvider {
69
    fn id(&self) -> &'static str {
1✔
70
        "github"
1✔
71
    }
1✔
72
    fn label(&self) -> &'static str {
1✔
73
        "GitHub"
1✔
74
    }
1✔
75

76
    async fn authorize(&self, cookie: &CookieManager, encoding_key: &EncodingKey) -> axum::response::Response {
1✔
77
        let (pkce_challenge, pkce_verifier) = oauth2::PkceCodeChallenge::new_random_sha256();
78
        let (authorize_url, csrf_state) = self
79
            .client
80
            .authorize_url(CsrfToken::new_random)
81
            .set_pkce_challenge(pkce_challenge)
82
            .add_scope(Scope::new("user:email".to_string()))
83
            .add_scope(Scope::new("read:user".to_string()))
84
            .url();
85

86
        // Store PKCE verifier and CSRF state in session
87
        let session_token = session::create_pkce_session(pkce_verifier.secret(), csrf_state.secret(), encoding_key);
88
        session::set_session_cookie(cookie, &session_token);
89

90
        trace!(state = %csrf_state.secret(), "Generated OAuth authorization URL");
91
        Redirect::to(authorize_url.as_str()).into_response()
92
    }
1✔
93

94
    async fn exchange_code_for_token(&self, code: &str, verifier: &str) -> Result<String, ErrorResponse> {
×
95
        let token = self
96
            .client
97
            .exchange_code(AuthorizationCode::new(code.to_string()))
98
            .set_pkce_verifier(PkceCodeVerifier::new(verifier.to_string()))
99
            .request_async(&self.http)
100
            .await
101
            .map_err(|e| {
×
102
                warn!(error = %e, "Token exchange with GitHub failed");
×
103
                ErrorResponse::bad_gateway("token_exchange_failed", Some(e.to_string()))
×
104
            })?;
×
105

106
        Ok(token.access_token().secret().to_string())
107
    }
×
108

109
    async fn fetch_user_from_token(&self, access_token: &str) -> Result<AuthUser, ErrorResponse> {
×
110
        let user = fetch_github_user(&self.http, access_token).await.map_err(|e| {
×
111
            warn!(error = %e, "Failed to fetch GitHub user profile");
×
112
            ErrorResponse::bad_gateway("github_api_error", Some(format!("failed to fetch user: {}", e)))
×
113
        })?;
×
114

115
        Ok(AuthUser {
116
            id: user.id.to_string(),
117
            username: user.login,
118
            name: user.name,
119
            email: user.email,
120
            avatar_url: Some(user.avatar_url),
121
        })
122
    }
×
123
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc