diff --git a/CHANGELOG.md b/CHANGELOG.md index c787faaee..2b7670c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ - Update DiscoverController, handle discover hashtag redirects ([18382e8a](https://github.com/pixelfed/pixelfed/commit/18382e8a)) - Update ApiV1Controller, use admin filter service ([94503a1c](https://github.com/pixelfed/pixelfed/commit/94503a1c)) - Update SearchApiV2Service, use more efficient query ([cee618e8](https://github.com/pixelfed/pixelfed/commit/cee618e8)) +- Update Curated Onboarding view, fix concierge form ([15ad69f7](https://github.com/pixelfed/pixelfed/commit/15ad69f7)) +- Update AP Profile Transformer, add `suspended` attribute ([25f3fa06](https://github.com/pixelfed/pixelfed/commit/25f3fa06)) +- Update AP Profile Transformer, fix movedTo attribute ([63100fe9](https://github.com/pixelfed/pixelfed/commit/63100fe9)) +- Update AP Profile Transformer, fix suspended attributes ([2e5e68e4](https://github.com/pixelfed/pixelfed/commit/2e5e68e4)) +- Update PrivacySettings controller, add cache invalidation ([e742d595](https://github.com/pixelfed/pixelfed/commit/e742d595)) +- Update ProfileController, preserve deleted actor objects for federated account deletion and use more efficient account cache lookup ([853a729f](https://github.com/pixelfed/pixelfed/commit/853a729f)) +- Update SiteController, add curatedOnboarding method that gracefully falls back to open registration when applicable ([95199843](https://github.com/pixelfed/pixelfed/commit/95199843)) +- Update AP transformers, add DeleteActor activity ([bcce1df6](https://github.com/pixelfed/pixelfed/commit/bcce1df6)) +- Update commands, add user account delete cli command to federate account deletion ([4aa0e25f](https://github.com/pixelfed/pixelfed/commit/4aa0e25f)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.13 (2024-03-05)](https://github.com/pixelfed/pixelfed/compare/v0.11.12...v0.11.13) 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(); + } +}