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 <cursoragent@cursor.com>
This commit is contained in:
parent
7b986fca9c
commit
c040ba4686
3 changed files with 70 additions and 19 deletions
|
|
@ -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(
|
||||
|
|
|
|||
26
app/Policies/SocialAccountPolicy.php
Normal file
26
app/Policies/SocialAccountPolicy.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class SocialAccountPolicy
|
||||
{
|
||||
/**
|
||||
* Authorize access to a social account. Must live in the user's current
|
||||
* workspace; cross-workspace lookups deny as 404 so we don't leak
|
||||
* existence across tenants (same tenancy pattern as PostPolicy).
|
||||
*/
|
||||
public function view(User $user, SocialAccount $account): bool|Response
|
||||
{
|
||||
if ($account->workspace_id !== $user->current_workspace_id) {
|
||||
return Response::denyAsNotFound();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
40
tests/Unit/Policies/SocialAccountPolicyTest.php
Normal file
40
tests/Unit/Policies/SocialAccountPolicyTest.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use App\Policies\SocialAccountPolicy;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->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);
|
||||
});
|
||||
Loading…
Reference in a new issue