use std::env;
use std::fmt::{Debug, Formatter};
use reqwest::Client;
use serde::Deserialize;
use tracing::debug;
use url::Url;
use super::jwt_bearer;
use crate::http::{Accepted as _, shared_client};
use crate::{CredentialResult, Token};
pub struct GitHubAuthProvider {
client: Client,
token_endpoint: Url,
workload_id: String,
}
impl Debug for GitHubAuthProvider {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GitHubAuthProvider").finish()
}
}
impl GitHubAuthProvider {
pub fn new(token_endpoint: Url, workload_id: String) -> Self {
GitHubAuthProvider {
client: shared_client(),
token_endpoint,
workload_id,
}
}
}
#[derive(Deserialize)]
struct GitHubTokenResponse {
value: String,
}
pub async fn fetch_github_oidc_token(client: &Client, audience: &str) -> CredentialResult<Token> {
let id_token = env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
.map_err(|_| invalid!("ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variable not set"))?;
let id_token_request_url = env::var("ACTIONS_ID_TOKEN_REQUEST_URL")
.map_err(|_| invalid!("ACTIONS_ID_TOKEN_REQUEST_URL environment variable not set"))?;
let response = client
.get(format!("{id_token_request_url}&audience={audience}"))
.header("Authorization", format!("Bearer {id_token}"))
.send()
.await?
.accepted()
.await?
.json::<GitHubTokenResponse>()
.await?;
Ok(Token::from(response.value))
}
impl GitHubAuthProvider {
pub async fn token(&self) -> CredentialResult<Option<Token>> {
if env::var("GITHUB_ACTIONS").ok().is_none() {
return Ok(None);
}
let github_token =
fetch_github_oidc_token(&self.client, self.token_endpoint.as_str()).await?;
let token = jwt_bearer(
&self.client,
&self.token_endpoint,
self.workload_id.clone(),
github_token.expose_secret().to_string(),
None,
)
.await?;
debug!("GitHubAuthProvider obtained a workload token");
Ok(Some(token))
}
}
impl_token_source!(GitHubAuthProvider);