Skip to main content

jj_ryu/auth/
github.rs

1//! GitHub authentication
2
3use crate::auth::AuthSource;
4use crate::error::{Error, Result};
5use std::env;
6use tokio::process::Command;
7use tracing::debug;
8
9/// GitHub authentication configuration
10#[derive(Debug, Clone)]
11pub struct GitHubAuthConfig {
12    /// Authentication token
13    pub token: String,
14    /// Where the token was obtained from
15    pub source: AuthSource,
16}
17
18/// Get GitHub authentication
19///
20/// Priority:
21/// 1. gh CLI (`gh auth token`)
22/// 2. `GITHUB_TOKEN` environment variable
23/// 3. `GH_TOKEN` environment variable
24pub async fn get_github_auth() -> Result<GitHubAuthConfig> {
25    // Try gh CLI first
26    debug!("attempting to get GitHub token via gh CLI");
27    if let Some(token) = get_gh_cli_token().await {
28        debug!("obtained GitHub token from gh CLI");
29        return Ok(GitHubAuthConfig {
30            token,
31            source: AuthSource::Cli,
32        });
33    }
34
35    // Try environment variables
36    debug!("gh CLI token not available, checking env vars");
37    if let Ok(token) = env::var("GITHUB_TOKEN") {
38        debug!("obtained GitHub token from GITHUB_TOKEN env var");
39        return Ok(GitHubAuthConfig {
40            token,
41            source: AuthSource::EnvVar,
42        });
43    }
44
45    if let Ok(token) = env::var("GH_TOKEN") {
46        debug!("obtained GitHub token from GH_TOKEN env var");
47        return Ok(GitHubAuthConfig {
48            token,
49            source: AuthSource::EnvVar,
50        });
51    }
52
53    debug!("no GitHub authentication found");
54    Err(Error::Auth(
55        "No GitHub authentication found. Run `gh auth login` or set GITHUB_TOKEN".to_string(),
56    ))
57}
58
59async fn get_gh_cli_token() -> Option<String> {
60    // Check gh is available
61    Command::new("gh").arg("--version").output().await.ok()?;
62
63    // Check authenticated
64    let status = Command::new("gh")
65        .args(["auth", "status"])
66        .output()
67        .await
68        .ok()?;
69
70    if !status.status.success() {
71        return None;
72    }
73
74    // Get token
75    let output = Command::new("gh")
76        .args(["auth", "token"])
77        .output()
78        .await
79        .ok()?;
80
81    if !output.status.success() {
82        return None;
83    }
84
85    let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
86    if token.is_empty() { None } else { Some(token) }
87}
88
89/// Test GitHub authentication
90pub async fn test_github_auth(config: &GitHubAuthConfig) -> Result<String> {
91    let octocrab = octocrab::Octocrab::builder()
92        .personal_token(config.token.clone())
93        .build()
94        .map_err(|e| Error::GitHubApi(e.to_string()))?;
95
96    let user = octocrab
97        .current()
98        .user()
99        .await
100        .map_err(|e| Error::Auth(format!("Invalid token: {e}")))?;
101
102    Ok(user.login)
103}