diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index e37de0378..9626b0488 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -21,6 +21,27 @@ class ApplicationsController extends Controller { + private function removeSensitiveData($application) + { + $token = auth()->user()->currentAccessToken(); + if ($token->can('view:sensitive')) { + return serializeApiResponse($application); + } + $application->makeHidden([ + 'custom_labels', + 'dockerfile', + 'docker_compose', + 'docker_compose_raw', + 'manual_webhook_secret_bitbucket', + 'manual_webhook_secret_gitea', + 'manual_webhook_secret_github', + 'manual_webhook_secret_gitlab', + 'private_key_id', + ]); + + return serializeApiResponse($application); + } + public function applications(Request $request) { $teamId = getTeamIdFromToken(); @@ -32,7 +53,7 @@ public function applications(Request $request) $applications->push($projects->pluck('applications')->flatten()); $applications = $applications->flatten(); $applications = $applications->map(function ($application) { - return serializeApiResponse($application); + return $this->removeSensitiveData($application); }); return response()->json([ @@ -484,10 +505,6 @@ public function application_by_uuid(Request $request) if (! $uuid) { return response()->json(['success' => false, 'message' => 'UUID is required.'], 400); } - $return = validateIncomingRequest($request); - if ($return instanceof \Illuminate\Http\JsonResponse) { - return $return; - } $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); if (! $application) { return response()->json(['success' => false, 'message' => 'Application not found.'], 404); @@ -495,7 +512,7 @@ public function application_by_uuid(Request $request) return response()->json([ 'success' => true, - 'data' => serializeApiResponse($application), + 'data' => $this->removeSensitiveData($application), ]); } @@ -625,7 +642,7 @@ public function update_by_uuid(Request $request) return response()->json([ 'success' => true, - 'data' => serializeApiResponse($application), + 'data' => $this->removeSensitiveData($application), ]); } @@ -635,10 +652,6 @@ public function envs_by_uuid(Request $request) if (is_null($teamId)) { return invalidTokenResponse(); } - $return = validateIncomingRequest($request); - if ($return instanceof \Illuminate\Http\JsonResponse) { - return $return; - } $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); if (! $application) { diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 36a5fffaf..24530dca6 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -20,6 +20,27 @@ class DatabasesController extends Controller { + private function removeSensitiveData($database) + { + $token = auth()->user()->currentAccessToken(); + if ($token->can('view:sensitive')) { + return serializeApiResponse($database); + } + + $database->makeHidden([ + 'internal_db_url', + 'external_db_url', + 'postgres_password', + 'dragonfly_password', + 'redis_password', + 'mongo_initdb_root_password', + 'keydb_password', + 'clickhouse_admin_password', + ]); + + return serializeApiResponse($database); + } + public function databases(Request $request) { $teamId = getTeamIdFromToken(); @@ -32,7 +53,7 @@ public function databases(Request $request) $databases = $databases->merge($project->databases()); } $databases = $databases->map(function ($database) { - return serializeApiResponse($database); + return $this->removeSensitiveData($database); }); return response()->json([ @@ -57,7 +78,7 @@ public function database_by_uuid(Request $request) return response()->json([ 'success' => true, - 'data' => serializeApiResponse($database), + 'data' => $this->removeSensitiveData($database), ]); } diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 76e67548c..7add22bd0 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -20,6 +20,20 @@ class DeployController extends Controller { + private function removeSensitiveData($deployment) + { + $token = auth()->user()->currentAccessToken(); + if ($token->can('view:sensitive')) { + return serializeApiResponse($deployment); + } + + $deployment->makeHidden([ + 'logs', + ]); + + return serializeApiResponse($deployment); + } + public function deployments(Request $request) { $teamId = getTeamIdFromToken(); @@ -61,7 +75,7 @@ public function deployment_by_uuid(Request $request) return response()->json([ 'success' => true, - 'data' => serializeApiResponse($deployment->makeHidden('logs')), + 'data' => $this->removeSensitiveData($deployment), ]); } diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php index a256e9caf..b7837c785 100644 --- a/app/Http/Controllers/Api/TeamController.php +++ b/app/Http/Controllers/Api/TeamController.php @@ -7,17 +7,36 @@ class TeamController extends Controller { + private function removeSensitiveData($team) + { + $token = auth()->user()->currentAccessToken(); + if ($token->can('view:sensitive')) { + return serializeApiResponse($team); + } + $team->makeHidden([ + 'smtp_username', + 'smtp_password', + 'resend_api_key', + 'telegram_token', + ]); + + return serializeApiResponse($team); + } + public function teams(Request $request) { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $teams = auth()->user()->teams; + $teams = auth()->user()->teams->sortBy('id'); + $teams = $teams->map(function ($team) { + return $this->removeSensitiveData($team); + }); return response()->json([ 'success' => true, - 'data' => serializeApiResponse($teams), + 'data' => $teams, ]); } @@ -33,6 +52,7 @@ public function team_by_id(Request $request) if (is_null($team)) { return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404); } + $team = $this->removeSensitiveData($team); return response()->json([ 'success' => true, @@ -52,10 +72,11 @@ public function members_by_id(Request $request) if (is_null($team)) { return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404); } + $members = $team->members; return response()->json([ 'success' => true, - 'data' => serializeApiResponse($team->members), + 'data' => serializeApiResponse($members), ]); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e29c4a307..5f1731071 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -67,5 +67,7 @@ class Kernel extends HttpKernel 'signed' => \App\Http\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class, + 'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class, ]; } diff --git a/app/Http/Middleware/OnlyRootApiToken.php b/app/Http/Middleware/OnlyRootApiToken.php new file mode 100644 index 000000000..bea1ec567 --- /dev/null +++ b/app/Http/Middleware/OnlyRootApiToken.php @@ -0,0 +1,25 @@ +user()->currentAccessToken(); + if ($token->can('*')) { + return $next($request); + } + + return response()->json(['success' => false, 'message' => 'You are not allowed to perform this action.'], 403); + } +} diff --git a/app/Http/Middleware/ReadOnlyApiToken.php b/app/Http/Middleware/ReadOnlyApiToken.php new file mode 100644 index 000000000..447a406a6 --- /dev/null +++ b/app/Http/Middleware/ReadOnlyApiToken.php @@ -0,0 +1,28 @@ +user()->currentAccessToken(); + if ($token->can('*')) { + return $next($request); + } + if ($token->can('read-only')) { + return response()->json(['success' => false, 'message' => 'You are not allowed to perform this action.'], 403); + } + + return $next($request); + } +} diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 94e0ac3a3..4afe50d53 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -332,8 +332,7 @@ public function handle(): void private function backup_standalone_mongodb(string $databaseWithCollections): void { try { - ray($this->database->toArray()); - $url = $this->database->get_db_url(useInternal: true); + $url = $this->database->internal_db_url; if ($databaseWithCollections === 'all') { $commands[] = 'mkdir -p '.$this->backup_dir; if (str($this->database->image)->startsWith('mongo:4.0')) { diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index 875a36141..ffdbe95c3 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -46,10 +46,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -87,13 +85,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index d6c4eb2ce..f81f4a2f0 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -44,10 +44,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -102,13 +100,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index 381711946..2b78c9f10 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -46,10 +46,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -108,13 +106,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index 8b4b35d11..858d7b383 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -52,10 +52,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -114,13 +112,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index ee639ae41..5a5ef8a62 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -50,10 +50,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -115,13 +113,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index fc0767109..58d8e03a8 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -52,10 +52,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -113,13 +111,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 1c5d39055..76bc97901 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -72,10 +72,8 @@ public function getListeners() public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -118,13 +116,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index b5c1dd881..a7ce0161a 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -46,10 +46,8 @@ class General extends Component public function mount() { - $this->db_url = $this->database->get_db_url(true); - if ($this->database->is_public) { - $this->db_url_public = $this->database->get_db_url(); - } + $this->db_url = $this->database->internal_db_url; + $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); } @@ -102,13 +100,12 @@ public function instantSave() return; } StartDatabaseProxy::run($this->database); - $this->db_url_public = $this->database->get_db_url(); $this->dispatch('success', 'Database is now publicly accessible.'); } else { StopDatabaseProxy::run($this->database); - $this->db_url_public = null; $this->dispatch('success', 'Database is no longer publicly accessible.'); } + $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { $this->database->is_public = ! $this->database->is_public; diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index c485a6a3a..ff8679d21 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -10,6 +10,12 @@ class ApiTokens extends Component public $tokens = []; + public bool $viewSensitiveData = false; + + public bool $readOnly = true; + + public array $permissions = ['read-only']; + public function render() { return view('livewire.security.api-tokens'); @@ -17,7 +23,33 @@ public function render() public function mount() { - $this->tokens = auth()->user()->tokens; + $this->tokens = auth()->user()->tokens->sortByDesc('created_at'); + } + + public function updatedViewSensitiveData() + { + if ($this->viewSensitiveData) { + $this->permissions[] = 'view:sensitive'; + $this->permissions = array_diff($this->permissions, ['*']); + } else { + $this->permissions = array_diff($this->permissions, ['view:sensitive']); + } + if (count($this->permissions) == 0) { + $this->permissions = ['*']; + } + } + + public function updatedReadOnly() + { + if ($this->readOnly) { + $this->permissions[] = 'read-only'; + $this->permissions = array_diff($this->permissions, ['*']); + } else { + $this->permissions = array_diff($this->permissions, ['read-only']); + } + if (count($this->permissions) == 0) { + $this->permissions = ['*']; + } } public function addNewToken() @@ -26,7 +58,13 @@ public function addNewToken() $this->validate([ 'description' => 'required|min:3|max:255', ]); - $token = auth()->user()->createToken($this->description); + // if ($this->viewSensitiveData) { + // $this->permissions[] = 'view:sensitive'; + // } + // if ($this->readOnly) { + // $this->permissions[] = 'read-only'; + // } + $token = auth()->user()->createToken($this->description, $this->permissions); $this->tokens = auth()->user()->tokens; session()->flash('token', $token->plainTextToken); } catch (\Exception $e) { diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 673224650..718fc9927 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -195,7 +195,7 @@ public function type(): string protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}", + get: fn () => "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->uuid}:9000/{$this->clickhouse_db}", ); } @@ -204,7 +204,7 @@ protected function externalDbUrl(): Attribute return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}"; + return "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}"; } return null; @@ -212,15 +212,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index d78d656c1..b8d16d512 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -212,15 +212,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 7b71bd55f..d2963cf02 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -212,15 +212,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 00df4fe71..b7907f251 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -212,15 +212,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 0863522a8..0f9f9a426 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -232,15 +232,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 79e7c37fa..bc4de88ee 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -213,15 +213,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 1d5276cf3..372d79fd8 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -213,15 +213,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index e0f863aca..64731a28b 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -208,15 +208,6 @@ protected function externalDbUrl(): Attribute ); } - public function get_db_url(bool $useInternal = false) - { - if ($this->is_public && ! $useInternal) { - return $this->externalDbUrl; - } else { - return $this->internalDbUrl; - } - } - public function environment() { return $this->belongsTo(Environment::class); diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index c5083534f..00fccda74 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -19,37 +19,67 @@ function invalidTokenResponse() function serializeApiResponse($data) { - if (! $data instanceof Collection) { - $data = collect($data); - } - $data = $data->sortKeys(); + if ($data instanceof Collection) { + $data = $data->map(function ($d) { + $d = collect($d)->sortKeys(); + $created_at = data_get($d, 'created_at'); + $updated_at = data_get($d, 'updated_at'); + if ($created_at) { + unset($d['created_at']); + $d['created_at'] = $created_at; - $created_at = data_get($data, 'created_at'); - $updated_at = data_get($data, 'updated_at'); - if ($created_at) { - unset($data['created_at']); - $data['created_at'] = $created_at; + } + if ($updated_at) { + unset($d['updated_at']); + $d['updated_at'] = $updated_at; + } + if (data_get($d, 'name')) { + $d = $d->prepend($d['name'], 'name'); + } + if (data_get($d, 'description')) { + $d = $d->prepend($d['description'], 'description'); + } + if (data_get($d, 'uuid')) { + $d = $d->prepend($d['uuid'], 'uuid'); + } - } - if ($updated_at) { - unset($data['updated_at']); - $data['updated_at'] = $updated_at; - } - if (data_get($data, 'name')) { - $data = $data->prepend($data['name'], 'name'); - } - if (data_get($data, 'description')) { - $data = $data->prepend($data['description'], 'description'); - } - if (data_get($data, 'uuid')) { - $data = $data->prepend($data['uuid'], 'uuid'); - } + if (! is_null(data_get($d, 'id'))) { + $d = $d->prepend($d['id'], 'id'); + } - if (data_get($data, 'id')) { - $data = $data->prepend($data['id'], 'id'); - } + return $d; + }); - return $data; + return $data; + } else { + $d = collect($data)->sortKeys(); + $created_at = data_get($d, 'created_at'); + $updated_at = data_get($d, 'updated_at'); + if ($created_at) { + unset($d['created_at']); + $d['created_at'] = $created_at; + + } + if ($updated_at) { + unset($d['updated_at']); + $d['updated_at'] = $updated_at; + } + if (data_get($d, 'name')) { + $d = $d->prepend($d['name'], 'name'); + } + if (data_get($d, 'description')) { + $d = $d->prepend($d['description'], 'description'); + } + if (data_get($d, 'uuid')) { + $d = $d->prepend($d['uuid'], 'uuid'); + } + + if (! is_null(data_get($d, 'id'))) { + $d = $d->prepend($d['id'], 'id'); + } + + return $d; + } } function sharedDataApplications() diff --git a/resources/views/livewire/security/api-tokens.blade.php b/resources/views/livewire/security/api-tokens.blade.php index b9120878d..3a5d4560c 100644 --- a/resources/views/livewire/security/api-tokens.blade.php +++ b/resources/views/livewire/security/api-tokens.blade.php @@ -3,29 +3,59 @@ API Tokens | Coolify -
-

API Tokens

- +
+

API Tokens

+
Tokens are created with the current team as scope. You will only have access to this team's resources. +
-

Create New Token

-
- - Create New Token +

New Token

+ +
+ + Create New Token +
+
+ Permissions : +
+ @if ($permissions) + @foreach ($permissions as $permission) + @if ($permission === '*') +
All (root/admin access), be careful!
+ @else +
{{ $permission }}
+ @endif + @endforeach + @endif +
+
+

Token Permissions

+
+ + +
@if (session()->has('token')) -
Please copy this token now. For your security, it won't be shown again. +
Please copy this token now. For your security, it won't be shown + again.
{{ session('token') }}
@endif -

Issued Tokens

+

Issued Tokens

@forelse ($tokens as $token) -
-
-
{{ $token->name }}
+
+
Description: {{ $token->name }}
+
Last used: {{ $token->last_used_at ? $token->last_used_at->diffForHumans() : 'Never' }}
+
+ @if ($token->abilities) + Abilities: + @foreach ($token->abilities as $ability) +
{{ $ability }}
+ @endforeach + @endif
+ Revoke token diff --git a/routes/api.php b/routes/api.php index 69eead3ba..61b3348b7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,8 @@ use App\Http\Controllers\Api\ServersController; use App\Http\Controllers\Api\TeamController; use App\Http\Middleware\ApiAllowed; +use App\Http\Middleware\OnlyRootApiToken; +use App\Http\Middleware\ReadOnlyApiToken; use App\Models\InstanceSettings; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; @@ -31,7 +33,7 @@ }); Route::group([ - 'middleware' => ['auth:sanctum'], + 'middleware' => ['auth:sanctum', OnlyRootApiToken::class], 'prefix' => 'v1', ], function () { Route::get('/enable', function () { @@ -81,13 +83,13 @@ Route::get('/projects/{uuid}/{environment_name}', [ProjectController::class, 'environment_details']); Route::get('/security/keys', [SecurityController::class, 'keys']); - Route::post('/security/keys', [SecurityController::class, 'create_key']); + Route::post('/security/keys', [SecurityController::class, 'create_key'])->middleware([ReadOnlyApiToken::class]); Route::get('/security/keys/{uuid}', [SecurityController::class, 'key_by_uuid']); - Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key']); - Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key']); + Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware([ReadOnlyApiToken::class]); + Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware([ReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy']); + Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware([ReadOnlyApiToken::class]); Route::get('/deployments', [DeployController::class, 'deployments']); Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid']); @@ -99,29 +101,29 @@ Route::get('/resources', [ResourcesController::class, 'resources']); Route::get('/applications', [ApplicationsController::class, 'applications']); - Route::post('/applications', [ApplicationsController::class, 'create_application']); + Route::post('/applications', [ApplicationsController::class, 'create_application'])->middleware([ReadOnlyApiToken::class]); Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid']); - Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid']); - Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid']); + Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid'])->middleware([ReadOnlyApiToken::class]); + Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid'])->middleware([ReadOnlyApiToken::class]); Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs_by_uuid']); - Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env']); - Route::post('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs']); + Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env'])->middleware([ReadOnlyApiToken::class]); + Route::post('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware([ReadOnlyApiToken::class]); Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid']); - Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid']); + Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware([ReadOnlyApiToken::class]); - Route::match(['get', 'post'], '/applications/{uuid}/action/deploy', [ApplicationsController::class, 'action_deploy']); - Route::match(['get', 'post'], '/applications/{uuid}/action/restart', [ApplicationsController::class, 'action_restart']); - Route::match(['get', 'post'], '/applications/{uuid}/action/stop', [ApplicationsController::class, 'action_stop']); + Route::match(['get', 'post'], '/applications/{uuid}/action/deploy', [ApplicationsController::class, 'action_deploy'])->middleware([ReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/applications/{uuid}/action/restart', [ApplicationsController::class, 'action_restart'])->middleware([ReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/applications/{uuid}/action/stop', [ApplicationsController::class, 'action_stop'])->middleware([ReadOnlyApiToken::class]); Route::get('/databases', [DatabasesController::class, 'databases']); - Route::post('/databases', [DatabasesController::class, 'create_database']); + Route::post('/databases', [DatabasesController::class, 'create_database'])->middleware([ReadOnlyApiToken::class]); Route::get('/databases/{uuid}', [DatabasesController::class, 'database_by_uuid']); // Route::patch('/databases/{uuid}', [DatabasesController::class, 'update_by_uuid']); - Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid']); + Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid'])->middleware([ReadOnlyApiToken::class]); - Route::delete('/envs/{env_uuid}', [EnvironmentVariablesController::class, 'delete_env_by_uuid']); + Route::delete('/envs/{env_uuid}', [EnvironmentVariablesController::class, 'delete_env_by_uuid'])->middleware([ReadOnlyApiToken::class]); });