use crate::authn::service::AuthnService;
use crate::authn::{
event::{AuthEventBuilder, AuthEventType},
factor::FactorKind,
store::{FactorStore, IdentityStore},
};
use crate::session::extractor::AuthSession;
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
pub(super) async fn is_oauth_expired(
&self,
session: &AuthSession,
provider_name: &str,
) -> bool {
use axess_factors::oauth::types::keys as oauth_keys;
const RFC_6749_AUTH_CODE_MAX: std::time::Duration = std::time::Duration::from_secs(600);
let Some(started) = session.get_custom(oauth_keys::STARTED).await else {
return true;
};
let Some(ts) = started.as_str() else {
return true;
};
let Ok(started_at) = chrono::DateTime::parse_from_rfc3339(ts) else {
return true;
};
let configured = self
.oauth_providers
.get(provider_name)
.map(|p| p.ceremony_timeout())
.unwrap_or(RFC_6749_AUTH_CODE_MAX);
let timeout = configured.min(RFC_6749_AUTH_CODE_MAX);
if configured > RFC_6749_AUTH_CODE_MAX {
tracing::warn!(
provider = %provider_name,
configured_secs = configured.as_secs(),
cap_secs = RFC_6749_AUTH_CODE_MAX.as_secs(),
"provider ceremony_timeout exceeds RFC 6749 §4.1.2 RECOMMENDED 600s; capped"
);
}
let elapsed = self.clock.now() - started_at.with_timezone(&chrono::Utc);
elapsed.to_std().unwrap_or_default() > timeout
}
pub(super) async fn record_oauth_failure_and_clear(
&self,
reason: &str,
provider_name: &str,
session: &AuthSession,
) {
self.record_oauth_failure(reason, provider_name, session)
.await;
self.clear_oauth_state(session).await;
}
pub(super) async fn record_oauth_failure(
&self,
reason: &str,
provider_name: &str,
session: &AuthSession,
) {
let user_id = session.user_id().await;
let tenant_id = session.tenant_id().await;
self.emit_audit(
AuthEventBuilder::failure(AuthEventType::LoginAttempt)
.maybe_attributed_to(user_id.as_ref(), tenant_id.as_ref())
.with_factor(FactorKind::Federated(
crate::authn::factor::FederatedProvider::Custom(provider_name.into()),
))
.with_error(reason),
)
.await;
}
pub(super) async fn clear_oauth_state(&self, session: &AuthSession) {
use axess_factors::oauth::types::keys as oauth_keys;
const KEYS: &[&str] = &[
oauth_keys::PKCE_VERIFIER,
oauth_keys::CSRF_STATE,
oauth_keys::NONCE,
oauth_keys::PROVIDER,
oauth_keys::PROVIDER_ISSUER,
oauth_keys::STARTED,
oauth_keys::EXPECTED_TENANT,
oauth_keys::PAR_INFLIGHT,
];
session
.mutate_custom(|obj| {
for key in KEYS {
obj.remove(*key);
}
})
.await;
}
}