use super::super::AuthnServiceImpl;
use super::support::*;
use crate::proto::udb::core::authn::entity::v1 as authn_entity_pb;
use crate::proto::udb::core::authn::services::v1 as authn_pb;
use crate::proto::udb::core::authn::services::v1::authn_service_server::AuthnService;
use crate::proto::udb::core::common::v1 as common_pb;
use tonic::Request;
fn ha_principal(user_id: &str, fingerprint: &str) -> authn_pb::CreateSessionRequest {
authn_pb::CreateSessionRequest {
principal: Some(authn_pb::Principal {
principal_id: user_id.to_string(),
subject: user_id.to_string(),
user_id: user_id.to_string(),
tenant_id: "acme".to_string(),
project_id: "billing".to_string(),
scopes: vec!["data:read".to_string()],
auth_method: "session".to_string(),
..Default::default()
}),
ttl_seconds: 300,
client_fingerprint: fingerprint.to_string(),
}
}
async fn validate_session(node: &AuthnServiceImpl, session_id: &str) -> bool {
node.validate_token(Request::new(authn_pb::ValidateTokenRequest {
token: session_id.to_string(),
token_type: authn_entity_pb::TokenType::Session as i32,
}))
.await
.expect("validate session token")
.into_inner()
.valid
}
#[tokio::test]
#[ignore = "requires live Postgres; run with UDB_LIVE_AUTH_TESTS=1 cargo test --lib live_postgres_ha_session_revocation_propagation -- --ignored --nocapture"]
async fn live_postgres_ha_session_revocation_propagation() {
let _guard = live_auth_db_lock().lock().await;
let pool = live_pg_pool().await;
migrate_native_auth_db(&pool).await;
let node1 = authn_service(pool.clone());
let node2 = authn_service(pool.clone());
let user = create_verified_user(&node1, "ha-revoke", "CorrectHorse1!").await;
let session = node1
.create_session(Request::new(ha_principal(&user.user_id, "ha-node1")))
.await
.expect("node1 create session")
.into_inner();
assert!(session.session_id.starts_with("sess_"));
assert!(
validate_session(&node2, &session.session_id).await,
"session created on node1 must validate on node2"
);
let revoked = node1
.revoke_session(Request::new(authn_pb::RevokeSessionRequest {
session_id: session.session_id.clone(),
revoke_reason: "ha_convergence_test".to_string(),
..Default::default()
}))
.await
.expect("node1 revoke session")
.into_inner();
assert_eq!(revoked.revoked_count, 1);
assert!(
!validate_session(&node2, &session.session_id).await,
"revocation on node1 must propagate to node2 within the propagation window (durable read)"
);
cleanup_native_auth_db(&pool).await;
}
#[tokio::test]
#[ignore = "requires live Postgres; run with UDB_LIVE_AUTH_TESTS=1 cargo test --lib live_postgres_ha_logout_all_propagation -- --ignored --nocapture"]
async fn live_postgres_ha_logout_all_propagation() {
let _guard = live_auth_db_lock().lock().await;
let pool = live_pg_pool().await;
migrate_native_auth_db(&pool).await;
let node1 = authn_service(pool.clone());
let node2 = authn_service(pool.clone());
let user = create_verified_user(&node1, "ha-logout", "CorrectHorse1!").await;
let s1 = node1
.create_session(Request::new(ha_principal(&user.user_id, "ha-dev-a")))
.await
.expect("node1 create session 1")
.into_inner();
let s2 = node1
.create_session(Request::new(ha_principal(&user.user_id, "ha-dev-b")))
.await
.expect("node1 create session 2")
.into_inner();
assert!(validate_session(&node2, &s1.session_id).await);
assert!(validate_session(&node2, &s2.session_id).await);
node1
.logout(Request::new(authn_pb::LogoutRequest {
session_id: s1.session_id.clone(),
all_sessions: true,
revoke_reason: "ha_logout_all".to_string(),
context: Some(common_pb::RequestContext {
principal_id: user.user_id.clone(),
..Default::default()
}),
..Default::default()
}))
.await
.expect("node1 logout all sessions");
assert!(
!validate_session(&node2, &s1.session_id).await,
"logout-all on node1 must invalidate session 1 on node2"
);
assert!(
!validate_session(&node2, &s2.session_id).await,
"logout-all on node1 must invalidate session 2 on node2"
);
cleanup_native_auth_db(&pool).await;
}