From 3cc416a8069eed98bb342c09700a6e5084444a94 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:16:36 +0100 Subject: [PATCH] Restrict upgrade-status endpoint to authenticated root team members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add auth:sanctum middleware to /api/upgrade-status route - Check user belongs to root team (id 0) before returning status - Return 403 if user is not authorized - Update frontend to send credentials with fetch request - Update OpenAPI docs with 401/403 responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/Http/Controllers/Api/OtherController.php | 24 +++++++++++++++++++- resources/views/livewire/upgrade.blade.php | 8 ++++++- routes/api.php | 12 +++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 27b0fc3fe..a0d5c6e56 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -189,9 +189,12 @@ public function healthcheck(Request $request) #[OA\Get( summary: 'Upgrade Status', - description: 'Get the current upgrade status. Returns the step and message from the upgrade process.', + description: 'Get the current upgrade status. Returns the step and message from the upgrade process. Only available to root team members.', path: '/upgrade-status', operationId: 'upgrade-status', + security: [ + ['bearerAuth' => []], + ], responses: [ new OA\Response( response: 200, @@ -204,6 +207,19 @@ public function healthcheck(Request $request) new OA\Property(property: 'message', type: 'string', example: 'Pulling Docker images'), ] )), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 403, + description: 'You are not allowed to view upgrade status.', + content: new OA\JsonContent( + type: 'object', + properties: [ + new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to view upgrade status.'), + ] + )), new OA\Response( response: 400, ref: '#/components/responses/400', @@ -212,6 +228,12 @@ public function healthcheck(Request $request) )] public function upgradeStatus(Request $request) { + // Only root team members can view upgrade status + $user = auth()->user(); + if (! $user || $user->currentTeam()->id !== 0) { + return response()->json(['message' => 'You are not allowed to view upgrade status.'], 403); + } + $statusFile = '/data/coolify/source/.upgrade-status'; if (! file_exists($statusFile)) { diff --git a/resources/views/livewire/upgrade.blade.php b/resources/views/livewire/upgrade.blade.php index f379f24a9..6d4e0a529 100644 --- a/resources/views/livewire/upgrade.blade.php +++ b/resources/views/livewire/upgrade.blade.php @@ -286,11 +286,17 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel // Poll upgrade status API for real progress this.checkUpgradeStatusInterval = setInterval(() => { - fetch('/api/upgrade-status') + fetch('/api/upgrade-status', { + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + } + }) .then(response => { if (response.ok) { return response.json(); } + // Auth errors (401/403) or service down - switch to health check throw new Error('Service unavailable'); }) .then(data => { diff --git a/routes/api.php b/routes/api.php index 745a83534..3453bdd09 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,11 +19,21 @@ use Illuminate\Support\Facades\Route; Route::get('/health', [OtherController::class, 'healthcheck']); -Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']); Route::group([ 'prefix' => 'v1', ], function () { Route::get('/health', [OtherController::class, 'healthcheck']); +}); + +Route::group([ + 'middleware' => ['auth:sanctum'], +], function () { + Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']); +}); +Route::group([ + 'middleware' => ['auth:sanctum'], + 'prefix' => 'v1', +], function () { Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']); });