CommentReportResponse,
ListCommentReports,
ListCommentReportsResponse,
+ DeleteAccount,
} from 'lemmy-js-client';
export interface API {
return api.client.saveUserSettings(form);
}
+export async function deleteUser(
+ api: API,
+): Promise<LoginResponse> {
+ let form: DeleteAccount = {
+ auth: api.auth,
+ password
+ };
+ return api.client.deleteAccount(form);
+}
+
export async function getSite(
api: API
): Promise<GetSiteResponse> {
resolvePerson,
saveUserSettings,
getSite,
+ createPost,
+ gamma,
+ resolveCommunity,
+ createComment,
+ resolveBetaCommunity,
+ deleteUser,
+ resolvePost,
+ API,
+ resolveComment,
} from './shared';
import {
PersonViewSafe,
let betaPerson = (await resolvePerson(beta, apShortname)).person;
assertUserFederation(alphaPerson, betaPerson);
});
+
+test('Delete user', async () => {
+ let userRes = await registerUser(alpha);
+ expect(userRes.jwt).toBeDefined();
+ let user: API = {
+ client: alpha.client,
+ auth: userRes.jwt
+ }
+
+ // make a local post and comment
+ let alphaCommunity = (await resolveCommunity(user, '!main@lemmy-alpha:8541')).community;
+ let localPost = (await createPost(user, alphaCommunity.community.id)).post_view.post;
+ expect(localPost).toBeDefined();
+ let localComment = (await createComment(user, localPost.id)).comment_view.comment;
+ expect(localComment).toBeDefined();
+
+ // make a remote post and comment
+ let betaCommunity = (await resolveBetaCommunity(user)).community;
+ let remotePost = (await createPost(user, betaCommunity.community.id)).post_view.post;
+ expect(remotePost).toBeDefined();
+ let remoteComment = (await createComment(user, remotePost.id)).comment_view.comment;
+ expect(remoteComment).toBeDefined();
+
+ await deleteUser(user);
+
+ expect((await resolvePost(alpha, localPost)).post).toBeUndefined();
+ expect((await resolveComment(alpha, localComment)).comment).toBeUndefined();
+ expect((await resolvePost(alpha, remotePost)).post).toBeUndefined();
+ expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined();
+});
let site_view = blocking(context.pool(), SiteView::read_local).await??;
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
- let federated_instances = build_federated_instances(
- context.pool(),
- &context.settings().federation,
- &context.settings().hostname,
- )
- .await?;
+ let federated_instances =
+ build_federated_instances(context.pool(), &context.settings()).await?;
Ok(GetSiteResponse {
site_view: Some(site_view),
community::Community,
email_verification::{EmailVerification, EmailVerificationForm},
password_reset_request::PasswordResetRequest,
+ person::Person,
person_block::PersonBlock,
post::{Post, PostRead, PostReadForm},
registration_application::RegistrationApplication,
use lemmy_utils::{
claims::Claims,
email::{send_email, translations::Lang},
- settings::structs::{FederationConfig, Settings},
+ settings::structs::Settings,
utils::generate_random_string,
LemmyError,
Sensitive,
#[tracing::instrument(skip_all)]
pub async fn build_federated_instances(
pool: &DbPool,
- federation_config: &FederationConfig,
- hostname: &str,
+ settings: &Settings,
) -> Result<Option<FederatedInstances>, LemmyError> {
+ let federation_config = &settings.federation;
+ let hostname = &settings.hostname;
let federation = federation_config.to_owned();
if federation.enabled {
let distinct_communities = blocking(pool, move |conn| {
Ok(())
}
+pub async fn delete_user_account(person_id: PersonId, pool: &DbPool) -> Result<(), LemmyError> {
+ // Comments
+ let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
+ blocking(pool, permadelete)
+ .await?
+ .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
+
+ // Posts
+ let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
+ blocking(pool, permadelete)
+ .await?
+ .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
+
+ blocking(pool, move |conn| Person::delete_account(conn, person_id)).await??;
+
+ Ok(())
+}
+
pub fn check_image_has_local_domain(url: &Option<DbUrl>) -> Result<(), LemmyError> {
if let Some(url) = url {
let settings = Settings::get();
None
};
- let federated_instances = build_federated_instances(
- context.pool(),
- &context.settings().federation,
- &context.settings().hostname,
- )
- .await?;
+ let federated_instances =
+ build_federated_instances(context.pool(), &context.settings()).await?;
Ok(GetSiteResponse {
site_view,
use crate::PerformCrud;
use actix_web::web::Data;
use bcrypt::verify;
-use lemmy_api_common::{blocking, get_local_user_view_from_jwt, person::*};
-use lemmy_db_schema::source::{comment::Comment, person::Person, post::Post};
+use lemmy_api_common::{delete_user_account, get_local_user_view_from_jwt, person::*};
+use lemmy_apub::protocol::activities::deletion::delete_user::DeleteUser;
use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::LemmyContext;
return Err(LemmyError::from_message("password_incorrect"));
}
- // Comments
- let person_id = local_user_view.person.id;
- let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
- blocking(context.pool(), permadelete)
- .await?
- .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
-
- // Posts
- let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
- blocking(context.pool(), permadelete)
- .await?
- .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
-
- blocking(context.pool(), move |conn| {
- Person::delete_account(conn, person_id)
- })
- .await??;
+ delete_user_account(local_user_view.person.id, context.pool()).await?;
+ DeleteUser::send(&local_user_view.person.into(), context).await?;
Ok(DeleteAccountResponse {})
}
--- /dev/null
+{
+ "actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "object": "http://ds9.lemmy.ml/u/lemmy_alpha",
+ "cc": [
+ "http://enterprise.lemmy.ml/c/main"
+ ],
+ "type": "Delete",
+ "id": "http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12"
+}
\ No newline at end of file
--- /dev/null
+use crate::{
+ activities::{generate_activity_id, send_lemmy_activity, verify_is_public, verify_person},
+ objects::person::ApubPerson,
+ protocol::activities::deletion::delete_user::DeleteUser,
+};
+use activitystreams_kinds::{activity::DeleteType, public};
+use lemmy_api_common::{blocking, delete_user_account};
+use lemmy_apub_lib::{
+ data::Data,
+ object_id::ObjectId,
+ traits::ActivityHandler,
+ verify::verify_urls_match,
+};
+use lemmy_db_schema::source::site::Site;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+
+/// This can be separate from Delete activity because it doesn't need to be handled in shared inbox
+/// (cause instance actor doesn't have shared inbox).
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for DeleteUser {
+ type DataType = LemmyContext;
+
+ async fn verify(
+ &self,
+ context: &Data<LemmyContext>,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_is_public(&self.to, &[])?;
+ verify_person(&self.actor, context, request_counter).await?;
+ verify_urls_match(self.actor.inner(), self.object.inner())?;
+ Ok(())
+ }
+
+ async fn receive(
+ self,
+ context: &Data<LemmyContext>,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let actor = self
+ .actor
+ .dereference(context, context.client(), request_counter)
+ .await?;
+ delete_user_account(actor.id, context.pool()).await?;
+ Ok(())
+ }
+}
+
+impl DeleteUser {
+ #[tracing::instrument(skip_all)]
+ pub async fn send(actor: &ApubPerson, context: &LemmyContext) -> Result<(), LemmyError> {
+ let actor_id = ObjectId::new(actor.actor_id.clone());
+ let id = generate_activity_id(
+ DeleteType::Delete,
+ &context.settings().get_protocol_and_hostname(),
+ )?;
+ let delete = DeleteUser {
+ actor: actor_id.clone(),
+ to: vec![public()],
+ object: actor_id,
+ kind: DeleteType::Delete,
+ id: id.clone(),
+ cc: vec![],
+ };
+
+ let remote_sites = blocking(context.pool(), Site::read_remote_sites).await??;
+ let inboxes = remote_sites
+ .into_iter()
+ .map(|s| s.inbox_url.into())
+ .collect();
+ send_lemmy_activity(context, &delete, &id, actor, inboxes, true).await?;
+ Ok(())
+ }
+}
use url::Url;
pub mod delete;
+pub mod delete_user;
pub mod undo_delete;
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
post::CreateOrUpdatePost,
private_message::CreateOrUpdatePrivateMessage,
},
- deletion::{delete::Delete, undo_delete::UndoDelete},
+ deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete},
following::{
accept::AcceptFollowCommunity,
follow::FollowCommunity,
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
#[serde(untagged)]
#[activity_handler(LemmyContext)]
+#[allow(clippy::enum_variant_names)]
pub enum SiteInboxActivities {
BlockUser(BlockUser),
UndoBlockUser(UndoBlockUser),
+ DeleteUser(DeleteUser),
}
#[async_trait::async_trait(?Send)]
--- /dev/null
+use crate::objects::person::ApubPerson;
+use activitystreams_kinds::activity::DeleteType;
+use lemmy_apub_lib::object_id::ObjectId;
+use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
+use url::Url;
+
+#[skip_serializing_none]
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DeleteUser {
+ pub(crate) actor: ObjectId<ApubPerson>,
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
+ pub(crate) to: Vec<Url>,
+ pub(crate) object: ObjectId<ApubPerson>,
+ #[serde(rename = "type")]
+ pub(crate) kind: DeleteType,
+ pub(crate) id: Url,
+
+ #[serde(deserialize_with = "crate::deserialize_one_or_many")]
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub(crate) cc: Vec<Url>,
+}
pub mod delete;
+pub mod delete_user;
pub mod undo_delete;
#[cfg(test)]
mod tests {
use crate::protocol::{
- activities::deletion::{delete::Delete, undo_delete::UndoDelete},
+ activities::deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete},
tests::test_parse_lemmy_item,
};
"assets/lemmy/activities/deletion/undo_delete_private_message.json",
)
.unwrap();
+
+ test_parse_lemmy_item::<DeleteUser>("assets/lemmy/activities/deletion/delete_user.json")
+ .unwrap();
}
}