diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 18e726470..1c66190e7 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -4747,7 +4747,7 @@ class ApiV1Controller extends Controller public function accountRemoveFollowById(Request $request, $id) { - abort_if(! $request->user(), 403); + abort_if(! $request->user() || ! $request->user()->token(), 403); abort_unless($request->user()->tokenCan('follow'), 403); $pid = $request->user()->profile_id; diff --git a/tests/Feature/Api/RemoveFollowerScopeTest.php b/tests/Feature/Api/RemoveFollowerScopeTest.php new file mode 100644 index 000000000..bab0e8c03 --- /dev/null +++ b/tests/Feature/Api/RemoveFollowerScopeTest.php @@ -0,0 +1,83 @@ +create(); + $profile = Profile::create([ + 'user_id' => $user->id, + 'username' => $user->username, + 'name' => $user->name, + ]); + $user->profile_id = $profile->id; + $user->save(); + + return $user; + } + + #[Test] + public function remove_follower_requires_follow_scope() + { + $alice = $this->createUserWithProfile(); + $bob = $this->createUserWithProfile(); + + Follower::withoutEvents(function () use ($alice, $bob) { + Follower::create([ + 'profile_id' => $bob->profile_id, + 'following_id' => $alice->profile_id, + ]); + }); + + // Alice tries to remove Bob with a read-only token — should be denied + Passport::actingAs($alice, ['read']); + + $response = $this->postJson("/api/v1/accounts/{$bob->profile_id}/remove_from_followers"); + + $response->assertStatus(403); + + // Verify follower was NOT removed + $this->assertDatabaseHas('followers', [ + 'profile_id' => $bob->profile_id, + 'following_id' => $alice->profile_id, + ]); + } + + #[Test] + public function remove_follower_denied_with_write_scope_only() + { + $alice = $this->createUserWithProfile(); + $bob = $this->createUserWithProfile(); + + Follower::withoutEvents(function () use ($alice, $bob) { + Follower::create([ + 'profile_id' => $bob->profile_id, + 'following_id' => $alice->profile_id, + ]); + }); + + // Alice tries to remove Bob with a write token (no follow scope) — should be denied + Passport::actingAs($alice, ['write']); + + $response = $this->postJson("/api/v1/accounts/{$bob->profile_id}/remove_from_followers"); + + $response->assertStatus(403); + + $this->assertDatabaseHas('followers', [ + 'profile_id' => $bob->profile_id, + 'following_id' => $alice->profile_id, + ]); + } +}