• 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

42.86
/pacman-server/src/auth/provider.rs
1
use async_trait::async_trait;
2
use axum_cookie::CookieManager;
3
use jsonwebtoken::{DecodingKey, EncodingKey};
4
use mockall::automock;
5
use serde::Serialize;
6
use tracing::warn;
7

8
use crate::errors::ErrorResponse;
9
use crate::session;
10

11
// A user object returned from the provider after authentication.
12
#[derive(Debug, Clone, Serialize)]
13
pub struct AuthUser {
14
    // A unique identifier for the user, from the provider.
15
    pub id: String,
16
    // A username from the provider. Generally unique, a handle for the user.
17
    pub username: String,
18

19
    // A display name for the user. Not always available.
20
    pub name: Option<String>,
21
    // An email address for the user. Not always available.
22
    pub email: Option<String>,
23
    // An avatar URL for the user. Not always available.
24
    pub avatar_url: Option<String>,
25
}
26

27
#[automock]
28
#[async_trait]
29
pub trait OAuthProvider: Send + Sync {
30
    // Builds a server response to redirect the user to the provider's authorization page.
31
    // This generally also includes beginning a PKCE flow (proof key for code exchange).
32
    // The cookie manager is used to store the PKCE verifier in the session.
33
    async fn authorize(&self, cookie: &CookieManager, encoding_key: &EncodingKey) -> axum::response::Response;
34

35
    // Handles the callback from the provider after the user has authorized the app.
36
    // This generally also includes completing the PKCE flow (proof key for code exchange).
37
    // The cookie manager is used to retrieve the PKCE verifier from the session.
38
    async fn handle_callback(
39
        &self,
40
        code: &str,
41
        state: &str,
42
        cookie: &CookieManager,
43
        decoding_key: &DecodingKey,
44
    ) -> Result<AuthUser, ErrorResponse> {
×
45
        // Common PKCE session validation and token exchange logic
46
        let verifier = self.validate_pkce_session(cookie, state, decoding_key).await?;
47
        let access_token = self.exchange_code_for_token(code, &verifier).await?;
48
        let user = self.fetch_user_from_token(&access_token).await?;
49
        Ok(user)
50
    }
×
51

52
    // Validates the PKCE session and returns the verifier
53
    async fn validate_pkce_session(
54
        &self,
55
        cookie: &CookieManager,
56
        state: &str,
57
        decoding_key: &DecodingKey,
58
    ) -> Result<String, ErrorResponse> {
×
59
        // Get the session token and verify it's a PKCE session
60
        let Some(session_token) = session::get_session_token(cookie) else {
61
            warn!(%state, "Missing session cookie during OAuth callback");
62
            return Err(ErrorResponse::bad_request(
63
                "invalid_request",
64
                Some("missing session cookie".into()),
65
            ));
66
        };
67

68
        let Some(claims) = session::decode_jwt(&session_token, decoding_key) else {
69
            warn!(%state, "Invalid session token during OAuth callback");
70
            return Err(ErrorResponse::bad_request(
71
                "invalid_request",
72
                Some("invalid session token".into()),
73
            ));
74
        };
75

76
        // Verify this is a PKCE session and the state matches
77
        if !session::is_pkce_session(&claims) {
78
            warn!(%state, "Session is not a PKCE session");
79
            return Err(ErrorResponse::bad_request(
80
                "invalid_request",
81
                Some("invalid session type".into()),
82
            ));
83
        }
84

85
        if claims.csrf_state.as_deref() != Some(state) {
86
            warn!(%state, "CSRF state mismatch during OAuth callback");
87
            return Err(ErrorResponse::bad_request(
88
                "invalid_request",
89
                Some("state parameter mismatch".into()),
90
            ));
91
        }
92

93
        let Some(verifier) = claims.pkce_verifier else {
94
            warn!(%state, "Missing PKCE verifier in session");
95
            return Err(ErrorResponse::bad_request(
96
                "invalid_request",
97
                Some("missing pkce verifier".into()),
98
            ));
99
        };
100

101
        Ok(verifier)
102
    }
×
103

104
    // Exchanges the authorization code for an access token using PKCE
105
    async fn exchange_code_for_token(&self, code: &str, verifier: &str) -> Result<String, ErrorResponse>;
106

107
    // Fetches user information from the provider using the access token
108
    async fn fetch_user_from_token(&self, access_token: &str) -> Result<AuthUser, ErrorResponse>;
109

110
    // The provider's unique identifier (e.g. "discord")
111
    fn id(&self) -> &'static str;
112

113
    // The provider's display name (e.g. "Discord")
114
    fn label(&self) -> &'static str;
115

116
    // Whether the provider is active (defaults to true for now)
117
    fn active(&self) -> bool {
2✔
118
        true
2✔
119
    }
2✔
120
}
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