mod account;
mod factor_pipeline;
mod login;
pub mod outcomes;
#[cfg(feature = "device")]
pub mod step_up;
mod verification;
#[cfg(feature = "fido2")]
pub(crate) mod fido2_service;
#[cfg(feature = "ldap")]
mod ldap_service;
#[cfg(feature = "oauth")]
pub(crate) mod oauth_service;
pub mod session_validator;
pub use outcomes::{FactorOutcome, LoginOutcome, PrepareOutcome, SignupOutcome};
pub use session_validator::{NoSessionRegistryError, SessionValidator, require_valid_session};
#[cfg(feature = "device")]
pub use step_up::{StepUpPolicy, StepUpPolicyBuilder, decide_step_up};
use crate::{
authn::store::{FactorStore, IdentityStore},
session::store::{SessionRegistry, SessionRegistryAdapter, SessionRegistryHandle},
};
use axess_clock::{Clock, SystemClock};
use axess_rng::{SecureRng, SystemRng};
use std::sync::Arc;
pub struct AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore,
{
pub(crate) identity: Arc<I>,
pub(crate) factors: Arc<F>,
pub(crate) registry: Option<Arc<dyn SessionRegistryHandle>>,
pub(crate) metrics: Arc<dyn crate::metrics::AuthnMetrics>,
pub(crate) max_sessions_per_user: Option<usize>,
pub(crate) rng: Arc<dyn SecureRng>,
pub(crate) clock: Arc<dyn Clock>,
#[cfg(feature = "fido2")]
pub(crate) fido2: Option<Arc<dyn axess_factors::fido2::Fido2Provider>>,
#[cfg(feature = "fido2")]
pub(crate) fido2_options: crate::authn::factor::Fido2Options,
#[cfg(feature = "ldap")]
pub(crate) ldap: Option<Arc<dyn axess_factors::ldap::LdapProvider>>,
#[cfg(feature = "oauth")]
pub(crate) oauth_providers: crate::federation::oauth::OAuthProviderRegistry,
#[cfg(feature = "oauth")]
pub(crate) sid_map: crate::federation::backchannel_logout::SidMap,
}
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
pub fn new(identity: I, factors: F) -> Self {
Self {
identity: Arc::new(identity),
factors: Arc::new(factors),
registry: None,
metrics: Arc::new(crate::metrics::NoopMetrics),
max_sessions_per_user: None,
rng: Arc::new(SystemRng),
clock: Arc::new(SystemClock),
#[cfg(feature = "fido2")]
fido2: None,
#[cfg(feature = "fido2")]
fido2_options: Default::default(),
#[cfg(feature = "ldap")]
ldap: None,
#[cfg(feature = "oauth")]
oauth_providers: Default::default(),
#[cfg(feature = "oauth")]
sid_map: Default::default(),
}
}
}
impl<B> AuthnService<B, B>
where
B: crate::authn::store::AuthnBackend + Clone,
{
pub fn from_backend(backend: B) -> Self {
Self::new(backend.clone(), backend)
}
}
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
pub(crate) async fn emit_audit(&self, builder: crate::authn::event::AuthEventBuilder) {
self.emit_audit_at(builder, self.clock.now()).await;
}
pub(crate) async fn emit_audit_at(
&self,
builder: crate::authn::event::AuthEventBuilder,
event_time: chrono::DateTime<chrono::Utc>,
) {
let event = builder.build_at(event_time);
let event_type = format!("{:?}", event.event_type);
let event_status = format!("{:?}", event.event_status);
if let Err(e) = self.identity.record_event(event).await {
tracing::error!(
error = %e,
event_type = %event_type,
event_status = %event_status,
"identity store rejected audit event; SOC visibility lost for this attempt"
);
}
}
pub fn with_rng(mut self, rng: impl SecureRng) -> Self {
self.rng = Arc::new(rng);
self
}
pub fn with_clock(mut self, clock: impl Clock) -> Self {
self.clock = Arc::new(clock);
self
}
#[cfg(feature = "fido2")]
pub fn with_fido2(mut self, provider: impl axess_factors::fido2::Fido2Provider) -> Self {
self.fido2 = Some(Arc::new(provider));
self
}
#[cfg(feature = "fido2")]
pub fn with_fido2_options(mut self, options: crate::authn::factor::Fido2Options) -> Self {
self.fido2_options = options;
self
}
#[cfg(feature = "ldap")]
pub fn with_ldap(mut self, provider: impl axess_factors::ldap::LdapProvider) -> Self {
self.ldap = Some(Arc::new(provider));
self
}
#[cfg(feature = "oauth")]
pub fn with_oauth_provider(
mut self,
provider: impl axess_factors::oauth::OAuthProvider,
) -> Self {
self.oauth_providers.add(provider);
self
}
#[cfg(feature = "oauth")]
pub fn oauth_providers(&self) -> &crate::federation::oauth::OAuthProviderRegistry {
&self.oauth_providers
}
pub fn with_registry(mut self, registry: impl SessionRegistry + 'static) -> Self {
self.registry = Some(Arc::new(SessionRegistryAdapter(registry)));
self
}
pub fn has_session_registry(&self) -> bool {
self.registry.is_some()
}
pub async fn invalidate_user_sessions(
&self,
user_id: &crate::authn::ids::UserId,
) -> Result<(), NoSessionRegistryError> {
match &self.registry {
Some(reg) => {
reg.invalidate_user(user_id).await;
Ok(())
}
None => Err(NoSessionRegistryError),
}
}
pub async fn invalidate_session(
&self,
user_id: &crate::authn::ids::UserId,
session_id: &crate::session::id::SessionId,
) -> Result<(), NoSessionRegistryError> {
match &self.registry {
Some(reg) => {
reg.invalidate_session(user_id, session_id).await;
Ok(())
}
None => Err(NoSessionRegistryError),
}
}
pub async fn active_sessions(
&self,
user_id: &crate::authn::ids::UserId,
) -> Result<Vec<crate::session::id::SessionId>, NoSessionRegistryError> {
match &self.registry {
Some(reg) => Ok(reg.active_sessions(user_id).await),
None => Err(NoSessionRegistryError),
}
}
pub fn with_max_sessions_per_user(mut self, max: usize) -> Self {
self.max_sessions_per_user = Some(max);
self
}
pub fn with_metrics(mut self, metrics: impl crate::metrics::AuthnMetrics) -> Self {
self.metrics = Arc::new(metrics);
self
}
pub fn session_validator(&self) -> SessionValidator {
SessionValidator {
registry: self.registry.clone(),
identity: None,
}
}
pub fn session_validator_with_identity_check(&self) -> SessionValidator {
SessionValidator {
registry: self.registry.clone(),
identity: Some(Arc::new(session_validator::IdentityWrapper(
self.identity.clone(),
))),
}
}
}