mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-16 10:15:05 +00:00
refactor(auth): enforce team member authorization across app
Restrict sensitive operations to admins/owners and hide sensitive data from team members: - Add authorization checks to Livewire components and API endpoints - Restrict team members from accessing sensitive permissions and data - Hide environment variable values from non-admin team members - Update policies to enforce team-level admin status requirement - Add useSensitivePermissions policy for read:sensitive tokens - Improve disabled button UX with auth-specific tooltips - Add authorization checks in middleware for API tokens Closes authorization gaps in project management, server management, and settings components.
This commit is contained in:
1 parent
e82942b387
commit
b878dc8102
49 files changed
+919
-136
No files matched your search
@@ -4,11 +4,14 @@ namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class AddEmpty extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public string $name;
|
||||
|
||||
public string $description = '';
|
||||
@@ -29,6 +32,7 @@ class AddEmpty extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->authorize('create', Project::class);
|
||||
$this->validate();
|
||||
$project = Project::create([
|
||||
'name' => $this->name,
|
||||
|
||||
@@ -123,6 +123,8 @@ class Show extends Component
|
||||
|
||||
public function downloadAllLogs(): string
|
||||
{
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
$logs = decode_remote_command_output($this->application_deployment_queue, includeAll: true)
|
||||
->map(function ($line) {
|
||||
$prefix = '';
|
||||
|
||||
@@ -6,11 +6,14 @@ use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeploymentNavbar extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
||||
|
||||
public Application $application;
|
||||
@@ -35,10 +38,15 @@ class DeploymentNavbar extends Component
|
||||
|
||||
public function show_debug()
|
||||
{
|
||||
$this->application->settings->is_debug_enabled = ! $this->application->settings->is_debug_enabled;
|
||||
$this->application->settings->save();
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->dispatch('refreshQueue');
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
$this->application->settings->is_debug_enabled = ! $this->application->settings->is_debug_enabled;
|
||||
$this->application->settings->save();
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->dispatch('refreshQueue');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function force_start()
|
||||
|
||||
@@ -11,11 +11,14 @@ use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class CloneMe extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public string $project_uuid;
|
||||
|
||||
public string $environment_uuid;
|
||||
@@ -91,6 +94,7 @@ class CloneMe extends Component
|
||||
public function clone(string $type)
|
||||
{
|
||||
try {
|
||||
$this->authorize('create', Project::class);
|
||||
$this->validate([
|
||||
'selectedDestination' => 'required',
|
||||
'newName' => ValidationPatterns::nameRules(),
|
||||
|
||||
@@ -90,6 +90,7 @@ class Heading extends Component
|
||||
$this->authorize('manage', $this->database);
|
||||
|
||||
$activity = RestartDatabase::run($this->database);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startdatabase'))");
|
||||
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -102,6 +103,7 @@ class Heading extends Component
|
||||
$this->authorize('manage', $this->database);
|
||||
|
||||
$activity = StartDatabase::run($this->database);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startdatabase'))");
|
||||
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -4,10 +4,13 @@ namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Edit extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Project $project;
|
||||
|
||||
public string $name;
|
||||
@@ -54,6 +57,7 @@ class Edit extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->project);
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'Project updated.');
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -5,11 +5,14 @@ namespace App\Livewire\Project;
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class EnvironmentEdit extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Project $project;
|
||||
|
||||
public Application $application;
|
||||
@@ -62,6 +65,7 @@ class EnvironmentEdit extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->environment);
|
||||
$this->syncData(true);
|
||||
redirectRoute($this, 'project.environment.edit', [
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
|
||||
@@ -105,6 +105,7 @@ class Heading extends Component
|
||||
try {
|
||||
$this->authorize('deploy', $this->service);
|
||||
$activity = StartService::run($this->service, pullLatestImages: true);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -125,6 +126,7 @@ class Heading extends Component
|
||||
$activity->save();
|
||||
}
|
||||
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -152,6 +154,7 @@ class Heading extends Component
|
||||
return;
|
||||
}
|
||||
$activity = StartService::run($this->service, stopBeforeStart: true);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -169,6 +172,7 @@ class Heading extends Component
|
||||
return;
|
||||
}
|
||||
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startservice'))");
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -72,7 +72,7 @@ class All extends Component
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
return $this->nullLockedValues($query->get());
|
||||
}
|
||||
|
||||
public function getEnvironmentVariablesPreviewProperty()
|
||||
@@ -86,7 +86,21 @@ class All extends Component
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
return $this->nullLockedValues($query->get());
|
||||
}
|
||||
|
||||
private function nullLockedValues($envs)
|
||||
{
|
||||
$isMember = auth()->user()?->isMember();
|
||||
|
||||
$envs->each(function ($env) use ($isMember) {
|
||||
if ($env->is_shown_once || $isMember) {
|
||||
$env->value = null;
|
||||
$env->real_value = null;
|
||||
}
|
||||
});
|
||||
|
||||
return $envs;
|
||||
}
|
||||
|
||||
public function getDevView()
|
||||
@@ -99,7 +113,12 @@ class All extends Component
|
||||
|
||||
private function formatEnvironmentVariables($variables)
|
||||
{
|
||||
return $variables->map(function ($item) {
|
||||
$isMember = auth()->user()?->isMember();
|
||||
|
||||
return $variables->map(function ($item) use ($isMember) {
|
||||
if ($isMember) {
|
||||
return "$item->key=(Hidden, only admins can view)";
|
||||
}
|
||||
if ($item->is_shown_once) {
|
||||
return "$item->key=(Locked Secret, delete and add again to change)";
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ class Show extends Component
|
||||
|
||||
public bool $is_redis_credential = false;
|
||||
|
||||
public bool $isValueHidden = false;
|
||||
|
||||
public array $problematicVariables = [];
|
||||
|
||||
protected $listeners = [
|
||||
@@ -134,6 +136,13 @@ class Show extends Component
|
||||
$this->is_really_required = $this->env->is_really_required ?? false;
|
||||
$this->is_shared = $this->env->is_shared ?? false;
|
||||
$this->real_value = $this->env->real_value;
|
||||
|
||||
if ($this->env->is_shown_once || auth()->user()?->isMember()) {
|
||||
$this->value = null;
|
||||
$this->real_value = null;
|
||||
}
|
||||
|
||||
$this->isValueHidden = auth()->user()?->isMember() ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user