80f9e01
use std::time::Duration;
use export::AppState;
use models::users::user_access_keys;
use models::users::user_deletion_requests::{self, UserDeletionRequestStatus};
use models::users::user_emails;
use models::users::user_recovery_codes;
use models::users::user_sessions;
use models::users::user_ssh_keys;
use models::users::{user, user_profiles};
use sea_orm::sea_query::Expr;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
use time::OffsetDateTime;
use crate::services::AuthService;
const CHECK_INTERVAL_SECS: u64 = 3600;
impl AuthService {
#[tracing::instrument(name = "auth.execute_due_deletions", skip_all)]
pub async fn execute_due_deletions(&self) -> Result<u64, crate::errors::AuthError> {
let db = self.db();
let now = OffsetDateTime::now_utc();
let due = user_deletion_requests::Entity::find()
.filter(user_deletion_requests::Column::Status.eq(UserDeletionRequestStatus::Scheduled))
.filter(user_deletion_requests::Column::ScheduledFor.lte(now))
.all(db.read())
.await?;
let mut executed = 0u64;
for request in due {
let uid = request.user_id;
let Some(user_row) = user::Entity::find_by_id(uid).one(db.write()).await? else {
let mut am: user_deletion_requests::ActiveModel = request.into();
am.status = Set(UserDeletionRequestStatus::Completed);
am.completed_at = Set(Some(now));
am.update(db.write()).await?;
executed += 1;
continue;
};
let anon_name = format!("deleted-{}", &uid.simple().to_string()[..8]);
let mut active = user_row.into_active_model();
active.username = Set(anon_name.clone());
active.lower_name = Set(anon_name.clone());
active.display_name = Set("deleted user".to_string());
active.avatar_url = Set(String::new());
active.is_active = Set(false);
active.prohibit_login = Set(true);
active.passwd_hash = Set(None);
active.password_last_changed_at = Set(None);
active.passwd_version = Set(Some(OffsetDateTime::now_utc().unix_timestamp()));
active.totp_encrypted_secret = Set(None);
active.totp_enabled = Set(false);
active.timezone = Set(None);
active.locale = Set(None);
active.language = Set(None);
active.updated_at = Set(now);
active.update(db.write()).await?;
if let Some(profile) = user_profiles::Entity::find_by_id(uid)
.one(db.write())
.await?
{
let mut am: user_profiles::ActiveModel = profile.into();
am.real_name = Set(None);
am.bio = Set(None);
am.pronouns = Set(None);
am.website = Set(None);
am.location = Set(None);
am.company = Set(None);
am.date_of_birth = Set(None);
am.banner_url = Set(None);
am.banner_hash = Set(None);
am.banner_color = Set(None);
am.accent_color = Set(None);
am.custom_status_emoji = Set(None);
am.custom_status_text = Set(None);
am.custom_status_expires_at = Set(None);
am.updated_at = Set(now);
am.update(db.write()).await?;
}
user_emails::Entity::update_many()
.col_expr(
user_emails::Column::Email,
Expr::value(format!("{anon_name}@deleted.invalid")),
)
.col_expr(
user_emails::Column::LowerEmail,
Expr::value(format!("{anon_name}@deleted.invalid")),
)
.col_expr(user_emails::Column::DeletedAt, Expr::value(Some(now)))
.col_expr(user_emails::Column::UpdatedAt, Expr::value(now))
.filter(user_emails::Column::UserId.eq(uid))
.exec(db.write())
.await?;
user_sessions::Entity::update_many()
.col_expr(user_sessions::Column::IsRevoked, Expr::value(true))
.col_expr(user_sessions::Column::RevokedAt, Expr::value(Some(now)))
.col_expr(user_sessions::Column::UpdatedAt, Expr::value(now))
.filter(user_sessions::Column::UserId.eq(uid))
.filter(user_sessions::Column::IsRevoked.eq(false))
.exec(db.write())
.await?;
user_access_keys::Entity::delete_many()
.filter(user_access_keys::Column::UserId.eq(uid))
.exec(db.write())
.await?;
user_ssh_keys::Entity::delete_many()
.filter(user_ssh_keys::Column::UserId.eq(uid))
.exec(db.write())
.await?;
user_recovery_codes::Entity::delete_many()
.filter(user_recovery_codes::Column::UserId.eq(uid))
.exec(db.write())
.await?;
let mut am: user_deletion_requests::ActiveModel = request.into();
am.status = Set(UserDeletionRequestStatus::Completed);
am.completed_at = Set(Some(now));
am.update(db.write()).await?;
executed += 1;
}
Ok(executed)
}
}
pub async fn run_deletion_executor(state: AppState) {
let service = AuthService::new(state);
let mut interval = tokio::time::interval(Duration::from_secs(CHECK_INTERVAL_SECS));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
interval.tick().await;
match service.execute_due_deletions().await {
Ok(0) => {}
Ok(n) => tracing::info!(count = n, "executed due account deletions"),
Err(error) => tracing::warn!(%error, "account deletion executor failed"),
}
}
}