diff --git a/app/Http/Controllers/LandingController.php b/app/Http/Controllers/LandingController.php index 3dfbdde3c..1c8d713f6 100644 --- a/app/Http/Controllers/LandingController.php +++ b/app/Http/Controllers/LandingController.php @@ -38,6 +38,8 @@ class LandingController extends Controller return DirectoryProfile::collection( Profile::whereNull('domain') + ->where('is_private', false) + ->whereNull('status') ->whereIsSuggestable(true) ->orderByDesc('updated_at') ->cursorPaginate(20) diff --git a/app/Http/Controllers/Settings/PrivacySettings.php b/app/Http/Controllers/Settings/PrivacySettings.php index 5c943706b..b09d81c40 100644 --- a/app/Http/Controllers/Settings/PrivacySettings.php +++ b/app/Http/Controllers/Settings/PrivacySettings.php @@ -242,6 +242,9 @@ trait PrivacySettings } } $profile->is_private = true; + // Clear directory listing when going private so the profile can't leak + // into the public directory (which lists is_suggestable profiles). + $profile->is_suggestable = false; $settings->show_guests = false; $settings->show_discover = false; $settings->save(); diff --git a/tests/Feature/Directory/DirectoryPrivateProfileTest.php b/tests/Feature/Directory/DirectoryPrivateProfileTest.php new file mode 100644 index 000000000..bca17135e --- /dev/null +++ b/tests/Feature/Directory/DirectoryPrivateProfileTest.php @@ -0,0 +1,63 @@ + true]); + config(['instance.enable_cc' => false]); +}); + +it('excludes private suggestable profiles from the directory', function () { + $publicUser = User::factory()->create(); + $publicUser->refresh(); + $publicProfile = $publicUser->profile; + $publicProfile->is_private = false; + $publicProfile->is_suggestable = true; + $publicProfile->save(); + + $privateUser = User::factory()->create(); + $privateUser->refresh(); + $privateProfile = $privateUser->profile; + $privateProfile->is_private = true; + $privateProfile->is_suggestable = true; // stale bug state + $privateProfile->save(); + + $res = $this->getJson('/api/landing/v1/directory') + ->assertOk() + ->json('data'); + + $ids = collect($res)->pluck('id')->map(fn ($id) => (string) $id)->all(); + + expect($ids)->toContain((string) $publicProfile->id); + expect($ids)->not->toContain((string) $privateProfile->id); +}); + +it('clears is_suggestable when an account goes private', function () { + $user = User::factory()->create(); + $user->refresh(); + $profile = $user->profile; + $profile->is_suggestable = true; + $profile->save(); + + $this->actingAs($user) + ->withSession(['auth.password_confirmed_at' => time()]) + ->post('/settings/privacy/account', ['mode' => 'keep-all', 'duration' => 60]) + ->assertOk(); + + $fresh = $profile->fresh(); + expect((bool) $fresh->is_private)->toBeTrue(); + expect((bool) $fresh->is_suggestable)->toBeFalse(); +});