use crate::auth::AuthSource;
use crate::error::{Error, Result};
use std::env;
use tokio::process::Command;
use tracing::debug;
#[derive(Debug, Clone)]
pub struct GitHubAuthConfig {
pub token: String,
pub source: AuthSource,
}
pub async fn get_github_auth() -> Result<GitHubAuthConfig> {
debug!("attempting to get GitHub token via gh CLI");
if let Some(token) = get_gh_cli_token().await {
debug!("obtained GitHub token from gh CLI");
return Ok(GitHubAuthConfig {
token,
source: AuthSource::Cli,
});
}
debug!("gh CLI token not available, checking env vars");
if let Ok(token) = env::var("GITHUB_TOKEN") {
debug!("obtained GitHub token from GITHUB_TOKEN env var");
return Ok(GitHubAuthConfig {
token,
source: AuthSource::EnvVar,
});
}
if let Ok(token) = env::var("GH_TOKEN") {
debug!("obtained GitHub token from GH_TOKEN env var");
return Ok(GitHubAuthConfig {
token,
source: AuthSource::EnvVar,
});
}
debug!("no GitHub authentication found");
Err(Error::Auth(
"No GitHub authentication found. Run `gh auth login` or set GITHUB_TOKEN".to_string(),
))
}
async fn get_gh_cli_token() -> Option<String> {
Command::new("gh").arg("--version").output().await.ok()?;
let status = Command::new("gh")
.args(["auth", "status"])
.output()
.await
.ok()?;
if !status.status.success() {
return None;
}
let output = Command::new("gh")
.args(["auth", "token"])
.output()
.await
.ok()?;
if !output.status.success() {
return None;
}
let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
if token.is_empty() { None } else { Some(token) }
}
pub async fn test_github_auth(config: &GitHubAuthConfig) -> Result<String> {
let octocrab = octocrab::Octocrab::builder()
.personal_token(config.token.clone())
.build()
.map_err(|e| Error::GitHubApi(e.to_string()))?;
let user = octocrab
.current()
.user()
.await
.map_err(|e| Error::Auth(format!("Invalid token: {e}")))?;
Ok(user.login)
}