use std::time::{Duration, SystemTime};
use lexe_api_core::error::{BackendApiError, BackendErrorKind};
use lexe_common::api::auth::{
BearerAuthRequest, BearerAuthRequestWire, BearerAuthToken, Scope,
TokenWithExpiration,
};
use lexe_crypto::ed25519;
use crate::def::BearerAuthBackendApi;
pub const DEFAULT_USER_TOKEN_LIFETIME_SECS: u32 = 10 * 60; const EXPIRATION_BUFFER: Duration = Duration::from_secs(30);
#[allow(private_interfaces, clippy::large_enum_variant)]
pub enum BearerAuthenticator {
Ephemeral { inner: EphemeralBearerAuthenticator },
Static { inner: StaticBearerAuthenticator },
}
struct EphemeralBearerAuthenticator {
user_key_pair: ed25519::KeyPair,
auth_token_lock: tokio::sync::Mutex<Option<TokenWithExpiration>>,
scope: Option<Scope>,
}
struct StaticBearerAuthenticator {
token: BearerAuthToken,
}
impl BearerAuthenticator {
pub fn new(
user_key_pair: ed25519::KeyPair,
maybe_token: Option<TokenWithExpiration>,
) -> Self {
let scope = None;
Self::new_with_scope(user_key_pair, maybe_token, scope)
}
pub fn new_with_scope(
user_key_pair: ed25519::KeyPair,
maybe_token: Option<TokenWithExpiration>,
scope: Option<Scope>,
) -> Self {
Self::Ephemeral {
inner: EphemeralBearerAuthenticator {
user_key_pair,
auth_token_lock: tokio::sync::Mutex::new(maybe_token),
scope,
},
}
}
pub fn new_static_token(token: BearerAuthToken) -> Self {
Self::Static {
inner: StaticBearerAuthenticator { token },
}
}
pub fn user_key_pair(&self) -> Option<&ed25519::KeyPair> {
match self {
Self::Ephemeral { inner } => Some(&inner.user_key_pair),
Self::Static { .. } => None,
}
}
pub async fn get_token<T: BearerAuthBackendApi + ?Sized>(
&self,
api: &T,
now: SystemTime,
) -> Result<BearerAuthToken, BackendApiError> {
self.get_token_with_exp(api, now)
.await
.map(|token_with_exp| token_with_exp.token)
}
pub async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
&self,
api: &T,
now: SystemTime,
) -> Result<TokenWithExpiration, BackendApiError> {
match self {
Self::Ephemeral { inner } =>
inner.get_token_with_exp(api, now).await,
Self::Static { inner } => inner.get_token_with_exp(api, now).await,
}
}
}
impl EphemeralBearerAuthenticator {
async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
&self,
api: &T,
now: SystemTime,
) -> Result<TokenWithExpiration, BackendApiError> {
let mut locked_token = self.auth_token_lock.lock().await;
if let Some(token) = locked_token.as_ref()
&& !token_needs_refresh(now, token.expiration)
{
return Ok(token.clone());
}
let token_with_exp = do_bearer_auth(
api,
now,
&self.user_key_pair,
DEFAULT_USER_TOKEN_LIFETIME_SECS,
self.scope.clone(),
)
.await?;
let token_clone = token_with_exp.clone();
*locked_token = Some(token_with_exp);
Ok(token_clone)
}
}
impl StaticBearerAuthenticator {
async fn get_token_with_exp<T: BearerAuthBackendApi + ?Sized>(
&self,
_api: &T,
_now: SystemTime,
) -> Result<TokenWithExpiration, BackendApiError> {
Ok(TokenWithExpiration {
expiration: None,
token: self.token.clone(),
})
}
}
pub async fn do_bearer_auth<T: BearerAuthBackendApi + ?Sized>(
api: &T,
now: SystemTime,
user_key_pair: &ed25519::KeyPair,
lifetime_secs: u32,
scope: Option<Scope>,
) -> Result<TokenWithExpiration, BackendApiError> {
let expiration = now + Duration::from_secs(lifetime_secs as u64);
let auth_req = BearerAuthRequest::new(now, lifetime_secs, scope);
let auth_req_wire = BearerAuthRequestWire::from(auth_req);
let (_, signed_req) =
user_key_pair.sign_struct(&auth_req_wire).map_err(|err| {
BackendApiError {
kind: BackendErrorKind::Building,
msg: format!("Error signing auth request: {err:#}"),
..Default::default()
}
})?;
let resp = api.bearer_auth(&signed_req).await?;
Ok(TokenWithExpiration {
expiration: Some(expiration),
token: resp.bearer_auth_token,
})
}
#[inline]
pub fn token_needs_refresh(
now: SystemTime,
expiration: Option<SystemTime>,
) -> bool {
match expiration {
Some(expiration) => now + EXPIRATION_BUFFER >= expiration,
None => false,
}
}