From c040ba4686f1a2882f87b5fcdde860b9d0a283c3 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 22:09:35 -0300 Subject: [PATCH] Authorize API social accounts via SocialAccountPolicy. Replace repeated workspace_id checks with PostPolicy-style denyAsNotFound tenancy so cross-tenant lookups stay 404 without leaking existence. Co-authored-by: Cursor --- .../Api/SocialAccountController.php | 23 ++--------- app/Policies/SocialAccountPolicy.php | 26 ++++++++++++ .../Unit/Policies/SocialAccountPolicyTest.php | 40 +++++++++++++++++++ 3 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 app/Policies/SocialAccountPolicy.php create mode 100644 tests/Unit/Policies/SocialAccountPolicyTest.php diff --git a/app/Http/Controllers/Api/SocialAccountController.php b/app/Http/Controllers/Api/SocialAccountController.php index 29b99e8d..556c4776 100644 --- a/app/Http/Controllers/Api/SocialAccountController.php +++ b/app/Http/Controllers/Api/SocialAccountController.php @@ -28,14 +28,9 @@ public function index(Request $request): AnonymousResourceCollection return SocialAccountResource::collection($accounts); } - public function toggle(Request $request, SocialAccount $account): SocialAccountResource|JsonResponse + public function toggle(Request $request, SocialAccount $account): SocialAccountResource { - if ($account->workspace_id !== $request->user()->currentWorkspace->id) { - return response()->json( - ['message' => 'Account not found.'], - Response::HTTP_NOT_FOUND, - ); - } + $this->authorize('view', $account); ToggleSocialAccount::execute($account); @@ -44,12 +39,7 @@ public function toggle(Request $request, SocialAccount $account): SocialAccountR public function boards(Request $request, SocialAccount $account): JsonResponse { - if ($account->workspace_id !== $request->user()->currentWorkspace->id) { - return response()->json( - ['message' => 'Account not found.'], - Response::HTTP_NOT_FOUND, - ); - } + $this->authorize('view', $account); if ($account->platform !== Platform::Pinterest) { return response()->json( @@ -77,12 +67,7 @@ public function boards(Request $request, SocialAccount $account): JsonResponse public function channels(Request $request, SocialAccount $account): JsonResponse { - if ($account->workspace_id !== $request->user()->currentWorkspace->id) { - return response()->json( - ['message' => 'Account not found.'], - Response::HTTP_NOT_FOUND, - ); - } + $this->authorize('view', $account); if ($account->platform !== Platform::Discord) { return response()->json( diff --git a/app/Policies/SocialAccountPolicy.php b/app/Policies/SocialAccountPolicy.php new file mode 100644 index 00000000..94422b75 --- /dev/null +++ b/app/Policies/SocialAccountPolicy.php @@ -0,0 +1,26 @@ +workspace_id !== $user->current_workspace_id) { + return Response::denyAsNotFound(); + } + + return true; + } +} diff --git a/tests/Unit/Policies/SocialAccountPolicyTest.php b/tests/Unit/Policies/SocialAccountPolicyTest.php new file mode 100644 index 00000000..99a6bc90 --- /dev/null +++ b/tests/Unit/Policies/SocialAccountPolicyTest.php @@ -0,0 +1,40 @@ +policy = new SocialAccountPolicy; +}); + +test('members of the current workspace can view a social account', function () { + $user = User::factory()->create(); + $workspace = Workspace::factory()->create(['user_id' => $user->id]); + $workspace->members()->attach($user->id, ['role' => Role::Member->value]); + $user->update(['current_workspace_id' => $workspace->id]); + + $account = SocialAccount::factory()->create(['workspace_id' => $workspace->id]); + + expect($this->policy->view($user->fresh(), $account))->toBeTrue(); +}); + +test('cross-workspace social account lookups deny as not found', function () { + $user = User::factory()->create(); + $workspace = Workspace::factory()->create(['user_id' => $user->id]); + $workspace->members()->attach($user->id, ['role' => Role::Member->value]); + $user->update(['current_workspace_id' => $workspace->id]); + + $otherWorkspace = Workspace::factory()->create(); + $account = SocialAccount::factory()->create(['workspace_id' => $otherWorkspace->id]); + + $result = $this->policy->view($user->fresh(), $account); + + expect($result)->toBeInstanceOf(Response::class) + ->and($result->status())->toBe(404); +});