Make remove content optional during account deletion (fixes #1617) (#3817)

* Make remove content optional during account deletion (fixes #1617)

* simplify purge params by passing context

* update js client

* add delete content

* update woodpecker config

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
Nutomic 2023-08-28 12:23:45 +02:00 committed by GitHub
parent 26d125cc63
commit 7fd14b3d2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 71 additions and 119 deletions

View file

@ -10,9 +10,9 @@ variables:
- "**/Cargo.toml"
- "Cargo.lock"
# database migrations
- "migrations"
- "migrations/**"
# typescript tests
- "api_tests"
- "api_tests/**"
# config files and scripts used by ci
- ".woodpecker.yml"
- ".rustfmt.toml"

View file

@ -19,7 +19,7 @@
"eslint": "^8.40.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^29.5.0",
"lemmy-js-client": "0.18.3-rc.3",
"lemmy-js-client": "0.19.0-rc.2",
"prettier": "^3.0.0",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"

View file

@ -707,6 +707,7 @@ export async function getPersonDetails(
export async function deleteUser(api: API): Promise<DeleteAccountResponse> {
let form: DeleteAccount = {
auth: api.auth,
delete_content: true,
password,
};
return api.client.deleteAccount(form);

View file

@ -2174,10 +2174,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lemmy-js-client@0.18.3-rc.3:
version "0.18.3-rc.3"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.3-rc.3.tgz#fc6489eb141bd09558bca38d9e46b40771a29f37"
integrity sha512-njixgXk4uMU4gGifnljwhSe9Kf445C4wAXcXhtpTtwPPLXpHQgxA1RASMb9Uq4zblfE6nC2JbrAka8y8N2N/Bw==
lemmy-js-client@0.19.0-rc.2:
version "0.19.0-rc.2"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.2.tgz#c3cb511b27f92538909a2b91a0f8527b1abad958"
integrity sha512-FXuf8s7bpBVkHL/OGWDb/0aGIrJ7uv3d4Xt1h6zmNDhw6MmmuD8RXgCHiS2jqhxjAEp96Dpl1NFXbpmKpix7tQ==
dependencies:
cross-fetch "^3.1.5"
form-data "^4.0.0"

View file

@ -47,13 +47,7 @@ pub async fn ban_from_site(
// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
if remove_data {
remove_user_data(
person.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
remove_user_data(person.id, &context).await?;
}
// Mod tables

View file

@ -33,24 +33,14 @@ impl Perform for PurgeCommunity {
let community = Community::read(&mut context.pool(), community_id).await?;
if let Some(banner) = community.banner {
purge_image_from_pictrs(context.client(), context.settings(), &banner)
.await
.ok();
purge_image_from_pictrs(&banner, context).await.ok();
}
if let Some(icon) = community.icon {
purge_image_from_pictrs(context.client(), context.settings(), &icon)
.await
.ok();
purge_image_from_pictrs(&icon, context).await.ok();
}
purge_image_posts_for_community(
community_id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
purge_image_posts_for_community(community_id, context).await?;
Community::delete(&mut context.pool(), community_id).await?;

View file

@ -32,24 +32,14 @@ impl Perform for PurgePerson {
let person = Person::read(&mut context.pool(), person_id).await?;
if let Some(banner) = person.banner {
purge_image_from_pictrs(context.client(), context.settings(), &banner)
.await
.ok();
purge_image_from_pictrs(&banner, context).await.ok();
}
if let Some(avatar) = person.avatar {
purge_image_from_pictrs(context.client(), context.settings(), &avatar)
.await
.ok();
purge_image_from_pictrs(&avatar, context).await.ok();
}
purge_image_posts_for_person(
person_id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
purge_image_posts_for_person(person_id, context).await?;
Person::delete(&mut context.pool(), person_id).await?;

View file

@ -34,15 +34,11 @@ impl Perform for PurgePost {
// Purge image
if let Some(url) = post.url {
purge_image_from_pictrs(context.client(), context.settings(), &url)
.await
.ok();
purge_image_from_pictrs(&url, context).await.ok();
}
// Purge thumbnail
if let Some(thumbnail_url) = post.thumbnail_url {
purge_image_from_pictrs(context.client(), context.settings(), &thumbnail_url)
.await
.ok();
purge_image_from_pictrs(&thumbnail_url, context).await.ok();
}
let community_id = post.community_id;

View file

@ -365,6 +365,7 @@ pub struct CommentReplyResponse {
/// Delete your account.
pub struct DeleteAccount {
pub password: Sensitive<String>,
pub delete_content: bool,
pub auth: Sensitive<String>,
}

View file

@ -1,4 +1,4 @@
use crate::post::SiteMetadata;
use crate::{context::LemmyContext, post::SiteMetadata};
use encoding::{all::encodings, DecoderTrap};
use lemmy_db_schema::newtypes::DbUrl;
use lemmy_utils::{
@ -150,12 +150,11 @@ pub(crate) async fn fetch_pictrs(
/// - It might not be an image
/// - Pictrs might not be set up
pub async fn purge_image_from_pictrs(
client: &ClientWithMiddleware,
settings: &Settings,
image_url: &Url,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pictrs_config = settings.pictrs_config()?;
is_image_content_type(client, image_url).await?;
let pictrs_config = context.settings().pictrs_config()?;
is_image_content_type(context.client(), image_url).await?;
let alias = image_url
.path_segments()
@ -168,7 +167,8 @@ pub async fn purge_image_from_pictrs(
let pictrs_api_key = pictrs_config
.api_key
.ok_or(LemmyErrorType::PictrsApiKeyNotProvided)?;
let response = client
let response = context
.client()
.post(&purge_url)
.timeout(REQWEST_TIMEOUT)
.header("x-api-token", pictrs_api_key)

View file

@ -58,7 +58,7 @@ pub enum SendActivityData {
CreatePrivateMessage(PrivateMessageView),
UpdatePrivateMessage(PrivateMessageView),
DeletePrivateMessage(Person, PrivateMessage, bool),
DeleteUser(Person),
DeleteUser(Person, bool),
CreateReport(Url, Person, Community, String),
}

View file

@ -42,7 +42,6 @@ use lemmy_utils::{
utils::slurs::build_slur_regex,
};
use regex::Regex;
use reqwest_middleware::ClientWithMiddleware;
use rosetta_i18n::{Language, LanguageId};
use tracing::warn;
use url::{ParseError, Url};
@ -529,19 +528,16 @@ pub fn check_private_instance_and_federation_enabled(
pub async fn purge_image_posts_for_person(
banned_person_id: PersonId,
pool: &mut DbPool<'_>,
settings: &Settings,
client: &ClientWithMiddleware,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pool = &mut context.pool();
let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
for post in posts {
if let Some(url) = post.url {
purge_image_from_pictrs(client, settings, &url).await.ok();
purge_image_from_pictrs(&url, context).await.ok();
}
if let Some(thumbnail_url) = post.thumbnail_url {
purge_image_from_pictrs(client, settings, &thumbnail_url)
.await
.ok();
purge_image_from_pictrs(&thumbnail_url, context).await.ok();
}
}
@ -552,19 +548,16 @@ pub async fn purge_image_posts_for_person(
pub async fn purge_image_posts_for_community(
banned_community_id: CommunityId,
pool: &mut DbPool<'_>,
settings: &Settings,
client: &ClientWithMiddleware,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pool = &mut context.pool();
let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
for post in posts {
if let Some(url) = post.url {
purge_image_from_pictrs(client, settings, &url).await.ok();
purge_image_from_pictrs(&url, context).await.ok();
}
if let Some(thumbnail_url) = post.thumbnail_url {
purge_image_from_pictrs(client, settings, &thumbnail_url)
.await
.ok();
purge_image_from_pictrs(&thumbnail_url, context).await.ok();
}
}
@ -575,21 +568,16 @@ pub async fn purge_image_posts_for_community(
pub async fn remove_user_data(
banned_person_id: PersonId,
pool: &mut DbPool<'_>,
settings: &Settings,
client: &ClientWithMiddleware,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pool = &mut context.pool();
// Purge user images
let person = Person::read(pool, banned_person_id).await?;
if let Some(avatar) = person.avatar {
purge_image_from_pictrs(client, settings, &avatar)
.await
.ok();
purge_image_from_pictrs(&avatar, context).await.ok();
}
if let Some(banner) = person.banner {
purge_image_from_pictrs(client, settings, &banner)
.await
.ok();
purge_image_from_pictrs(&banner, context).await.ok();
}
// Update the fields to None
@ -608,7 +596,7 @@ pub async fn remove_user_data(
Post::update_removed_for_creator(pool, banned_person_id, None, true).await?;
// Purge image posts
purge_image_posts_for_person(banned_person_id, pool, settings, client).await?;
purge_image_posts_for_person(banned_person_id, context).await?;
// Communities
// Remove all communities where they're the top mod
@ -635,12 +623,10 @@ pub async fn remove_user_data(
// Delete the community images
if let Some(icon) = first_mod_community.community.icon {
purge_image_from_pictrs(client, settings, &icon).await.ok();
purge_image_from_pictrs(&icon, context).await.ok();
}
if let Some(banner) = first_mod_community.community.banner {
purge_image_from_pictrs(client, settings, &banner)
.await
.ok();
purge_image_from_pictrs(&banner, context).await.ok();
}
// Update the fields to None
Community::update(
@ -695,23 +681,18 @@ pub async fn remove_user_data_in_community(
Ok(())
}
pub async fn delete_user_account(
pub async fn purge_user_account(
person_id: PersonId,
pool: &mut DbPool<'_>,
settings: &Settings,
client: &ClientWithMiddleware,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let pool = &mut context.pool();
// Delete their images
let person = Person::read(pool, person_id).await?;
if let Some(avatar) = person.avatar {
purge_image_from_pictrs(client, settings, &avatar)
.await
.ok();
purge_image_from_pictrs(&avatar, context).await.ok();
}
if let Some(banner) = person.banner {
purge_image_from_pictrs(client, settings, &banner)
.await
.ok();
purge_image_from_pictrs(&banner, context).await.ok();
}
// No need to update avatar and banner, those are handled in Person::delete_account
@ -726,7 +707,7 @@ pub async fn delete_user_account(
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
// Purge image posts
purge_image_posts_for_person(person_id, pool, settings, client).await?;
purge_image_posts_for_person(person_id, context).await?;
// Leave communities they mod
CommunityModerator::leave_all_communities(pool, person_id).await?;

View file

@ -5,8 +5,9 @@ use lemmy_api_common::{
context::LemmyContext,
person::{DeleteAccount, DeleteAccountResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt,
utils::{local_user_view_from_jwt, purge_user_account},
};
use lemmy_db_schema::source::person::Person;
use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[tracing::instrument(skip(context))]
@ -26,8 +27,14 @@ pub async fn delete_account(
return Err(LemmyErrorType::IncorrectLogin)?;
}
if data.delete_content {
purge_user_account(local_user_view.person.id, &context).await?;
} else {
Person::delete_account(&mut context.pool(), local_user_view.person.id).await?;
}
ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person),
SendActivityData::DeleteUser(local_user_view.person, data.delete_content),
&context,
)
.await?;

View file

@ -165,13 +165,7 @@ impl ActivityHandler for BlockUser {
)
.await?;
if self.remove_data.unwrap_or(false) {
remove_user_data(
blocked_person.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
remove_user_data(blocked_person.id, context).await?;
}
// write mod log

View file

@ -10,20 +10,17 @@ use activitypub_federation::{
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::{context::LemmyContext, utils::delete_user_account};
use lemmy_api_common::{context::LemmyContext, utils::purge_user_account};
use lemmy_db_schema::source::person::Person;
use lemmy_utils::error::LemmyError;
use url::Url;
pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<(), LemmyError> {
pub async fn delete_user(
person: Person,
delete_content: bool,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let actor: ApubPerson = person.into();
delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
let id = generate_activity_id(
DeleteType::Delete,
@ -36,6 +33,7 @@ pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<
kind: DeleteType::Delete,
id: id.clone(),
cc: vec![],
remove_data: Some(delete_content),
};
let inboxes = remote_instance_inboxes(&mut context.pool()).await?;
@ -68,13 +66,11 @@ impl ActivityHandler for DeleteUser {
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context).await?;
delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
if self.remove_data.unwrap_or(false) {
purge_user_account(actor.id, context).await?;
} else {
Person::delete_account(&mut context.pool(), actor.id).await?;
}
Ok(())
}
}

View file

@ -335,7 +335,7 @@ pub async fn match_outgoing_activities(
DeletePrivateMessage(person, pm, deleted) => {
send_apub_delete_private_message(&person.into(), pm, deleted, context).await
}
DeleteUser(person) => delete_user(person, context).await,
DeleteUser(person, delete_content) => delete_user(person, delete_content, context).await,
CreateReport(url, actor, community, reason) => {
Report::send(ObjectId::from(url), actor, community, reason, context).await
}

View file

@ -23,4 +23,6 @@ pub struct DeleteUser {
#[serde(deserialize_with = "deserialize_one_or_many", default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) cc: Vec<Url>,
/// Nonstandard field. If present, all content from the user should be deleted along with the account
pub(crate) remove_data: Option<bool>,
}