mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(api-tokens): allow token creation for MCP when REST API is disabled
Closes #11281
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- Files written on a server with `tee` get the server umask (027/077 on hardened hosts). Set the mode explicitly (`chmod 644`) for files that a non-root container user reads through a read-only bind mount.
|
||||
- `sshd` keeps the login shell of an open multiplexed SSH connection. Call `SshMultiplexingHelper::removeMuxFile()` before a live test of SSH or login-shell behavior.
|
||||
- SSH retries replay the full command batch. Only exit code 255 is an SSH transport failure; other exit codes come from the remote command, so do not retry them. Keep batches safe to run twice (for example `docker rm -f <name> || true` before `docker run --name <name>`).
|
||||
- Do not put `pkill -f "playwright run-server"` in the same shell command as other work; the pattern matches that shell and kills it. Run it as its own command.
|
||||
- Redirect browser test output to a file (`> /tmp/x.log 2>&1`); piping it (`| tail`) hangs because the Playwright server keeps the pipe open.
|
||||
- Browser `click('Text')` can match a hidden element first (for example inactive modal steps) and wait forever; use `button:visible:has-text("Text")`.
|
||||
- Never run `php artisan test` inside a dev container (`./scripts/dev exec` or `docker exec coolify-dev-*`). The container sets `DB_CONNECTION=pgsql` as a real environment variable, which wins over `phpunit.xml`, so `RefreshDatabase` runs `migrate:fresh` on the dev database and wipes it. Run tests on the host only.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire\Security;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
@@ -35,8 +34,6 @@ class ApiTokens extends Component
|
||||
365 => '1 year',
|
||||
];
|
||||
|
||||
public $isApiEnabled;
|
||||
|
||||
#[Locked]
|
||||
public bool $canUseRootPermissions = false;
|
||||
|
||||
@@ -56,7 +53,6 @@ class ApiTokens extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
||||
$this->canUseRootPermissions = auth()->user()->can('useRootPermissions', PersonalAccessToken::class);
|
||||
$this->canUseWritePermissions = auth()->user()->can('useWritePermissions', PersonalAccessToken::class);
|
||||
$this->canUseDeployPermissions = auth()->user()->can('useDeployPermissions', PersonalAccessToken::class);
|
||||
|
||||
@@ -5,24 +5,6 @@
|
||||
|
||||
<x-security.settings-layout>
|
||||
|
||||
@if (!$isApiEnabled)
|
||||
<div class="application-settings-form">
|
||||
<x-application.settings-section title="API disabled"
|
||||
description="Enable the Coolify API before creating access tokens.">
|
||||
<x-empty title="API access is turned off"
|
||||
description="{{ isInstanceAdmin() ? 'Enable API access in instance settings to issue tokens.' : 'Ask an instance admin to enable API access.' }}"
|
||||
icon-name="keys" size="sm">
|
||||
@if (isInstanceAdmin())
|
||||
<x-slot:actions>
|
||||
<a href="{{ route('settings.advanced') }}" class="button" {{ wireNavigate() }}>
|
||||
Enable API access
|
||||
</a>
|
||||
</x-slot:actions>
|
||||
@endif
|
||||
</x-empty>
|
||||
</x-application.settings-section>
|
||||
</div>
|
||||
@else
|
||||
@php
|
||||
$expirationList = collect($expirationOptions)
|
||||
->map(fn ($label, $days) => ['value' => (string) $days, 'label' => $label])
|
||||
@@ -32,6 +14,9 @@
|
||||
@endphp
|
||||
|
||||
<div class="application-settings-form flex flex-col gap-6">
|
||||
<x-api-disabled-callout
|
||||
message="REST API requests are rejected. Tokens still authenticate the MCP endpoint when the MCP server is enabled." />
|
||||
|
||||
@can('create', App\Models\PersonalAccessToken::class)
|
||||
<form wire:submit="addNewToken">
|
||||
<x-application.settings-section title="New API token">
|
||||
@@ -262,6 +247,5 @@
|
||||
</div>
|
||||
</x-application.settings-section>
|
||||
</div>
|
||||
@endif
|
||||
</x-security.settings-layout>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +101,12 @@
|
||||
@endif
|
||||
@if ($is_mcp_server_enabled)
|
||||
<x-callout type="info" title="MCP endpoint" class="mt-4">
|
||||
<code>{{ url('/mcp') }}</code> uses Sanctum bearer tokens from Security → API Tokens.
|
||||
<div class="flex items-center gap-1">
|
||||
<code>{{ url('/mcp') }}</code>
|
||||
<x-copy-button :value="url('/mcp')" label="Copy MCP endpoint" />
|
||||
</div>
|
||||
Authenticate with a bearer token from
|
||||
<a href="{{ route('security.api-tokens') }}" class="font-medium underline" {{ wireNavigate() }}>Security → API Tokens</a>.
|
||||
</x-callout>
|
||||
@endif
|
||||
</x-application.settings-section>
|
||||
|
||||
@@ -77,3 +77,17 @@ it('does not show the API notice on the webhooks page when the API is enabled',
|
||||
Livewire::test(Webhooks::class, ['resource' => $this->application])
|
||||
->assertDontSee('API access is turned off');
|
||||
});
|
||||
|
||||
it('lets users create API tokens for MCP while the REST API is disabled', function () {
|
||||
$this->actingAs($this->teamAdmin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApiTokens::class)
|
||||
->assertSee('API access is turned off')
|
||||
->assertSee('Create token')
|
||||
->set('description', 'MCP client')
|
||||
->call('addNewToken')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($this->teamAdmin->tokens()->where('name', 'MCP client')->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
@@ -127,3 +127,24 @@ test('open API allowlist warning is shown when API access is enabled without all
|
||||
->assertSet('is_api_enabled', true)
|
||||
->assertSee('API access is open to every source');
|
||||
});
|
||||
|
||||
test('MCP endpoint callout links to API token management', function () {
|
||||
$rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]);
|
||||
Server::factory()->create(['id' => 0, 'team_id' => $rootTeam->id]);
|
||||
InstanceSettings::forceCreate([
|
||||
'id' => 0,
|
||||
'is_api_enabled' => false,
|
||||
'is_mcp_server_enabled' => true,
|
||||
]);
|
||||
Once::flush();
|
||||
|
||||
$user = User::factory()->create();
|
||||
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||
|
||||
$this->actingAs($user);
|
||||
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||
|
||||
Livewire::test(Advanced::class)
|
||||
->assertSee(url('/mcp'))
|
||||
->assertSee('href="'.route('security.api-tokens').'"', false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user