pub mod cache;
mod error;
pub(crate) mod http;
mod scope;
use std::fmt;
pub use error::CredentialError;
pub use scope::{
AuthHeader, AuthScheme, CredentialScope, PROJECT_PLACEHOLDER, REGION_PLACEHOLDER, fill_endpoint,
};
use crate::google::{SERVICE_ACCOUNT_TYPE, ServiceAccountKey, access_token};
#[derive(Clone, PartialEq, Eq)]
pub struct ApiKeySecret(String);
impl ApiKeySecret {
#[must_use]
pub fn expose(&self) -> &str {
&self.0
}
}
impl fmt::Debug for ApiKeySecret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ApiKeySecret(<redacted>)")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CredentialKind {
ApiKey,
GoogleServiceAccount,
}
impl CredentialKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ApiKey => "api_key",
Self::GoogleServiceAccount => "google_service_account",
}
}
}
#[derive(Debug, Clone)]
pub enum ProviderCredential {
ApiKey(ApiKeySecret),
GoogleServiceAccount(Box<ServiceAccountKey>),
}
impl ProviderCredential {
pub fn parse(secret: &str) -> Result<Self, CredentialError> {
let Ok(value) = serde_json::from_str::<serde_json::Value>(secret) else {
return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
};
if value.get("type").and_then(serde_json::Value::as_str) != Some(SERVICE_ACCOUNT_TYPE) {
return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
}
serde_json::from_value::<ServiceAccountKey>(value)
.map(|key| Self::GoogleServiceAccount(Box::new(key)))
.map_err(|e| CredentialError::Malformed(e.to_string()))
}
#[must_use]
pub const fn kind(&self) -> CredentialKind {
match self {
Self::ApiKey(_) => CredentialKind::ApiKey,
Self::GoogleServiceAccount(_) => CredentialKind::GoogleServiceAccount,
}
}
#[must_use]
pub fn scope(&self) -> CredentialScope {
match self {
Self::ApiKey(_) => CredentialScope::empty(),
Self::GoogleServiceAccount(key) => CredentialScope {
project: Some(key.project_id.clone()),
region: None,
principal: Some(key.client_email.clone()),
},
}
}
pub async fn bearer(&self, cache_key: &str) -> Result<AuthHeader, CredentialError> {
match self {
Self::ApiKey(key) => Ok(AuthHeader {
scheme: AuthScheme::ApiKey,
value: key.expose().to_owned(),
}),
Self::GoogleServiceAccount(key) => {
let key_id = format!("{}:{cache_key}", self.kind().as_str());
Ok(AuthHeader {
scheme: AuthScheme::Bearer,
value: access_token(&key_id, key).await?,
})
},
}
}
pub fn fill_endpoint(&self, template: &str) -> Result<String, CredentialError> {
fill_endpoint(template, &self.scope())
}
}