use crate::authn::service::AuthnService;
use crate::authn::{
error::AuthnError,
event::{AuthEventBuilder, AuthEventType},
store::{FactorStore, IdentityStore},
types::StatusDetail,
};
impl<I, F> AuthnService<I, F>
where
I: IdentityStore,
F: FactorStore<Error = I::Error>,
{
#[tracing::instrument(skip(self, detail))]
pub async fn suspend_user(
&self,
user_id: &crate::authn::ids::UserId,
detail: StatusDetail,
) -> Result<(), AuthnError<I::Error>> {
self.suspend_user_inner(None, None, user_id, detail).await
}
pub async fn suspend_user_by(
&self,
actor: &crate::authn::ids::UserId,
user_id: &crate::authn::ids::UserId,
detail: StatusDetail,
) -> Result<(), AuthnError<I::Error>> {
self.suspend_user_inner(Some(*actor), None, user_id, detail)
.await
}
pub async fn suspend_user_in_tenant(
&self,
user_id: &crate::authn::ids::UserId,
expected_tenant: &crate::authn::ids::TenantId,
detail: StatusDetail,
) -> Result<(), AuthnError<I::Error>> {
self.suspend_user_inner(None, Some(*expected_tenant), user_id, detail)
.await
}
pub async fn suspend_user_by_in_tenant(
&self,
actor: &crate::authn::ids::UserId,
user_id: &crate::authn::ids::UserId,
expected_tenant: &crate::authn::ids::TenantId,
detail: StatusDetail,
) -> Result<(), AuthnError<I::Error>> {
self.suspend_user_inner(Some(*actor), Some(*expected_tenant), user_id, detail)
.await
}
async fn suspend_user_inner(
&self,
actor: Option<crate::authn::ids::UserId>,
expected_tenant: Option<crate::authn::ids::TenantId>,
user_id: &crate::authn::ids::UserId,
detail: StatusDetail,
) -> Result<(), AuthnError<I::Error>> {
let user = self
.verify_admin_target_in_tenant(
user_id,
actor.as_ref(),
expected_tenant.as_ref(),
"suspend",
)
.await?;
self.identity
.suspend_user(user_id, detail)
.await
.map_err(AuthnError::Store)?;
if let Some(reg) = &self.registry {
reg.invalidate_user(user_id).await;
}
let mut builder = AuthEventBuilder::success(AuthEventType::AccountSuspended)
.attributed_to(&user.id, &user.tenant_id);
if let Some(actor_id) = actor {
builder = builder.with_actor(actor_id);
}
self.emit_audit(builder).await;
Ok(())
}
#[tracing::instrument(skip(self))]
pub async fn activate_user(
&self,
user_id: &crate::authn::ids::UserId,
) -> Result<(), AuthnError<I::Error>> {
self.activate_user_inner(None, None, user_id).await
}
pub async fn activate_user_by(
&self,
actor: &crate::authn::ids::UserId,
user_id: &crate::authn::ids::UserId,
) -> Result<(), AuthnError<I::Error>> {
self.activate_user_inner(Some(*actor), None, user_id).await
}
pub async fn activate_user_in_tenant(
&self,
user_id: &crate::authn::ids::UserId,
expected_tenant: &crate::authn::ids::TenantId,
) -> Result<(), AuthnError<I::Error>> {
self.activate_user_inner(None, Some(*expected_tenant), user_id)
.await
}
pub async fn activate_user_by_in_tenant(
&self,
actor: &crate::authn::ids::UserId,
user_id: &crate::authn::ids::UserId,
expected_tenant: &crate::authn::ids::TenantId,
) -> Result<(), AuthnError<I::Error>> {
self.activate_user_inner(Some(*actor), Some(*expected_tenant), user_id)
.await
}
async fn activate_user_inner(
&self,
actor: Option<crate::authn::ids::UserId>,
expected_tenant: Option<crate::authn::ids::TenantId>,
user_id: &crate::authn::ids::UserId,
) -> Result<(), AuthnError<I::Error>> {
let user = self
.verify_admin_target_in_tenant(
user_id,
actor.as_ref(),
expected_tenant.as_ref(),
"activate",
)
.await?;
self.identity
.activate_user(user_id)
.await
.map_err(AuthnError::Store)?;
let mut builder = AuthEventBuilder::success(AuthEventType::AccountActivated)
.attributed_to(&user.id, &user.tenant_id);
if let Some(actor_id) = actor {
builder = builder.with_actor(actor_id);
}
self.emit_audit(builder).await;
Ok(())
}
async fn verify_admin_target_in_tenant(
&self,
user_id: &crate::authn::ids::UserId,
actor: Option<&crate::authn::ids::UserId>,
expected_tenant: Option<&crate::authn::ids::TenantId>,
action_label: &'static str,
) -> Result<crate::authn::types::User, AuthnError<I::Error>> {
let user = self
.identity
.get_user(user_id)
.await
.map_err(AuthnError::Store)?
.ok_or(AuthnError::NoFlow)?;
if let Some(expected) = expected_tenant
&& user.tenant_id != *expected
{
tracing::warn!(
user_id = %user_id,
user_tenant = %user.tenant_id,
expected_tenant = %expected,
actor = ?actor,
action = %action_label,
"cross-tenant admin action refused"
);
return Err(AuthnError::CrossTenant);
}
Ok(user)
}
}