61 lines
1.8 KiB
Rust
61 lines
1.8 KiB
Rust
use domain::{errors::DomainError, models::UserRole, value_objects::UserId};
|
|
|
|
use crate::users::deps::RequestAccountDeletionDeps;
|
|
|
|
/// Tombstones `target`. A user may delete themselves; an admin may delete
|
|
/// anyone. Either way the *actor's* password is the proof, and the last
|
|
/// active admin can never be removed. `UserDeleted` rides the repository
|
|
/// write, so the tombstone and its event commit together.
|
|
pub async fn execute(
|
|
deps: &RequestAccountDeletionDeps,
|
|
actor: UserId,
|
|
target: UserId,
|
|
password: &str,
|
|
) -> Result<(), DomainError> {
|
|
let actor_user = deps
|
|
.user
|
|
.find_by_id(&actor)
|
|
.await?
|
|
.ok_or_else(|| DomainError::Unauthorized("account is not active".into()))?;
|
|
|
|
let is_self = actor == target;
|
|
if !is_self && !matches!(actor_user.role(), UserRole::Admin) {
|
|
return Err(DomainError::Forbidden(
|
|
"only an admin can delete another account".into(),
|
|
));
|
|
}
|
|
|
|
if !deps
|
|
.password_hasher
|
|
.verify(password, actor_user.password_hash())
|
|
.await?
|
|
{
|
|
return Err(DomainError::InvalidCredentials("incorrect password".into()));
|
|
}
|
|
|
|
let target_user = if is_self {
|
|
actor_user
|
|
} else {
|
|
deps.user
|
|
.find_by_id(&target)
|
|
.await?
|
|
.ok_or_else(|| DomainError::NotFound("User not found".into()))?
|
|
};
|
|
|
|
if matches!(target_user.role(), UserRole::Admin) && deps.user.count_active_admins().await? <= 1
|
|
{
|
|
return Err(DomainError::Conflict(
|
|
"the last active admin cannot be deleted".into(),
|
|
));
|
|
}
|
|
|
|
deps.user
|
|
.mark_deleted(
|
|
&target,
|
|
&[domain::events::DomainEvent::UserDeleted {
|
|
user_id: target.clone(),
|
|
}],
|
|
)
|
|
.await
|
|
}
|