This commit is contained in:
Andras Bacsai
2025-04-23 13:22:01 +02:00
parent 0dceb40a6e
commit 78ef80f800
9 changed files with 257 additions and 53 deletions

View File

@@ -45,6 +45,7 @@ class ApplicationsController extends Controller
'private_key_id', 'private_key_id',
'value', 'value',
'real_value', 'real_value',
'http_basic_auth_password',
]); ]);
} }
@@ -183,6 +184,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
], ],
) )
), ),
@@ -299,6 +303,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
], ],
) )
), ),
@@ -415,6 +422,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
], ],
) )
), ),
@@ -515,6 +525,9 @@ class ApplicationsController extends Controller
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
], ],
) )
), ),
@@ -612,6 +625,9 @@ class ApplicationsController extends Controller
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
], ],
) )
), ),
@@ -711,7 +727,6 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type) private function create_application(Request $request, $type)
{ {
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration'];
$teamId = getTeamIdFromToken(); $teamId = getTeamIdFromToken();
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
@@ -721,6 +736,8 @@ class ApplicationsController extends Controller
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; return $return;
} }
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password'];
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
'name' => 'string|max:255', 'name' => 'string|max:255',
'description' => 'string|nullable', 'description' => 'string|nullable',
@@ -729,6 +746,9 @@ class ApplicationsController extends Controller
'environment_uuid' => 'string|nullable', 'environment_uuid' => 'string|nullable',
'server_uuid' => 'string|required', 'server_uuid' => 'string|required',
'destination_uuid' => 'string', 'destination_uuid' => 'string',
'is_http_basic_auth_enabled' => 'boolean',
'http_basic_auth_username' => 'string|nullable',
'http_basic_auth_password' => 'string|nullable',
]); ]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields); $extraFields = array_diff(array_keys($request->all()), $allowedFields);
@@ -1758,25 +1778,19 @@ class ApplicationsController extends Controller
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
if ($request->collect()->count() == 0) {
return response()->json([
'message' => 'Invalid request.',
], 400);
}
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; return $return;
} }
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) { if (! $application) {
return response()->json([ return response()->json([
'message' => 'Application not found', 'message' => 'Application not found',
], 404); ], 404);
} }
$server = $application->destination->server; $server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration']; $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password'];
$validationRules = [ $validationRules = [
'name' => 'string|max:255', 'name' => 'string|max:255',
@@ -1789,6 +1803,9 @@ class ApplicationsController extends Controller
'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_start_command' => 'string|nullable',
'docker_compose_custom_build_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable',
'custom_nginx_configuration' => 'string|nullable', 'custom_nginx_configuration' => 'string|nullable',
'is_http_basic_auth_enabled' => 'boolean|nullable',
'http_basic_auth_username' => 'string',
'http_basic_auth_password' => 'string',
]; ];
$validationRules = array_merge(sharedDataApplications(), $validationRules); $validationRules = array_merge(sharedDataApplications(), $validationRules);
$validator = customApiValidator($request->all(), $validationRules); $validator = customApiValidator($request->all(), $validationRules);
@@ -1844,6 +1861,29 @@ class ApplicationsController extends Controller
'errors' => $errors, 'errors' => $errors,
], 422); ], 422);
} }
if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) {
if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) {
$validationErrors = [];
if (blank($request->http_basic_auth_username)) {
$validationErrors['http_basic_auth_username'] = 'The http_basic_auth_username is required.';
}
if (blank($request->http_basic_auth_password)) {
$validationErrors['http_basic_auth_password'] = 'The http_basic_auth_password is required.';
}
if (count($validationErrors) > 0) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $validationErrors,
], 422);
}
}
}
if ($request->has('is_http_basic_auth_enabled') && $application->is_container_label_readonly_enabled === false) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
$domains = $request->domains; $domains = $request->domains;
$requestHasDomains = $request->has('domains'); $requestHasDomains = $request->has('domains');
if ($requestHasDomains && $server->isProxyShouldRun()) { if ($requestHasDomains && $server->isProxyShouldRun()) {

View File

@@ -92,11 +92,11 @@ class General extends Component
'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required', 'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.settings.is_preserve_repository_enabled' => 'boolean|required',
'application.is_http_basic_auth_enabled' => 'boolean|required',
'application.http_basic_auth_username' => 'string|nullable',
'application.http_basic_auth_password' => 'string|nullable',
'application.watch_paths' => 'nullable', 'application.watch_paths' => 'nullable',
'application.redirect' => 'string|required', 'application.redirect' => 'string|required',
'application.http_basic_auth_enabled' => 'boolean|required',
'application.http_basic_auth_username' => 'nullable',
'application.http_basic_auth_password' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -181,6 +181,9 @@ class General extends Component
if ($this->application->settings->isDirty('is_spa')) { if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
} }
if ($this->application->isDirty('is_http_basic_auth_enabled')) {
$this->application->save();
}
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->application->refresh(); $this->application->refresh();

View File

@@ -103,7 +103,7 @@ use Visus\Cuid2\Cuid2;
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'],
'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
] ]
@@ -119,7 +119,10 @@ class Application extends BaseModel
protected $appends = ['server_status']; protected $appends = ['server_status'];
protected $casts = ['custom_network_aliases' => 'array']; protected $casts = [
'custom_network_aliases' => 'array',
'http_basic_auth_password' => 'encrypted',
];
public function customNetworkAliases(): Attribute public function customNetworkAliases(): Attribute
{ {

View File

@@ -297,7 +297,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
return $payload; return $payload;
} }
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
{ {
$labels = collect([]); $labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -306,7 +306,8 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
$labels->push("caddy_ingress_network={$network}"); $labels->push("caddy_ingress_network={$network}");
} }
$http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
$hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
foreach ($domains as $loop => $domain) { foreach ($domains as $loop => $domain) {
$url = Url::fromString($domain); $url = Url::fromString($domain);
@@ -344,28 +345,27 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
$labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}");
} }
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\"");
$labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\"");
} }
} }
return $labels->sort(); return $labels->sort();
} }
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
{ {
$labels = collect([]); $labels = collect([]);
$labels->push('traefik.enable=true'); $labels->push('traefik.enable=true');
$labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); $labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
$http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
$http_basic_auth_label = "http-basic-auth-{$uuid}"; $http_basic_auth_label = "http-basic-auth-{$uuid}";
$hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}");
$labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}");
} }
$middlewares_from_labels = collect([]); $middlewares_from_labels = collect([]);
@@ -453,7 +453,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $middlewares->push($to_www_name);
} }
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label); $middlewares->push($http_basic_auth_label);
} }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
@@ -479,7 +479,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $middlewares->push($to_www_name);
} }
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label); $middlewares->push($http_basic_auth_label);
} }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
@@ -531,7 +531,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $middlewares->push($to_www_name);
} }
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label); $middlewares->push($http_basic_auth_label);
} }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
@@ -557,7 +557,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $middlewares->push($to_www_name);
} }
if ($http_basic_auth_enabled) { if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label); $middlewares->push($http_basic_auth_label);
} }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
@@ -604,7 +604,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
redirect_direction: $application->redirect, redirect_direction: $application->redirect,
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -619,7 +619,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
redirect_direction: $application->redirect, redirect_direction: $application->redirect,
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -634,7 +634,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
redirect_direction: $application->redirect, redirect_direction: $application->redirect,
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -647,7 +647,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
redirect_direction: $application->redirect, redirect_direction: $application->redirect,
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -670,7 +670,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -684,7 +684,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -698,7 +698,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -710,7 +710,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(),
http_basic_auth_enabled: $application->http_basic_auth_enabled, is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password, http_basic_auth_password: $application->http_basic_auth_password,
)); ));
@@ -779,12 +779,15 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
$options[$option] = array_unique($options[$option]); $options[$option] = array_unique($options[$option]);
} }
if ($option === '--hostname') { if ($option === '--hostname') {
$regexForParsingHostname = '/hostname=([^\s]+)/'; // Match --hostname=value or --hostname value
$regexForParsingHostname = '/--hostname(?:=|\s+)([^\s]+)/';
preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches); preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches);
$value = $hostname_matches[1] ?? null; $value = $hostname_matches[1] ?? null;
if ($value) {
$options[$option][] = $value; $options[$option][] = $value;
$options[$option] = array_unique($options[$option]); $options[$option] = array_unique($options[$option]);
} }
}
if (isset($match[2]) && $match[2] !== '') { if (isset($match[2]) && $match[2] !== '') {
$value = $match[2]; $value = $match[2];
$options[$option][] = $value; $options[$option][] = $value;

View File

@@ -12,7 +12,7 @@ return new class extends Migration
public function up(): void public function up(): void
{ {
Schema::table('applications', function (Blueprint $table) { Schema::table('applications', function (Blueprint $table) {
$table->boolean('http_basic_auth_enabled')->default(false); $table->boolean('is_http_basic_auth_enabled')->default(false);
$table->string('http_basic_auth_username')->nullable(true)->default(null); $table->string('http_basic_auth_username')->nullable(true)->default(null);
$table->string('http_basic_auth_password')->nullable(true)->default(null); $table->string('http_basic_auth_password')->nullable(true)->default(null);
}); });
@@ -24,7 +24,7 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
Schema::table('applications', function (Blueprint $table) { Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('http_basic_auth_enabled'); $table->dropColumn('is_http_basic_auth_enabled');
$table->dropColumn('http_basic_auth_username'); $table->dropColumn('http_basic_auth_username');
$table->dropColumn('http_basic_auth_password'); $table->dropColumn('http_basic_auth_password');
}); });

View File

@@ -339,6 +339,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "description": "Use build server."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
},
"http_basic_auth_username": {
"type": "string",
"nullable": true,
"description": "Username for HTTP Basic Authentication"
},
"http_basic_auth_password": {
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
} }
}, },
"type": "object" "type": "object"
@@ -673,6 +687,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "description": "Use build server."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
},
"http_basic_auth_username": {
"type": "string",
"nullable": true,
"description": "Username for HTTP Basic Authentication"
},
"http_basic_auth_password": {
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
} }
}, },
"type": "object" "type": "object"
@@ -1007,6 +1035,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "description": "Use build server."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
},
"http_basic_auth_username": {
"type": "string",
"nullable": true,
"description": "Username for HTTP Basic Authentication"
},
"http_basic_auth_password": {
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
} }
}, },
"type": "object" "type": "object"
@@ -1270,6 +1312,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "description": "Use build server."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
},
"http_basic_auth_username": {
"type": "string",
"nullable": true,
"description": "Username for HTTP Basic Authentication"
},
"http_basic_auth_password": {
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
} }
}, },
"type": "object" "type": "object"
@@ -1516,6 +1572,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "description": "Use build server."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
},
"http_basic_auth_username": {
"type": "string",
"nullable": true,
"description": "Username for HTTP Basic Authentication"
},
"http_basic_auth_password": {
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
} }
}, },
"type": "object" "type": "object"
@@ -7554,7 +7624,7 @@
"nullable": true, "nullable": true,
"description": "Custom Nginx configuration base64 encoded." "description": "Custom Nginx configuration base64 encoded."
}, },
"http_basic_auth_enabled": { "is_http_basic_auth_enabled": {
"type": "boolean", "type": "boolean",
"description": "HTTP Basic Authentication enabled." "description": "HTTP Basic Authentication enabled."
}, },

