Skip to main content

jj_ryu/auth/
gitlab.rs

1//! GitLab authentication
2
3use crate::auth::AuthSource;
4use crate::error::{Error, Result};
5use reqwest::Client;
6use serde::Deserialize;
7use std::env;
8use tokio::process::Command;
9use tracing::debug;
10
11/// GitLab authentication configuration
12#[derive(Debug, Clone)]
13pub struct GitLabAuthConfig {
14    /// Authentication token
15    pub token: String,
16    /// Where the token was obtained from
17    pub source: AuthSource,
18    /// GitLab host (e.g., "gitlab.com")
19    pub host: String,
20}
21
22/// Get GitLab authentication
23///
24/// Priority:
25/// 1. glab CLI (`glab auth token`)
26/// 2. `GITLAB_TOKEN` environment variable
27/// 3. `GL_TOKEN` environment variable
28pub async fn get_gitlab_auth(host: Option<&str>) -> Result<GitLabAuthConfig> {
29    let host = host
30        .map(String::from)
31        .or_else(|| env::var("GITLAB_HOST").ok())
32        .unwrap_or_else(|| "gitlab.com".to_string());
33
34    // Try glab CLI first
35    debug!(host = %host, "attempting to get GitLab token via glab CLI");
36    if let Some(token) = get_glab_cli_token(&host).await {
37        debug!("obtained GitLab token from glab CLI");
38        return Ok(GitLabAuthConfig {
39            token,
40            source: AuthSource::Cli,
41            host,
42        });
43    }
44
45    // Try environment variables
46    debug!("glab CLI token not available, checking env vars");
47    if let Ok(token) = env::var("GITLAB_TOKEN") {
48        debug!("obtained GitLab token from GITLAB_TOKEN env var");
49        return Ok(GitLabAuthConfig {
50            token,
51            source: AuthSource::EnvVar,
52            host,
53        });
54    }
55
56    if let Ok(token) = env::var("GL_TOKEN") {
57        debug!("obtained GitLab token from GL_TOKEN env var");
58        return Ok(GitLabAuthConfig {
59            token,
60            source: AuthSource::EnvVar,
61            host,
62        });
63    }
64
65    debug!("no GitLab authentication found");
66    Err(Error::Auth(
67        "No GitLab authentication found. Run `glab auth login` or set GITLAB_TOKEN".to_string(),
68    ))
69}
70
71async fn get_glab_cli_token(host: &str) -> Option<String> {
72    // Check glab is available
73    Command::new("glab").arg("--version").output().await.ok()?;
74
75    // Check authenticated
76    let status = Command::new("glab")
77        .args(["auth", "status", "--hostname", host])
78        .output()
79        .await
80        .ok()?;
81
82    if !status.status.success() {
83        return None;
84    }
85
86    // Get token
87    let output = Command::new("glab")
88        .args(["auth", "token", "--hostname", host])
89        .output()
90        .await
91        .ok()?;
92
93    if !output.status.success() {
94        return None;
95    }
96
97    let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
98    if token.is_empty() { None } else { Some(token) }
99}
100
101#[derive(Deserialize)]
102struct GitLabUser {
103    username: String,
104}
105
106/// Test GitLab authentication
107pub async fn test_gitlab_auth(config: &GitLabAuthConfig) -> Result<String> {
108    let url = format!("https://{}/api/v4/user", config.host);
109
110    let client = Client::builder()
111        .timeout(std::time::Duration::from_secs(30))
112        .build()
113        .map_err(|e| Error::GitLabApi(format!("failed to create HTTP client: {e}")))?;
114
115    let user: GitLabUser = client
116        .get(&url)
117        .header("PRIVATE-TOKEN", &config.token)
118        .send()
119        .await?
120        .error_for_status()
121        .map_err(|e| Error::Auth(format!("Invalid token: {e}")))?
122        .json()
123        .await?;
124
125    Ok(user.username)
126}