use crate::authn::service::AuthnService;
use crate::authn::{
error::AuthnError,
event::{AuthEventBuilder, AuthEventType},
store::{FactorStore, IdentityStore},
};
use crate::session::extractor::AuthSession;
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
#[tracing::instrument(skip(self, target_user, session))]
pub async fn begin_impersonation(
&self,
admin_user_id: &crate::authn::ids::UserId,
target_user: &crate::authn::types::User,
session: &AuthSession,
) -> Result<(), AuthnError<I::Error>> {
let now = self.clock.now();
session.clear_custom().await;
session
.set_authenticated(target_user.id, target_user.tenant_id, now)
.await;
session.regenerate().await;
let sid = session.session_id().await;
if let Some(reg) = &self.registry
&& !reg.register(&target_user.id, &sid).await
{
tracing::error!(
admin_user_id = %admin_user_id,
target_user_id = %target_user.id,
"begin_impersonation register failed; clearing session and refusing"
);
session.clear().await;
return Err(AuthnError::NoFlow);
}
self.emit_audit_at(
AuthEventBuilder::success(AuthEventType::Impersonation)
.attributed_to(&target_user.id, &target_user.tenant_id)
.with_session(sid)
.with_actor(*admin_user_id),
now,
)
.await;
tracing::warn!(
admin = %admin_user_id,
target = %target_user.id,
"admin impersonation: session assumed target identity"
);
Ok(())
}
pub async fn begin_impersonation_in_tenant(
&self,
admin_user: &crate::authn::types::User,
target_user: &crate::authn::types::User,
session: &AuthSession,
) -> Result<(), AuthnError<I::Error>> {
if admin_user.tenant_id != target_user.tenant_id {
tracing::warn!(
admin = %admin_user.id,
admin_tenant = %admin_user.tenant_id,
target = %target_user.id,
target_tenant = %target_user.tenant_id,
"cross-tenant impersonation refused"
);
self.emit_audit(
AuthEventBuilder::failure(AuthEventType::Impersonation)
.attributed_to(&target_user.id, &target_user.tenant_id)
.with_actor(admin_user.id)
.with_error("cross-tenant impersonation refused"),
)
.await;
return Err(AuthnError::CrossTenant);
}
self.begin_impersonation(&admin_user.id, target_user, session)
.await
}
}