feat(auth): refine authorization checks for S3 storage and service management

This commit is contained in:
Andras Bacsai
2025-08-23 18:50:50 +02:00
parent adb8f9d88e
commit b5fe5dd909
4 changed files with 146 additions and 100 deletions

View File

@@ -29,7 +29,7 @@ class S3StoragePolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
return true; return $user->isAdmin();
} }
/** /**

View File

@@ -28,7 +28,7 @@ class ServicePolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
return true; return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
} }
/** /**
@@ -36,7 +36,7 @@ class ServicePolicy
*/ */
public function update(User $user, Service $service): bool public function update(User $user, Service $service): bool
{ {
return true; return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
} }
/** /**
@@ -73,10 +73,22 @@ class ServicePolicy
public function stop(User $user, Service $service): bool public function stop(User $user, Service $service): bool
{ {
if ($user->isAdmin()) { return $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
return true;
} }
return false; /**
* Determine whether the user can manage environment variables.
*/
public function manageEnvironment(User $user, Service $service): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
}
/**
* Determine whether the user can deploy the service.
*/
public function deploy(User $user, Service $service): bool
{
return $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
} }
} }

View File

@@ -20,6 +20,15 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\Application::class => \App\Policies\ApplicationPolicy::class, \App\Models\Application::class => \App\Policies\ApplicationPolicy::class,
\App\Models\ApplicationPreview::class => \App\Policies\ApplicationPreviewPolicy::class, \App\Models\ApplicationPreview::class => \App\Policies\ApplicationPreviewPolicy::class,
\App\Models\ApplicationSetting::class => \App\Policies\ApplicationSettingPolicy::class, \App\Models\ApplicationSetting::class => \App\Policies\ApplicationSettingPolicy::class,
// Database policies - all use the shared DatabasePolicy
\App\Models\StandalonePostgresql::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneMysql::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneMariadb::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneMongodb::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneRedis::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneKeydb::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneDragonfly::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneClickhouse::class => \App\Policies\DatabasePolicy::class,
]; ];
/** /**

View File

@@ -2,9 +2,11 @@
<form wire:submit='submit' class="flex flex-col pb-32"> <form wire:submit='submit' class="flex flex-col pb-32">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2>General</h2> <h2>General</h2>
@can('update', $application)
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
@endcan
{{-- <x-forms.button wire:click="downloadConfig"> {{-- <x-forms.button wire:click="downloadConfig">
Download Config Download Config
@@ -24,6 +26,7 @@
@if (!$application->dockerfile && $application->build_pack !== 'dockerimage') @if (!$application->dockerfile && $application->build_pack !== 'dockerimage')
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
@can('update', $application)
<x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack" <x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack"
label="Build Pack" required> label="Build Pack" required>
<option value="nixpacks">Nixpacks</option> <option value="nixpacks">Nixpacks</option>
@@ -31,6 +34,16 @@
<option value="dockerfile">Dockerfile</option> <option value="dockerfile">Dockerfile</option>
<option value="dockercompose">Docker Compose</option> <option value="dockercompose">Docker Compose</option>
</x-forms.select> </x-forms.select>
@else
<x-forms.select disabled label="Build Pack" required>
<option value="nixpacks" @if ($application->build_pack === 'nixpacks') selected @endif>Nixpacks</option>
<option value="static" @if ($application->build_pack === 'static') selected @endif>Static</option>
<option value="dockerfile" @if ($application->build_pack === 'dockerfile') selected @endif>Dockerfile
</option>
<option value="dockercompose" @if ($application->build_pack === 'dockercompose') selected @endif>Docker
Compose</option>
</x-forms.select>
@endcan
@if ($application->settings->is_static || $application->build_pack === 'static') @if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.select id="application.static_image" label="Static Image" required> <x-forms.select id="application.static_image" label="Static Image" required>
<option value="nginx:alpine">nginx:alpine</option> <option value="nginx:alpine">nginx:alpine</option>
@@ -52,8 +65,10 @@
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}" label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ str($serviceName)->slug('_') }}.domain"></x-forms.input> id="parsedServiceDomains.{{ str($serviceName)->slug('_') }}.domain"></x-forms.input>
@can('update', $application)
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate <x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button> Domain</x-forms.button>
@endcan
</div> </div>
@endif @endif
@endforeach @endforeach
@@ -66,8 +81,10 @@
<x-forms.textarea id="application.custom_nginx_configuration" <x-forms.textarea id="application.custom_nginx_configuration"
placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration" placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration"
helper="You can add custom Nginx configuration here." /> helper="You can add custom Nginx configuration here." />
@can('update', $application)
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx <x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
Configuration</x-forms.button> Configuration</x-forms.button>
@endcan
@endif @endif
<div class="w-96 pb-6"> <div class="w-96 pb-6">
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
@@ -90,8 +107,10 @@
<x-forms.input placeholder="https://coolify.io" wire:model.blur-sm="application.fqdn" <x-forms.input placeholder="https://coolify.io" wire:model.blur-sm="application.fqdn"
label="Domains" label="Domains"
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " /> helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
@can('update', $application)
<x-forms.button wire:click="getWildcardDomain">Generate Domain <x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button> </x-forms.button>
@endcan
@endif @endif
</div> </div>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
@@ -204,14 +223,18 @@
@endif @endif
<div class="flex flex-col gap-2 pt-6 pb-10"> <div class="flex flex-col gap-2 pt-6 pb-10">
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
@can('update', $application)
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)"> <div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
@else
<div class="flex flex-col gap-2">
@endcan
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/" <x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/"
id="application.base_directory" label="Base Directory" id="application.base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." /> helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="initLoadingCompose" <x-forms.input x-bind:disabled="initLoadingCompose"
placeholder="/docker-compose.yaml" id="application.docker_compose_location" placeholder="/docker-compose.yaml"
label="Docker Compose Location" id="application.docker_compose_location" label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" /> helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
</div> </div>
<div class="w-96"> <div class="w-96">
@@ -240,7 +263,8 @@
</div> </div>
@else @else
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory" <x-forms.input placeholder="/" id="application.base_directory"
label="Base Directory"
helper="Directory to use as root. Useful for monorepos." /> helper="Directory to use as root. Useful for monorepos." />
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile) @if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location" <x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
@@ -419,6 +443,7 @@
@script @script
<script> <script>
$wire.$on('loadCompose', (isInit = true) => { $wire.$on('loadCompose', (isInit = true) => {
// Only load compose file if user has permission (this event should only be dispatched when authorized)
$wire.initLoadingCompose = true; $wire.initLoadingCompose = true;
$wire.loadComposeFile(isInit); $wire.loadComposeFile(isInit);
}); });