#![cfg(test)]
use super::*;
use crate::authn::types::{EntityState, Tenant, User};
use crate::session::data::SessionData;
use crate::session::extractor::AuthSession;
use crate::session::layer::{SessionHandle, SessionInner};
use crate::session::store::{
MemorySessionRegistry, SessionRegistry, SessionRegistryAdapter, SessionRegistryHandle,
};
use crate::testing::mock_authn::MockIdentityStore;
use tokio::sync::RwLock;
fn fixture_user_id() -> crate::authn::ids::UserId {
axess_identity::testing::user("u-validator")
}
fn fixture_tenant_id() -> crate::authn::ids::TenantId {
axess_identity::testing::tenant("t-validator")
}
fn make_session() -> AuthSession {
let inner = SessionInner {
id: crate::session::id::SessionId::new(&axess_rng::SystemRng),
data: SessionData::default(),
modified: false,
regenerate: false,
pre_cycle_id: None,
pending_fingerprint: None,
max_custom_bytes: 64 * 1024,
};
AuthSession(SessionHandle(Arc::new(RwLock::new(inner))))
}
async fn authenticated_session() -> AuthSession {
let session = make_session();
session
.set_authenticated(fixture_user_id(), fixture_tenant_id(), chrono::Utc::now())
.await;
session
}
fn registry_handle(registry: MemorySessionRegistry) -> Arc<dyn SessionRegistryHandle> {
Arc::new(SessionRegistryAdapter(registry))
}
fn identity_handle(store: MockIdentityStore) -> Arc<dyn IdentityHandle> {
Arc::new(IdentityWrapper(Arc::new(store)))
}
fn build_user(
user_id: &crate::authn::ids::UserId,
tenant_id: &crate::authn::ids::TenantId,
) -> User {
let now = chrono::Utc::now();
User {
id: *user_id,
tenant_id: *tenant_id,
identifier: "validator-user".into(),
display_name: "validator-user".into(),
status: EntityState::Active,
webauthn_id: None,
created_by: crate::authn::ids::UserId::system(),
created_at: now,
updated_by: crate::authn::ids::UserId::system(),
updated_at: now,
}
}
fn build_tenant(id: crate::authn::ids::TenantId, identifier: &str) -> Tenant {
let now = chrono::Utc::now();
Tenant {
id,
identifier: identifier.into(),
display_name: identifier.into(),
status: EntityState::Active,
created_by: crate::authn::ids::UserId::system(),
created_at: now,
updated_by: crate::authn::ids::UserId::system(),
updated_at: now,
}
}
#[tokio::test]
async fn unauthenticated_session_returns_false() {
let validator = SessionValidator {
registry: None,
identity: None,
};
let session = make_session();
assert!(
!validator.is_valid(&session).await,
"unauthenticated session must be invalid"
);
}
#[tokio::test]
async fn authenticated_session_no_registry_no_identity_returns_true() {
let validator = SessionValidator {
registry: None,
identity: None,
};
let session = authenticated_session().await;
assert!(
validator.is_valid(&session).await,
"authenticated session with no extra gates must be valid"
);
}
#[tokio::test]
async fn registry_rejection_returns_false() {
let registry = MemorySessionRegistry::new();
let validator = SessionValidator {
registry: Some(registry_handle(registry)),
identity: None,
};
let session = authenticated_session().await;
assert!(
!validator.is_valid(&session).await,
"registry rejection must invalidate the session"
);
}
#[tokio::test]
async fn registry_acceptance_returns_true() {
let registry = MemorySessionRegistry::new();
let session = authenticated_session().await;
let sid = session.session_id().await;
registry.register(&fixture_user_id(), &sid).await.unwrap();
let validator = SessionValidator {
registry: Some(registry_handle(registry)),
identity: None,
};
assert!(
validator.is_valid(&session).await,
"registered session must be valid"
);
}
#[tokio::test]
async fn identity_check_tenant_matches_returns_true() {
let user_id = fixture_user_id();
let tenant_id = fixture_tenant_id();
let identity = MockIdentityStore::new()
.with_tenant(build_tenant(tenant_id, "validator-tenant"))
.with_user(build_user(&user_id, &tenant_id));
let validator = SessionValidator {
registry: None,
identity: Some(identity_handle(identity)),
};
let session = authenticated_session().await;
assert!(
validator.is_valid(&session).await,
"tenant cross-check must pass when user.tenant_id == session.tenant_id"
);
}
#[tokio::test]
async fn identity_check_tenant_mismatch_returns_false() {
let user_id = fixture_user_id();
let session_tenant = fixture_tenant_id();
let other_tenant = axess_identity::testing::tenant("t-other");
let identity = MockIdentityStore::new()
.with_tenant(build_tenant(other_tenant, "other-tenant"))
.with_user(build_user(&user_id, &other_tenant));
let validator = SessionValidator {
registry: None,
identity: Some(identity_handle(identity)),
};
let session = make_session();
session
.set_authenticated(user_id, session_tenant, chrono::Utc::now())
.await;
assert!(
!validator.is_valid(&session).await,
"tenant-mismatch must invalidate the session"
);
}
#[tokio::test]
async fn identity_check_user_not_found_returns_false() {
let identity = MockIdentityStore::new();
let validator = SessionValidator {
registry: None,
identity: Some(identity_handle(identity)),
};
let session = authenticated_session().await;
assert!(
!validator.is_valid(&session).await,
"unknown user must invalidate the session"
);
}
#[cfg(feature = "oauth")]
#[test]
fn oauth_providers_accessor_returns_configured_registry() {
use crate::authn::service::AuthnService;
use crate::testing::mock_authn::MockFactorStore;
use axess_factors::oauth::MockOAuthProvider;
let service = AuthnService::new(MockIdentityStore::new(), MockFactorStore::new())
.with_oauth_provider(MockOAuthProvider::new("ax-028-idp"));
let registry = service.oauth_providers();
assert_eq!(
registry.provider_count(),
1,
"oauth_providers() must return the live registry; \
a leaked-default would report 0"
);
let names = registry.provider_names();
assert!(
names.iter().any(|n| n.as_ref() == "ax-028-idp"),
"registry must contain the registered provider; names={:?}",
names
);
}