View File

@@ -248,6 +248,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' description: 'Use build server.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username:
type: string
nullable: true
description: 'Username for HTTP Basic Authentication'
http_basic_auth_password:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
type: object type: object
responses: responses:
'201': '201':
@@ -487,6 +498,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' description: 'Use build server.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username:
type: string
nullable: true
description: 'Username for HTTP Basic Authentication'
http_basic_auth_password:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
type: object type: object
responses: responses:
'201': '201':
@@ -726,6 +748,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' description: 'Use build server.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username:
type: string
nullable: true
description: 'Username for HTTP Basic Authentication'
http_basic_auth_password:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
type: object type: object
responses: responses:
'201': '201':
@@ -912,6 +945,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' description: 'Use build server.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username:
type: string
nullable: true
description: 'Username for HTTP Basic Authentication'
http_basic_auth_password:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
type: object type: object
responses: responses:
'201': '201':
@@ -1089,6 +1133,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' description: 'Use build server.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username:
type: string
nullable: true
description: 'Username for HTTP Basic Authentication'
http_basic_auth_password:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
type: object type: object
responses: responses:
'201': '201':
@@ -5042,7 +5097,7 @@ components:
type: string type: string
nullable: true nullable: true
description: 'Custom Nginx configuration base64 encoded.' description: 'Custom Nginx configuration base64 encoded.'
http_basic_auth_enabled: is_http_basic_auth_enabled:
type: boolean type: boolean
description: 'HTTP Basic Authentication enabled.' description: 'HTTP Basic Authentication enabled.'
http_basic_auth_username: http_basic_auth_username:

