use crate::config::turnkey::{Config, StoredApiKey};
use anyhow::{anyhow, bail, Context, Result};
use turnkey_api_key_stamper::TurnkeyP256ApiKey;
use turnkey_client::TurnkeyClient;
const NUM_AUTH_ENV_VARS: usize = 3;
const ENV_ORG_ID: &str = "TVC_ORG_ID";
const ENV_API_BASE_URL: &str = "TVC_API_BASE_URL";
const ENV_API_KEY_PUBLIC: &str = "TVC_API_KEY_PUBLIC";
const ENV_API_KEY_PRIVATE: &str = "TVC_API_KEY_PRIVATE";
const DEFAULT_API_BASE_URL: &str = "https://api.turnkey.com";
pub struct AuthenticatedClient {
pub client: TurnkeyClient<TurnkeyP256ApiKey>,
pub org_id: String,
pub api_base_url: String,
}
pub async fn build_client() -> Result<AuthenticatedClient> {
let (org_id, api_base_url, api_key_public, api_key_private) =
match load_credentials_from_env_vars()? {
Some(creds) => creds,
None => load_credentials_from_config().await?,
};
build_authed_client(&org_id, &api_base_url, &api_key_public, &api_key_private)
}
async fn load_credentials_from_config() -> Result<(String, String, String, String)> {
let config = Config::load().await?;
let (alias, org_config) = config
.active_org_config()
.ok_or_else(|| anyhow!("No active organization. Run `tvc login` first."))?;
let api_key = StoredApiKey::load(org_config)
.await?
.ok_or_else(|| anyhow!("No API key found for org '{alias}'. Run `tvc login` first."))?;
Ok((
org_config.id.clone(),
org_config.api_base_url.clone(),
api_key.public_key.clone(),
api_key.private_key.clone(),
))
}
fn build_authed_client(
org_id: &str,
api_base_url: &str,
api_key_public: &str,
api_key_private: &str,
) -> Result<AuthenticatedClient> {
let stamper = TurnkeyP256ApiKey::from_strings(api_key_private, Some(api_key_public))
.context("failed to load API key")?;
let client = TurnkeyClient::builder()
.api_key(stamper)
.base_url(api_base_url)
.build()
.context("failed to build Turnkey client")?;
Ok(AuthenticatedClient {
client,
org_id: org_id.to_string(),
api_base_url: api_base_url.to_string(),
})
}
fn read_env_var(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|s| !s.is_empty())
}
fn load_credentials_from_env_vars() -> Result<Option<(String, String, String, String)>> {
let org_id = read_env_var(ENV_ORG_ID);
let api_key_public = read_env_var(ENV_API_KEY_PUBLIC);
let api_key_private = read_env_var(ENV_API_KEY_PRIVATE);
let api_base_url =
read_env_var(ENV_API_BASE_URL).unwrap_or_else(|| DEFAULT_API_BASE_URL.to_string());
let mut missing: Vec<&str> = Vec::new();
if org_id.is_none() {
missing.push(ENV_ORG_ID);
}
if api_key_public.is_none() {
missing.push(ENV_API_KEY_PUBLIC);
}
if api_key_private.is_none() {
missing.push(ENV_API_KEY_PRIVATE);
}
if missing.len() == NUM_AUTH_ENV_VARS {
return Ok(None);
}
if !missing.is_empty() {
bail!(
"partial env var auth: missing {}. Set all three ({}, {}, {}) env vars or none.",
missing.join(", "),
ENV_ORG_ID,
ENV_API_KEY_PUBLIC,
ENV_API_KEY_PRIVATE,
);
}
Ok(Some((
org_id.unwrap(),
api_base_url,
api_key_public.unwrap(),
api_key_private.unwrap(),
)))
}