test(api): cover member access to the cloud provider token list

A team member with a read token gets 403 on GET /api/v1/cloud-tokens
and an error from the MCP cloud_provider list_tokens action.
This commit is contained in:
Andras Bacsai
2026-11-09 10:59:00 +01:00
parent f4cbf63e76
commit 132dd0e34c
2 changed files with 51 additions and 0 deletions
@@ -80,6 +80,27 @@ describe('GET /api/v1/cloud-tokens', function () {
$response->assertJsonCount(1);
});
test('rejects a team member with a read token', function () {
CloudProviderToken::factory()->create([
'team_id' => $this->team->id,
'name' => 'Production Hetzner',
'provider' => 'hetzner',
]);
$member = User::factory()->create();
$this->team->members()->attach($member->id, ['role' => 'member']);
session(['currentTeam' => $this->team]);
$memberToken = $member->createToken('member-read', ['read'])->plainTextToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer '.$memberToken,
'Content-Type' => 'application/json',
])->getJson('/api/v1/cloud-tokens');
$response->assertForbidden();
expect($response->getContent())->not->toContain('Production Hetzner');
});
test('rejects request without authentication', function () {
$response = $this->getJson('/api/v1/cloud-tokens');
$response->assertStatus(401);
+30
View File
@@ -7,6 +7,7 @@ use App\Mcp\Tools\CoolifyHelp;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\CloudProviderToken;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
@@ -903,6 +904,35 @@ test('list_applications list_databases list_services server_uuid filters use des
->not->toContain($otherServerService->uuid);
});
test('cloud_provider list_tokens rejects a team member with a read token', function () {
CloudProviderToken::factory()->create([
'team_id' => $this->team->id,
'name' => 'Production Hetzner',
'provider' => 'hetzner',
]);
$member = User::factory()->create();
$this->team->members()->attach($member->id, ['role' => 'member']);
session(['currentTeam' => $this->team]);
$memberToken = $member->createToken('member-read', ['read'])->plainTextToken;
auth()->forgetGuards();
$response = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json, text/event-stream',
'Authorization' => 'Bearer '.$memberToken,
])->postJson('/mcp', [
'jsonrpc' => '2.0',
'id' => 1,
'method' => 'tools/call',
'params' => ['name' => 'cloud_provider', 'arguments' => (object) ['action' => 'list_tokens']],
]);
$response->assertOk();
expect($response->json('result.isError'))->toBeTrue()
->and($response->getContent())->not->toContain('Production Hetzner');
});
test('list_tags and get_current_team and list_team_members are team scoped', function () {
Tag::create(['name' => 'prod', 'team_id' => $this->team->id]);
Tag::create(['name' => 'theirs', 'team_id' => Team::factory()->create()->id]);