use crate::error::Error;
use crate::service_account::{ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
use crate::storage::{self, Storage};
use crate::types::AccessToken;
use actix_web::client as awc;
use futures::lock::Mutex;
use std::fmt;
use std::io;
use std::path::PathBuf;
pub struct Authenticator {
pub client: awc::Client,
storage: Storage,
auth_flow: ServiceAccountFlow,
}
impl Authenticator {
pub async fn token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Result<AccessToken, Error>
where
T: AsRef<str>,
{
self.find_token(scopes, false).await
}
pub async fn header<'a, T>(
self: &Self,
scopes: &'a [T],
) -> Result<String, Error>
where
T: AsRef<str>,
{
let tok = self.token(scopes).await?;
Ok(format!("Bearer {}", tok.as_str()))
}
pub async fn force_refreshed_token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Result<AccessToken, Error>
where
T: AsRef<str>,
{
self.find_token(scopes, true).await
}
async fn find_token<'a, T>(
&'a self,
scopes: &'a [T],
force_refresh: bool,
) -> Result<AccessToken, Error>
where
T: AsRef<str>,
{
log::debug!(
"access token requested for scopes: {}",
DisplayScopes(scopes)
);
let hashed_scopes = storage::ScopeSet::from(scopes);
match self.storage.get(hashed_scopes).await {
Some(t) if !t.is_expired() && !force_refresh => {
log::debug!("found valid token in cache: {:?}", t);
Ok(t.into())
}
_ => {
let token_info = self.auth_flow.token(&self.client, scopes).await?;
self.storage.set(hashed_scopes, token_info.clone()).await?;
Ok(token_info.into())
}
}
}
}
struct DisplayScopes<'a, T>(&'a [T]);
impl<'a, T> fmt::Display for DisplayScopes<'a, T>
where
T: AsRef<str>,
{
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
f.write_str("[")?;
let mut iter = self.0.iter();
if let Some(first) = iter.next() {
f.write_str(first.as_ref())?;
for scope in iter {
f.write_str(", ")?;
f.write_str(scope.as_ref())?;
}
}
f.write_str("]")
}
}
pub struct AuthenticatorBuilder {
client_builder: awc::ClientBuilder,
storage_type: StorageType,
auth_flow_opts: ServiceAccountFlowOpts,
}
impl AuthenticatorBuilder {
async fn common_build(
client_builder: awc::ClientBuilder,
storage_type: StorageType,
auth_flow_opts: ServiceAccountFlowOpts,
) -> io::Result<Authenticator> {
let client = client_builder.finish();
let storage = match storage_type {
StorageType::Memory => Storage::Memory {
tokens: Mutex::new(storage::JSONTokens::new()),
},
StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?),
};
let auth_flow = ServiceAccountFlow::new(auth_flow_opts)?;
Ok(Authenticator {
client,
storage,
auth_flow,
})
}
pub fn client(
self,
client_builder: awc::ClientBuilder,
) -> AuthenticatorBuilder {
AuthenticatorBuilder {
client_builder: client_builder,
storage_type: self.storage_type,
auth_flow_opts: self.auth_flow_opts,
}
}
pub fn persist_tokens_to_disk<P: Into<PathBuf>>(
self,
path: P,
) -> AuthenticatorBuilder {
AuthenticatorBuilder {
storage_type: StorageType::Disk(path.into()),
..self
}
}
pub fn with_service_key(
key: ServiceAccountKey,
subject: &str,
) -> AuthenticatorBuilder {
let auth_flow_opts = ServiceAccountFlowOpts {
key,
subject: Some(subject.to_string()),
};
AuthenticatorBuilder {
client_builder: awc::ClientBuilder::new(),
storage_type: StorageType::Memory,
auth_flow_opts,
}
}
}
impl AuthenticatorBuilder {
pub fn subject(
self,
subject: impl Into<String>,
) -> Self {
AuthenticatorBuilder {
auth_flow_opts: ServiceAccountFlowOpts {
subject: Some(subject.into()),
..self.auth_flow_opts
},
..self
}
}
pub async fn build(self) -> io::Result<Authenticator> {
Self::common_build(self.client_builder, self.storage_type, self.auth_flow_opts).await
}
}
enum StorageType {
Memory,
Disk(PathBuf),
}