use futures::future::BoxFuture;
use crate::error::Error;
pub trait TokenProvider: Send + Sync {
fn token(&self) -> BoxFuture<'_, Result<String, Error>>;
}
pub struct StaticTokenProvider {
token: String,
}
impl std::fmt::Debug for StaticTokenProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StaticTokenProvider")
.field("token", &"[redacted]")
.finish()
}
}
impl StaticTokenProvider {
pub fn new(token: impl Into<String>) -> Self {
let token = token.into();
let raw = if token
.get(..7)
.is_some_and(|s| s.eq_ignore_ascii_case("bearer "))
{
&token[7..]
} else {
&token
};
Self {
token: if raw.is_empty() {
raw.to_string()
} else {
format!("Bearer {raw}")
},
}
}
}
impl TokenProvider for Box<dyn TokenProvider + Send + Sync> {
fn token(&self) -> BoxFuture<'_, Result<String, Error>> {
(**self).token()
}
}
impl TokenProvider for StaticTokenProvider {
fn token(&self) -> BoxFuture<'_, Result<String, Error>> {
let token = self.token.clone();
Box::pin(async move { Ok(token) })
}
}
pub struct BasicAuthProvider {
header: String,
}
impl BasicAuthProvider {
pub fn new(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
use base64::Engine as _;
let raw = format!("{}:{}", username.as_ref(), password.as_ref());
let encoded = base64::engine::general_purpose::STANDARD.encode(raw);
Self {
header: format!("Basic {encoded}"),
}
}
}
impl std::fmt::Debug for BasicAuthProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BasicAuthProvider")
.field("header", &"[redacted]")
.finish()
}
}
impl TokenProvider for BasicAuthProvider {
fn token(&self) -> BoxFuture<'_, Result<String, Error>> {
let header = self.header.clone();
Box::pin(async move { Ok(header) })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn static_provider_debug_redacts_token() {
let p = StaticTokenProvider::new("super-secret");
assert_eq!(
format!("{p:?}"),
"StaticTokenProvider { token: \"[redacted]\" }"
);
}
#[tokio::test]
async fn static_provider_returns_bearer_header() {
let provider = StaticTokenProvider::new("tok");
assert_eq!(provider.token().await.unwrap(), "Bearer tok");
}
#[tokio::test]
async fn static_provider_strips_bearer_prefix_case_insensitive() {
for prefix in &["Bearer ", "bearer ", "BEARER "] {
let provider = StaticTokenProvider::new(format!("{prefix}tok"));
assert_eq!(
provider.token().await.unwrap(),
"Bearer tok",
"failed for prefix {prefix:?}"
);
}
}
#[tokio::test]
async fn static_provider_empty_token_returns_empty() {
let provider = StaticTokenProvider::new("");
assert_eq!(provider.token().await.unwrap(), "");
}
#[tokio::test]
async fn basic_provider_returns_basic_header() {
let provider = BasicAuthProvider::new("admin", "admin");
let result = provider.token().await.unwrap();
assert_eq!(result, "Basic YWRtaW46YWRtaW4=");
}
}