use std::sync::Arc;
use hyper_util::client::legacy::connect::HttpConnector;
use tokio::sync::Mutex;
use yup_oauth2::authenticator::Authenticator;
use yup_oauth2::{ServiceAccountAuthenticator, ServiceAccountKey as OAuthKey, hyper_rustls};
use crate::config::ServiceAccountKey;
use crate::error::{ProxyError, Result};
use crate::provider::AuthStrategy;
pub enum RequestAuth {
Gcp(Arc<GcpAuthProvider>),
Bearer(String),
}
impl RequestAuth {
pub async fn from_strategy(strategy: &AuthStrategy) -> Result<Self> {
match strategy {
AuthStrategy::GcpOAuth2(key) => {
let provider = GcpAuthProvider::new(key).await?;
Ok(Self::Gcp(Arc::new(provider)))
}
AuthStrategy::BearerToken(token) => Ok(Self::Bearer(token.clone())),
}
}
pub async fn authorization_header_value(&self) -> Result<String> {
match self {
Self::Gcp(gcp) => {
let token = gcp.get_access_token().await?;
Ok(format!("Bearer {}", token))
}
Self::Bearer(t) => Ok(format!("Bearer {}", t)),
}
}
}
pub struct GcpAuthProvider {
authenticator: Arc<Mutex<ServiceAccountAuth>>,
}
const CLOUD_PLATFORM_SCOPE: &str = "https://www.googleapis.com/auth/cloud-platform";
type ServiceAccountAuth = Authenticator<hyper_rustls::HttpsConnector<HttpConnector>>;
impl GcpAuthProvider {
pub async fn new(service_account_key: &ServiceAccountKey) -> Result<Self> {
let oauth_key = Self::convert_service_account_key(service_account_key);
let authenticator = Self::create_authenticator(oauth_key).await?;
Ok(Self { authenticator: Arc::new(Mutex::new(authenticator)) })
}
pub async fn get_access_token(&self) -> Result<String> {
let scopes = &[CLOUD_PLATFORM_SCOPE];
let guard = self.authenticator.lock().await;
let token = guard
.token(scopes)
.await
.map_err(|e| ProxyError::Auth(format!("Failed to get access token: {}", e)))?;
token
.token()
.ok_or_else(|| ProxyError::Auth("Access token is missing from response".to_string()))
.map(|s| s.to_string())
}
fn convert_service_account_key(service_account_key: &ServiceAccountKey) -> OAuthKey {
OAuthKey {
key_type: Some("service_account".to_string()),
project_id: Some(service_account_key.project_id.clone()),
private_key_id: Some(service_account_key.private_key_id.clone()),
private_key: service_account_key.private_key.clone(),
client_email: service_account_key.client_email.clone(),
client_id: Some(service_account_key.client_id.clone()),
auth_uri: Some(service_account_key.auth_uri.clone()),
token_uri: service_account_key.token_uri.clone(),
auth_provider_x509_cert_url: Some(
service_account_key.auth_provider_x509_cert_url.clone(),
),
client_x509_cert_url: Some(service_account_key.client_x509_cert_url.clone()),
}
}
async fn create_authenticator(oauth_key: OAuthKey) -> Result<ServiceAccountAuth> {
ServiceAccountAuthenticator::builder(oauth_key)
.build()
.await
.map_err(|e| ProxyError::Auth(format!("Failed to create authenticator: {}", e)))
}
}