use crate::errors::{ClientError, DPoPError};
use anyhow::Result;
use atproto_identity::key::KeyData;
use atproto_oauth::dpop::{DpopRetry, request_dpop};
use bytes::Bytes;
use reqwest::header::HeaderMap;
use reqwest_chain::ChainMiddleware;
use reqwest_middleware::ClientBuilder;
use tracing::Instrument;
#[derive(Clone)]
pub struct DPoPAuth {
pub dpop_private_key_data: KeyData,
pub oauth_access_token: String,
}
#[derive(Clone)]
pub struct AppPasswordAuth {
pub access_token: String,
}
#[derive(Clone)]
pub enum Auth {
None,
DPoP(DPoPAuth),
AppPassword(AppPasswordAuth),
}
pub async fn get_json(http_client: &reqwest::Client, url: &str) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
get_json_with_headers(http_client, url, &empty).await
}
pub async fn get_json_with_headers(
http_client: &reqwest::Client,
url: &str,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let http_response = http_client
.get(url)
.headers(additional_headers.clone())
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| ClientError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn get_bytes(http_client: &reqwest::Client, url: &str) -> Result<Bytes> {
let empty = HeaderMap::default();
get_bytes_with_headers(http_client, url, &empty).await
}
pub async fn get_bytes_with_headers(
http_client: &reqwest::Client,
url: &str,
additional_headers: &HeaderMap,
) -> Result<Bytes> {
let http_response = http_client
.get(url)
.headers(additional_headers.clone())
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
Ok(http_response
.bytes()
.await
.map_err(|error| ClientError::ByteStreamFailed {
url: url.to_string(),
error,
})?)
}
pub async fn get_dpop_json(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
url: &str,
) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
get_dpop_json_with_headers(http_client, dpop_auth, url, &empty).await
}
pub async fn get_dpop_json_with_headers(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
url: &str,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let (dpop_proof_token, dpop_proof_header, dpop_proof_claim) = request_dpop(
&dpop_auth.dpop_private_key_data,
"GET",
url,
&dpop_auth.oauth_access_token,
)
.map_err(|error| DPoPError::ProofGenerationFailed { error })?;
let dpop_retry = DpopRetry::new(
dpop_proof_header.clone(),
dpop_proof_claim.clone(),
dpop_auth.dpop_private_key_data.clone(),
true,
);
let dpop_retry_client = ClientBuilder::new(http_client.clone())
.with(ChainMiddleware::new(dpop_retry.clone()))
.build();
let http_response = dpop_retry_client
.get(url)
.headers(additional_headers.clone())
.header(
"Authorization",
&format!("DPoP {}", dpop_auth.oauth_access_token),
)
.header("DPoP", &dpop_proof_token)
.send()
.await
.map_err(|error| DPoPError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| DPoPError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn post_dpop_json(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
url: &str,
record: serde_json::Value,
) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
post_dpop_json_with_headers(http_client, dpop_auth, url, record, &empty).await
}
pub async fn post_dpop_json_with_headers(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
url: &str,
record: serde_json::Value,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let (dpop_proof_token, dpop_proof_header, dpop_proof_claim) = request_dpop(
&dpop_auth.dpop_private_key_data,
"POST",
url,
&dpop_auth.oauth_access_token,
)
.map_err(|error| DPoPError::ProofGenerationFailed { error })?;
let dpop_retry = DpopRetry::new(
dpop_proof_header.clone(),
dpop_proof_claim.clone(),
dpop_auth.dpop_private_key_data.clone(),
true,
);
let dpop_retry_client = ClientBuilder::new(http_client.clone())
.with(ChainMiddleware::new(dpop_retry.clone()))
.build();
let http_response = dpop_retry_client
.post(url)
.headers(additional_headers.clone())
.header(
"Authorization",
&format!("DPoP {}", dpop_auth.oauth_access_token),
)
.header("DPoP", &dpop_proof_token)
.json(&record)
.send()
.await
.map_err(|error| DPoPError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| DPoPError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn post_dpop_bytes_with_headers(
http_client: &reqwest::Client,
dpop_auth: &DPoPAuth,
url: &str,
payload: Bytes,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let (dpop_proof_token, dpop_proof_header, dpop_proof_claim) = request_dpop(
&dpop_auth.dpop_private_key_data,
"POST",
url,
&dpop_auth.oauth_access_token,
)
.map_err(|error| DPoPError::ProofGenerationFailed { error })?;
let dpop_retry = DpopRetry::new(
dpop_proof_header.clone(),
dpop_proof_claim.clone(),
dpop_auth.dpop_private_key_data.clone(),
true,
);
let dpop_retry_client = ClientBuilder::new(http_client.clone())
.with(ChainMiddleware::new(dpop_retry.clone()))
.build();
let http_response = dpop_retry_client
.post(url)
.headers(additional_headers.clone())
.header(
"Authorization",
&format!("DPoP {}", dpop_auth.oauth_access_token),
)
.header("DPoP", &dpop_proof_token)
.body(payload)
.send()
.await
.map_err(|error| DPoPError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| DPoPError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn post_json(
http_client: &reqwest::Client,
url: &str,
data: serde_json::Value,
) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
post_json_with_headers(http_client, url, data, &empty).await
}
pub async fn post_json_with_headers(
http_client: &reqwest::Client,
url: &str,
data: serde_json::Value,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let http_response = http_client
.post(url)
.headers(additional_headers.clone())
.json(&data)
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| ClientError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn get_apppassword_json(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
get_apppassword_json_with_headers(http_client, app_auth, url, &empty).await
}
pub async fn get_apppassword_json_with_headers(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let mut headers = additional_headers.clone();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
);
let http_response = http_client
.get(url)
.headers(headers)
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| ClientError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn post_apppassword_json(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
data: serde_json::Value,
) -> Result<serde_json::Value> {
let empty = HeaderMap::default();
post_apppassword_json_with_headers(http_client, app_auth, url, data, &empty).await
}
pub async fn post_apppassword_json_with_headers(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
data: serde_json::Value,
additional_headers: &HeaderMap,
) -> Result<serde_json::Value> {
let mut headers = additional_headers.clone();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
);
let http_response = http_client
.post(url)
.headers(headers)
.json(&data)
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
let value = http_response
.json::<serde_json::Value>()
.await
.map_err(|error| ClientError::JsonParseFailed {
url: url.to_string(),
error,
})?;
Ok(value)
}
pub async fn get_apppassword_bytes_with_headers(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
additional_headers: &HeaderMap,
) -> Result<Bytes> {
let mut headers = additional_headers.clone();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
);
let http_response = http_client
.get(url)
.headers(headers)
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
Ok(http_response
.bytes()
.await
.map_err(|error| ClientError::ByteStreamFailed {
url: url.to_string(),
error,
})?)
}
pub async fn post_apppassword_bytes_with_headers(
http_client: &reqwest::Client,
app_auth: &AppPasswordAuth,
url: &str,
payload: Bytes,
additional_headers: &HeaderMap,
) -> Result<Bytes> {
let mut headers = additional_headers.clone();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
);
let http_response = http_client
.post(url)
.headers(headers)
.body(payload)
.send()
.instrument(tracing::info_span!("post_apppassword_bytes_with_headers", url = %url))
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.to_string(),
error,
})?;
Ok(http_response
.bytes()
.await
.map_err(|error| ClientError::ByteStreamFailed {
url: url.to_string(),
error,
})?)
}