1use 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#[derive(Debug, Clone)]
13pub struct GitLabAuthConfig {
14 pub token: String,
16 pub source: AuthSource,
18 pub host: String,
20}
21
22pub 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 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 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 Command::new("glab").arg("--version").output().await.ok()?;
74
75 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 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
106pub 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}