use crate::authn::service::AuthnService;
use crate::authn::{
event::{AuthEventBuilder, AuthEventType},
factor::FactorKind,
ids::UserId,
store::{FactorStore, IdentityStore},
};
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
#[tracing::instrument(skip(self, refresh_token))]
pub async fn refresh_oauth_token(
&self,
provider_name: &str,
refresh_token: &str,
) -> Result<axess_factors::oauth::OAuthClaims, axess_factors::oauth::OAuthError> {
use axess_factors::oauth::OAuthError;
if refresh_token.is_empty() {
self.record_oauth_refresh_failure("token_refresh_no_token", provider_name)
.await;
return Err(OAuthError::NoRefreshToken);
}
let Some(provider) = self.oauth_providers.get(provider_name) else {
self.record_oauth_refresh_failure("token_refresh_unknown_provider", provider_name)
.await;
return Err(OAuthError::UnknownProvider(provider_name.to_string()));
};
let provider = provider.clone();
let claims = match provider.refresh_token(refresh_token).await {
Ok(c) => c,
Err(e) => {
self.record_oauth_refresh_failure("token_refresh_provider_rejected", provider_name)
.await;
return Err(e);
}
};
let subject_user = UserId::try_new(claims.subject.as_str()).ok();
self.emit_audit(
AuthEventBuilder::success(AuthEventType::LoginAttempt)
.maybe_attributed_to(subject_user.as_ref(), None)
.with_factor(FactorKind::Federated(
crate::authn::factor::FederatedProvider::Custom(provider_name.into()),
))
.with_error("token_refresh"),
)
.await;
Ok(claims)
}
async fn record_oauth_refresh_failure(&self, reason: &str, provider_name: &str) {
self.emit_audit(
AuthEventBuilder::failure(AuthEventType::LoginAttempt)
.with_factor(FactorKind::Federated(
crate::authn::factor::FederatedProvider::Custom(provider_name.into()),
))
.with_error(reason),
)
.await;
}
}