From 4aa0e25f4c5c394d49727e6aaba89c0b3405c927 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 8 Mar 2024 06:44:02 -0700 Subject: [PATCH] Update commands, add user account delete cli command to federate account deletion --- app/Console/Commands/UserAccountDelete.php | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 app/Console/Commands/UserAccountDelete.php diff --git a/app/Console/Commands/UserAccountDelete.php b/app/Console/Commands/UserAccountDelete.php new file mode 100644 index 000000000..68fad1e92 --- /dev/null +++ b/app/Console/Commands/UserAccountDelete.php @@ -0,0 +1,123 @@ + strlen($value) > 0 + ? User::withTrashed()->whereStatus('deleted')->where('username', 'like', "%{$value}%")->pluck('username', 'id')->all() + : [], + ); + + $user = User::withTrashed()->find($id); + + table( + ['Username', 'Name', 'Email', 'Created'], + [[$user->username, $user->name, $user->email, $user->created_at]] + ); + + $confirmed = confirm( + label: 'Do you want to federate this account deletion?', + default: false, + yes: 'Proceed', + no: 'Cancel', + hint: 'This action is irreversible' + ); + + if (! $confirmed) { + $this->error('Aborting...'); + exit; + } + + $profile = Profile::withTrashed()->find($user->profile_id); + + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($profile, new DeleteActor()); + $activity = $fractal->createData($resource)->toArray(); + + $audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched']) + ->where('nodeinfo_last_fetched', '>', now()->subHours(12)) + ->distinct() + ->pluck('shared_inbox'); + + $payload = json_encode($activity); + + $client = new Client([ + 'timeout' => 10, + ]); + + $version = config('pixelfed.version'); + $appUrl = config('app.url'); + $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; + + $requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) { + foreach ($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity, [ + 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent' => $userAgent, + ]); + yield function () use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + ], + ]); + }; + } + }; + + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => 50, + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + }, + ]); + + $promise = $pool->promise(); + + $promise->wait(); + } +}