View File

@@ -350,16 +350,18 @@
</div> </div>
<h3 class="pt-8">HTTP Basic Authentication</h3> <h3 class="pt-8">HTTP Basic Authentication</h3>
<div x-data="{ enabled: {{ $application->http_basic_auth_enabled ? 'true' : 'false' }} }"> <div>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." <x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="application.http_basic_auth_enabled" x-model="enabled" /> label="Enable" id="application.is_http_basic_auth_enabled" />
</div> </div>
@if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2" x-show="enabled"> <div class="flex gap-2 py-2">
<x-forms.input id="application.http_basic_auth_username" label="Username" /> <x-forms.input id="application.http_basic_auth_username" label="Username" required />
<x-forms.input id="application.http_basic_auth_password" type="password" label="Password" /> <x-forms.input id="application.http_basic_auth_password" type="password" label="Password"
required />
</div> </div>
@endif
</div> </div>
@if ($application->settings->is_container_label_readonly_enabled) @if ($application->settings->is_container_label_readonly_enabled)

View File

@@ -1,11 +1,39 @@
<?php <?php
test('Hostname', function () {
$input = '--hostname=test';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'test',
]);
});
test('HostnameWithoutEqualSign', function () {
$input = '--hostname test';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'test',
]);
});
test('HostnameWithoutEqualSignAndHyphens', function () {
$input = '--hostname my-super-host';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'my-super-host',
]);
});
test('HostnameWithHyphens', function () {
$input = '--hostname=my-super-host';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'my-super-host',
]);
});
test('ConvertCapAdd', function () { test('ConvertCapAdd', function () {
$input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN --hostname=my-super-host'; $input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN';
$output = convertDockerRunToCompose($input); $output = convertDockerRunToCompose($input);
expect($output)->toBe([ expect($output)->toBe([
'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'], 'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'],
'hostname' => 'my-super-host',
]); ]);
}); });