feat: account deletion endpoints

This commit is contained in:
2026-10-26 02:49:07 +01:00
parent 54c8433c78
commit 0beedfbf0a
14 changed files with 420 additions and 8 deletions

View File

@@ -1,3 +1,4 @@
use crate::html::forms::DeleteAccountForm;
use crate::html::helpers::build_page_context;
use crate::html::templates::{
ProfileSettingsTemplate, RemoteActorDisplay, UserSummaryView, UsersTemplate,
@@ -13,11 +14,16 @@ use application::users::{
update_profile_fields,
};
use axum::{
Form,
extract::{Extension, Multipart, Path, Query, State},
http::StatusCode,
response::IntoResponse,
};
use domain::{errors::DomainError, value_objects::Username};
use domain::{
errors::DomainError,
value_objects::{UserId, Username},
};
use uuid::Uuid;
pub async fn get_users_list(
OptionalCookieUser(user_id): OptionalCookieUser,
@@ -76,6 +82,7 @@ pub async fn get_user_by_username(
#[derive(serde::Deserialize, Default)]
pub struct SavedQuery {
pub saved: Option<String>,
pub error: Option<String>,
}
pub async fn get_profile_settings(
@@ -109,6 +116,7 @@ pub async fn get_profile_settings(
also_known_as: settings.also_known_as.as_deref(),
profile_fields: &settings.fields,
saved,
error: params.error.as_deref(),
embed_url: format!(
"{}/users/{}?embed=true",
state.app_ctx.config.base_url,
@@ -177,3 +185,61 @@ pub async fn post_profile_settings(
axum::response::Redirect::to("/settings/profile?saved=1").into_response()
}
async fn delete_account(
state: &AppState,
actor: UserId,
target: UserId,
password: &str,
) -> Result<(), DomainError> {
application::users::request_account_deletion::execute(
&state.app_ctx.deps.users.request_account_deletion,
actor,
target,
password,
)
.await
}
pub async fn post_delete_own_account(
RequiredCookieUser(user_id): RequiredCookieUser,
State(state): State<AppState>,
Extension(csrf): Extension<CsrfToken>,
Form(form): Form<DeleteAccountForm>,
) -> impl IntoResponse {
if crate::html::csrf::mismatch(&csrf, &form.csrf_token) {
return StatusCode::FORBIDDEN.into_response();
}
match delete_account(&state, user_id.clone(), user_id, &form.password).await {
Ok(()) => super::auth::get_logout(State(state)).await.into_response(),
Err(e) => axum::response::Redirect::to(&format!(
"/settings/profile?error={}",
crate::html::helpers::encode_domain_error(&e)
))
.into_response(),
}
}
pub async fn post_delete_user(
RequiredCookieUser(actor): RequiredCookieUser,
State(state): State<AppState>,
Extension(csrf): Extension<CsrfToken>,
Path(target): Path<Uuid>,
Form(form): Form<DeleteAccountForm>,
) -> impl IntoResponse {
if crate::html::csrf::mismatch(&csrf, &form.csrf_token) {
return StatusCode::FORBIDDEN.into_response();
}
let target_id = UserId::from_uuid(target);
let own = actor == target_id;
match delete_account(&state, actor, target_id, &form.password).await {
// An admin removing their own account ends their own session too.
Ok(()) if own => super::auth::get_logout(State(state)).await.into_response(),
Ok(()) => axum::response::Redirect::to("/users").into_response(),
Err(e) => axum::response::Redirect::to(&format!(
"/users/{target}?error={}",
crate::html::helpers::encode_domain_error(&e)
))
.into_response(),
}
}