From f14a2862d80c6ccb6a34d20cb21898edf8554740 Mon Sep 17 00:00:00 2001 From: naterfute Date: Wed, 4 Jun 2025 17:32:55 -0700 Subject: [PATCH] feat(captcha): Remove Recaptcha support in favor of others - Remove ReCaptcha support and all related files - Adds support for the following captchas - Cloudflare Turnstile - Hcaptcha - Friendly Captcha - Implement new captcha middleware and controller - Add frontend components for new captcha options - Update settings configuration and views Closes #169 --- .../Repository/RepositoryInterface.php | 214 +++--- .../Admin/Settings/AdvancedController.php | 78 +- .../Admin/Settings/CaptchaController.php | 109 +++ .../Admin/Settings/IndexController.php | 76 +- .../Api/Client/Servers/SettingsController.php | 207 +++--- app/Http/Kernel.php | 122 ++-- app/Http/Middleware/VerifyCaptcha.php | 207 ++++++ app/Http/Middleware/VerifyReCaptcha.php | 72 -- .../Settings/AdvancedSettingsFormRequest.php | 74 +- .../Settings/CaptchaSettingsFormRequest.php | 59 ++ app/Http/ViewComposers/AssetComposer.php | 42 +- app/Providers/SettingsServiceProvider.php | 185 ++--- composer.lock | 666 +++++++++--------- config/app.php | 438 ++++++------ config/captcha.php | 53 ++ config/cors.php | 90 +-- config/recaptcha.php | 31 - package.json | 9 +- pnpm-lock.yaml | 94 +++ resources/scripts/api/auth/login.ts | 87 ++- resources/scripts/components/Captcha.tsx | 57 ++ .../scripts/components/FriendlyCaptcha.tsx | 48 ++ .../components/auth/LoginContainer.tsx | 220 ++++-- resources/scripts/state/settings.ts | 27 +- resources/views/admin/index.blade.php | 50 +- .../views/admin/nodes/view/settings.blade.php | 605 +++++++++------- .../views/admin/settings/advanced.blade.php | 48 -- .../views/admin/settings/captcha.blade.php | 246 +++++-- .../views/admin/settings/index.blade.php | 6 +- resources/views/admin/settings/mail.blade.php | 371 +++++----- resources/views/layouts/admin.blade.php | 26 +- .../partials/admin/settings/nav.blade.php | 1 + routes/admin.php | 4 + routes/auth.php | 8 +- 34 files changed, 2750 insertions(+), 1880 deletions(-) create mode 100644 app/Http/Controllers/Admin/Settings/CaptchaController.php create mode 100644 app/Http/Middleware/VerifyCaptcha.php delete mode 100644 app/Http/Middleware/VerifyReCaptcha.php create mode 100644 app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php create mode 100644 config/captcha.php delete mode 100644 config/recaptcha.php create mode 100644 resources/scripts/components/Captcha.tsx create mode 100644 resources/scripts/components/FriendlyCaptcha.tsx diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php index 5dc0c6655..975e7e1c9 100644 --- a/app/Contracts/Repository/RepositoryInterface.php +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -9,133 +9,133 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator; interface RepositoryInterface { - /** - * Return an identifier or Model object to be used by the repository. - */ - public function model(): string; + /** + * Return an identifier or Model object to be used by the repository. + */ + public function model(): string; - /** - * Return the model being used for this repository instance. - */ - public function getModel(): Model; + /** + * Return the model being used for this repository instance. + */ + public function getModel(): Model; - /** - * Returns an instance of a query builder. - */ - public function getBuilder(): Builder; + /** + * Returns an instance of a query builder. + */ + public function getBuilder(): Builder; - /** - * Returns the columns to be selected or returned by the query. - */ - public function getColumns(): array; + /** + * Returns the columns to be selected or returned by the query. + */ + public function getColumns(): array; - /** - * An array of columns to filter the response by. - */ - public function setColumns(array|string $columns = ['*']): self; + /** + * An array of columns to filter the response by. + */ + public function setColumns(array|string $columns = ['*']): self; - /** - * Stop repository update functions from returning a fresh - * model when changes are committed. - */ - public function withoutFreshModel(): self; + /** + * Stop repository update functions from returning a fresh + * model when changes are committed. + */ + public function withoutFreshModel(): self; - /** - * Return a fresh model with a repository updates a model. - */ - public function withFreshModel(): self; + /** + * Return a fresh model with a repository updates a model. + */ + public function withFreshModel(): self; - /** - * Set whether the repository should return a fresh model - * when changes are committed. - */ - public function setFreshModel(bool $fresh = true): self; + /** + * Set whether the repository should return a fresh model + * when changes are committed. + */ + public function setFreshModel(bool $fresh = true): self; - /** - * Create a new model instance and persist it to the database. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function create(array $fields, bool $validate = true, bool $force = false): mixed; + /** + * Create a new model instance and persist it to the database. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(array $fields, bool $validate = true, bool $force = false): mixed; - /** - * Find a model that has the specific ID passed. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function find(int $id): mixed; + /** + * Find a model that has the specific ID passed. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function find(int $id): mixed; - /** - * Find a model matching an array of where clauses. - */ - public function findWhere(array $fields): Collection; + /** + * Find a model matching an array of where clauses. + */ + public function findWhere(array $fields): Collection; - /** - * Find and return the first matching instance for the given fields. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function findFirstWhere(array $fields): mixed; + /** + * Find and return the first matching instance for the given fields. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function findFirstWhere(array $fields): mixed; - /** - * Return a count of records matching the passed arguments. - */ - public function findCountWhere(array $fields): int; + /** + * Return a count of records matching the passed arguments. + */ + public function findCountWhere(array $fields): int; - /** - * Delete a given record from the database. - */ - public function delete(int $id): int; + /** + * Delete a given record from the database. + */ + public function delete(int $id): int; - /** - * Delete records matching the given attributes. - */ - public function deleteWhere(array $attributes): int; + /** + * Delete records matching the given attributes. + */ + public function deleteWhere(array $attributes): int; - /** - * Update a given ID with the passed array of fields. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(int $id, array $fields, bool $validate = true, bool $force = false): mixed; + /** + * Update a given ID with the passed array of fields. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(int $id, array $fields, bool $validate = true, bool $force = false): mixed; - /** - * Perform a mass update where matching records are updated using whereIn. - * This does not perform any model data validation. - */ - public function updateWhereIn(string $column, array $values, array $fields): int; + /** + * Perform a mass update where matching records are updated using whereIn. + * This does not perform any model data validation. + */ + public function updateWhereIn(string $column, array $values, array $fields): int; - /** - * Update a record if it exists in the database, otherwise create it. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false): mixed; + /** + * Update a record if it exists in the database, otherwise create it. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function updateOrCreate(array $where, array $fields, bool $validate = true, bool $force = false): mixed; - /** - * Return all records associated with the given model. - */ - public function all(): Collection; + /** + * Return all records associated with the given model. + */ + public function all(): Collection; - /** - * Return a paginated result set using a search term if set on the repository. - */ - public function paginated(int $perPage): LengthAwarePaginator; + /** + * Return a paginated result set using a search term if set on the repository. + */ + public function paginated(int $perPage): LengthAwarePaginator; - /** - * Insert a single or multiple records into the database at once skipping - * validation and mass assignment checking. - */ - public function insert(array $data): bool; + /** + * Insert a single or multiple records into the database at once skipping + * validation and mass assignment checking. + */ + public function insert(array $data): bool; - /** - * Insert multiple records into the database and ignore duplicates. - */ - public function insertIgnore(array $values): bool; + /** + * Insert multiple records into the database and ignore duplicates. + */ + public function insertIgnore(array $values): bool; - /** - * Get the amount of entries in the database. - */ - public function count(): int; + /** + * Get the amount of entries in the database. + */ + public function count(): int; } diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index 9033e7b92..de58f3cf3 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -14,49 +14,41 @@ use Pterodactyl\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest; class AdvancedController extends Controller { - /** - * AdvancedController constructor. - */ - public function __construct( - private AlertsMessageBag $alert, - private ConfigRepository $config, - private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private ViewFactory $view, - ) { + /** + * AdvancedController constructor. + */ + public function __construct( + private AlertsMessageBag $alert, + private ConfigRepository $config, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private ViewFactory $view, + ) { + } + + /** + * Render advanced Panel settings UI. + */ + public function index(): View + { + return $this->view->make('admin.settings.advanced'); + } + + /** + * Update advanced settings. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(AdvancedSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); } - /** - * Render advanced Panel settings UI. - */ - public function index(): View - { - $showRecaptchaWarning = false; - if ( - $this->config->get('recaptcha._shipped_secret_key') === $this->config->get('recaptcha.secret_key') - || $this->config->get('recaptcha._shipped_website_key') === $this->config->get('recaptcha.website_key') - ) { - $showRecaptchaWarning = true; - } + $this->kernel->call('queue:restart'); + $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - return $this->view->make('admin.settings.advanced', [ - 'showRecaptchaWarning' => $showRecaptchaWarning, - ]); - } - - /** - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(AdvancedSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - $this->settings->set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings.advanced'); - } -} + return redirect()->route('admin.settings.advanced'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/Settings/CaptchaController.php b/app/Http/Controllers/Admin/Settings/CaptchaController.php new file mode 100644 index 000000000..84a040ca8 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/CaptchaController.php @@ -0,0 +1,109 @@ +alert = $alert; + $this->settings = $settings; + } + + /** + * Render CAPTCHA settings page. + */ + public function index(): View + { + return view('admin.settings.captcha', [ + 'providers' => [ + 'none' => 'Disabled', + 'hcaptcha' => 'hCaptcha', + 'mcaptcha' => 'mCaptcha', + 'turnstile' => 'Cloudflare Turnstile', + 'friendly' => 'Friendly Captcha', + 'recaptcha' => 'Recaptcha V3' + ], + 'current' => [ + 'driver' => $this->settings->get('settings::captcha:driver', 'none'), + 'hcaptcha' => [ + 'site_key' => $this->settings->get('settings::captcha:hcaptcha:site_key', ''), + 'secret_key' => $this->settings->get('settings::captcha:hcaptcha:secret_key', ''), + ], + 'mcaptcha' => [ + 'site_key' => $this->settings->get('settings::captcha:mcaptcha:site_key', ''), + 'secret_key' => $this->settings->get('settings::captcha:mcaptcha:secret_key', ''), + 'endpoint' => $this->settings->get('settings::captcha:mcaptcha:endpoint', ''), + ], + 'turnstile' => [ + 'site_key' => $this->settings->get('settings::captcha:turnstile:site_key', ''), + 'secret_key' => $this->settings->get('settings::captcha:turnstile:secret_key', ''), + ], + 'friendly' => [ + 'site_key' => $this->settings->get('settings::captcha:friendly:site_key', ''), + 'secret_key' => $this->settings->get('settings::captcha:friendly:secret_key', ''), + ], + 'recaptcha' => [ + 'site_key' => $this->settings->get('settings::captcha:recaptcha:site_key', ''), + 'secret_key' => $this->settings->get('settings::captcha:friendly:secret_key', ''), + ], + ], + ]); + } + + /** + * Handle CAPTCHA settings update. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(CaptchaSettingsFormRequest $request): RedirectResponse + { + $data = $request->validated(); + $driver = $data['driver']; + + // Save the driver + $this->settings->set('settings::captcha:driver', $driver); + + // Clear all provider settings first + $providers = ['hcaptcha', 'mcaptcha', 'turnstile', 'friendly', 'recaptcha']; + foreach ($providers as $provider) { + $this->settings->set("settings::captcha:{$provider}:site_key", ''); + $this->settings->set("settings::captcha:{$provider}:secret_key", ''); + if ($provider === 'mcaptcha') { + $this->settings->set("settings::captcha:{$provider}:endpoint", ''); + } + } + + // Save the selected provider's config if enabled + if ($driver !== 'none' && isset($data[$driver])) { + foreach ($data[$driver] as $key => $value) { + $this->settings->set("settings::captcha:{$driver}:{$key}", $value); + } + } + + $this->alert->success('CAPTCHA settings have been updated successfully.')->flash(); + return redirect()->route('admin.settings.captcha'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index f45a46404..b7998adc7 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -15,46 +15,46 @@ use Pterodactyl\Http\Requests\Admin\Settings\BaseSettingsFormRequest; class IndexController extends Controller { - use AvailableLanguages; + use AvailableLanguages; - /** - * IndexController constructor. - */ - public function __construct( - private AlertsMessageBag $alert, - private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private SoftwareVersionService $versionService, - private ViewFactory $view, - ) { + /** + * IndexController constructor. + */ + public function __construct( + private AlertsMessageBag $alert, + private Kernel $kernel, + private SettingsRepositoryInterface $settings, + private SoftwareVersionService $versionService, + private ViewFactory $view, + ) { + } + + /** + * Render the UI for basic Panel settings. + */ + public function index(): View + { + return $this->view->make('admin.settings.index', [ + 'version' => $this->versionService, + 'languages' => $this->getAvailableLanguages(true), + ]); + } + + /** + * Handle settings update. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(BaseSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); } - /** - * Render the UI for basic Panel settings. - */ - public function index(): View - { - return $this->view->make('admin.settings.index', [ - 'version' => $this->versionService, - 'languages' => $this->getAvailableLanguages(true), - ]); - } + $this->kernel->call('queue:restart'); + $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - /** - * Handle settings update. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(BaseSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - $this->settings->set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings'); - } + return redirect()->route('admin.settings'); + } } diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index f662c1c5d..93243bf34 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -17,121 +17,122 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest class SettingsController extends ClientApiController { - /** - * SettingsController constructor. - */ - public function __construct( - private ServerRepository $repository, - private ReinstallServerService $reinstallServerService, - ) { - parent::__construct(); + /** + * SettingsController constructor. + */ + public function __construct( + private ServerRepository $repository, + private ReinstallServerService $reinstallServerService, + ) { + parent::__construct(); + } + + /** + * Renames a server. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function rename(RenameServerRequest $request, Server $server): JsonResponse + { + $name = $request->input('name'); + $description = $request->has('description') ? (string) $request->input('description') : $server->description; + $this->repository->update($server->id, [ + 'name' => $name, + 'description' => $description, + ]); + + if ($server->name !== $name) { + Activity::event('server:settings.rename') + ->property(['old' => $server->name, 'new' => $name]) + ->log(); } - /** - * Renames a server. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function rename(RenameServerRequest $request, Server $server): JsonResponse - { - $name = $request->input('name'); - $description = $request->has('description') ? (string) $request->input('description') : $server->description; - $this->repository->update($server->id, [ - 'name' => $name, - 'description' => $description, - ]); - - if ($server->name !== $name) { - Activity::event('server:settings.rename') - ->property(['old' => $server->name, 'new' => $name]) - ->log(); - } - - if ($server->description !== $description) { - Activity::event('server:settings.description') - ->property(['old' => $server->description, 'new' => $description]) - ->log(); - } - - return new JsonResponse([], Response::HTTP_NO_CONTENT); + if ($server->description !== $description) { + Activity::event('server:settings.description') + ->property(['old' => $server->description, 'new' => $description]) + ->log(); } - /** - * Reinstalls the server on the daemon. - * - * @throws \Throwable - */ - public function reinstall(ReinstallServerRequest $request, Server $server): JsonResponse - { - $this->reinstallServerService->handle($server); + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } - Activity::event('server:reinstall')->log(); + /** + * Reinstalls the server on the daemon. + * + * @throws \Throwable + */ + public function reinstall(ReinstallServerRequest $request, Server $server): JsonResponse + { + $this->reinstallServerService->handle($server); - return new JsonResponse([], Response::HTTP_ACCEPTED); + Activity::event('server:reinstall')->log(); + + return new JsonResponse([], Response::HTTP_ACCEPTED); + } + + /** + * Changes the Docker image in use by the server. + * + * @throws \Throwable + */ + public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse + { + if (!in_array($request->input('docker_image'), array_values($server->egg->docker_images))) { + throw new BadRequestHttpException('The requested Docker image is not allowed for this server.'); } - /** - * Changes the Docker image in use by the server. - * - * @throws \Throwable - */ - public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse - { - if (!in_array($request->input('docker_image'), array_values($server->egg->docker_images))) { - throw new BadRequestHttpException('The requested Docker image is not allowed for this server.'); - } + $original = $server->image; + $server->forceFill(['image' => $request->input('docker_image')])->saveOrFail(); - $original = $server->image; - $server->forceFill(['image' => $request->input('docker_image')])->saveOrFail(); - - if ($original !== $server->image) { - Activity::event('server:startup.image') - ->property(['old' => $original, 'new' => $request->input('docker_image')]) - ->log(); - } - - return new JsonResponse([], Response::HTTP_NO_CONTENT); + if ($original !== $server->image) { + Activity::event('server:startup.image') + ->property(['old' => $original, 'new' => $request->input('docker_image')]) + ->log(); } - /** - * Reset Startup Command - */ - private function resetStartupCommand(Server $server): JsonResponse - { - $server->startup = $server->egg->startup; - $server->save(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } - return new JsonResponse([], Response::HTTP_NO_CONTENT); + /** + * Reset Startup Command + */ + private function resetStartupCommand(Server $server): JsonResponse + { + $server->startup = $server->egg->startup; + $server->save(); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Changes the egg for a server. + * + * @throws \Throwable + */ + public function changeEgg(SetEggRequest $request, Server $server): JsonResponse + { + $eggId = $request->input('egg_id'); + $nestId = $request->input('nest_id'); + $originalEggId = $server->egg_id; + $originalNestId = $server->nest_id; + + // Check if the new Egg and Nest IDs are different from the current ones + if ($originalEggId !== $eggId || $originalNestId !== $nestId) { + // Update the server's Egg and Nest IDs + $server->egg_id = $eggId; + $server->nest_id = $nestId; + $server->save(); + + // Log an activity event for the Egg change + Activity::event('server:settings.egg') + ->property(['original_egg_id' => $originalEggId, 'new_egg_id' => $eggId, 'original_nest_id' => $originalNestId, 'new_nest_id' => $nestId]) + ->log(); + + // Reset the server's startup command + $this->resetStartupCommand($server); } - /** - * Changes the egg for a server. - * - * @throws \Throwable - */ - public function changeEgg(SetEggRequest $request, Server $server): JsonResponse { - $eggId = $request->input('egg_id'); - $nestId = $request->input('nest_id'); - $originalEggId = $server->egg_id; - $originalNestId = $server->nest_id; - - // Check if the new Egg and Nest IDs are different from the current ones - if ($originalEggId !== $eggId || $originalNestId !== $nestId) { - // Update the server's Egg and Nest IDs - $server->egg_id = $eggId; - $server->nest_id = $nestId; - $server->save(); - - // Log an activity event for the Egg change - Activity::event('server:settings.egg') - ->property(['original_egg_id' => $originalEggId, 'new_egg_id' => $eggId, 'original_nest_id' => $originalNestId, 'new_nest_id' => $nestId]) - ->log(); - - // Reset the server's startup command - $this->resetStartupCommand($server); - } - - return new JsonResponse([], Response::HTTP_NO_CONTENT); - } + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b7085d9ed..ba1eaeabc 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,7 +11,7 @@ use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; -use Pterodactyl\Http\Middleware\VerifyReCaptcha; +use Pterodactyl\Http\Middleware\VerifyCaptcha; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -36,66 +36,66 @@ use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel { - /** - * The application's global HTTP middleware stack. - */ - protected $middleware = [ - TrustProxies::class, - HandleCors::class, - PreventRequestsDuringMaintenance::class, - ValidatePostSize::class, - TrimStrings::class, - ConvertEmptyStringsToNull::class, - ]; + /** + * The application's global HTTP middleware stack. + */ + protected $middleware = [ + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + ]; - /** - * The application's route middleware groups. - */ - protected $middlewareGroups = [ - 'web' => [ - EncryptCookies::class, - AddQueuedCookiesToResponse::class, - StartSession::class, - ShareErrorsFromSession::class, - VerifyCsrfToken::class, - SubstituteBindings::class, - LanguageMiddleware::class, - ], - 'api' => [ - EnsureStatefulRequests::class, - 'auth:sanctum', - IsValidJson::class, - TrackAPIKey::class, - RequireTwoFactorAuthentication::class, - AuthenticateIPAccess::class, - ], - 'application-api' => [ - SubstituteBindings::class, - AuthenticateApplicationUser::class, - ], - 'client-api' => [ - SubstituteClientBindings::class, - RequireClientApiKey::class, - ], - 'daemon' => [ - SubstituteBindings::class, - DaemonAuthenticate::class, - ], - ]; + /** + * The application's route middleware groups. + */ + protected $middlewareGroups = [ + 'web' => [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + LanguageMiddleware::class, + ], + 'api' => [ + EnsureStatefulRequests::class, + 'auth:sanctum', + IsValidJson::class, + TrackAPIKey::class, + RequireTwoFactorAuthentication::class, + AuthenticateIPAccess::class, + ], + 'application-api' => [ + SubstituteBindings::class, + AuthenticateApplicationUser::class, + ], + 'client-api' => [ + SubstituteClientBindings::class, + RequireClientApiKey::class, + ], + 'daemon' => [ + SubstituteBindings::class, + DaemonAuthenticate::class, + ], + ]; - /** - * The application's route middleware. - */ - protected $middlewareAliases = [ - 'auth' => Authenticate::class, - 'auth.basic' => AuthenticateWithBasicAuth::class, - 'auth.session' => AuthenticateSession::class, - 'guest' => RedirectIfAuthenticated::class, - 'csrf' => VerifyCsrfToken::class, - 'throttle' => ThrottleRequests::class, - 'can' => Authorize::class, - 'bindings' => SubstituteBindings::class, - 'recaptcha' => VerifyReCaptcha::class, - 'node.maintenance' => MaintenanceMiddleware::class, - ]; + /** + * The application's route middleware. + */ + protected $middlewareAliases = [ + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, + 'guest' => RedirectIfAuthenticated::class, + 'csrf' => VerifyCsrfToken::class, + 'throttle' => ThrottleRequests::class, + 'can' => Authorize::class, + 'bindings' => SubstituteBindings::class, + 'captcha' => VerifyCaptcha::class, + 'node.maintenance' => MaintenanceMiddleware::class, + ]; } diff --git a/app/Http/Middleware/VerifyCaptcha.php b/app/Http/Middleware/VerifyCaptcha.php new file mode 100644 index 000000000..80cc21479 --- /dev/null +++ b/app/Http/Middleware/VerifyCaptcha.php @@ -0,0 +1,207 @@ + 'https://challenges.cloudflare.com/turnstile/v0/siteverify', + 'hcaptcha' => 'https://hcaptcha.com/siteverify', + 'friendly' => 'https://api.friendlycaptcha.com/api/v1/siteverify', + 'mcaptcha' => 'https://mcaptcha.org/api/siteverify', + ]; + + private const PROVIDER_FIELDS = [ + 'turnstile' => 'cf-turnstile-response', + 'hcaptcha' => 'h-captcha-response', + 'friendly' => 'frc-captcha-response', + 'mcaptcha' => 'mcaptcha-response', + ]; + + + public function __construct( + private Dispatcher $dispatcher, + private Repository $config, + private Client $client + ) { + } + + public function handle(Request $request, \Closure $next): mixed + { + $driver = $this->config->get('captcha.driver'); + + if (!$this->shouldVerifyCaptcha($driver)) { + return $next($request); + } + + $fieldName = self::PROVIDER_FIELDS[$driver]; + $captchaResponse = $this->getCaptchaResponseFromRequest($request, $fieldName); + + + + if (empty($captchaResponse)) { + \Log::warning('CAPTCHA Middleware - Missing response token', [ + 'expected_field' => $fieldName, + 'available_fields' => array_keys($request->all()), + ]); + $this->logAndTriggerFailure($request, $driver, 'missing_response'); + throw new HttpException(400, 'Please complete the CAPTCHA challenge.'); + } + + try { + $result = $this->verifyWithProvider($driver, $captchaResponse, $request->ip()); + + if ($this->isResponseValid($result, $request, $driver)) { + return $next($request); + } + + $this->logAndTriggerFailure($request, $driver, 'verification_failed', $result); + throw new HttpException(400, 'CAPTCHA verification failed. Please try again.'); + + } catch (ClientExceptionInterface $e) { + $this->logAndTriggerFailure($request, $driver, 'service_error'); + \Log::error('CAPTCHA service error', ['error' => $e->getMessage()]); + throw new HttpException(503, 'CAPTCHA service unavailable. Please try again later.'); + } catch (\Exception $e) { + $this->logAndTriggerFailure($request, $driver, 'unexpected_error'); + \Log::error('CAPTCHA unexpected error', ['error' => $e->getMessage()]); + throw new HttpException(500, 'An unexpected error occurred during CAPTCHA verification.'); + } + } + + private function shouldVerifyCaptcha(?string $driver): bool + { + return $driver && array_key_exists($driver, self::PROVIDER_FIELDS); + } + + private function getCaptchaResponseFromRequest(Request $request, string $fieldName): ?string + { + + if ($request->isJson()) { + $data = $request->json()->all(); + return $data['captchaData'] ?? $data[$fieldName] ?? null; + } + + $response = $request->input($fieldName) ?? $request->input('captchaData'); + + if (empty($response) && in_array($request->method(), ['POST', 'PUT', 'PATCH'])) { + $rawInput = file_get_contents('php://input'); + if (!empty($rawInput)) { + parse_str($rawInput, $parsed); + $response = $parsed[$fieldName] ?? $parsed['captchaData'] ?? null; + } + } + + return $response; + } + + private function verifyWithProvider(string $driver, string $response, string $remoteIp): \stdClass + { + $secretKey = $this->config->get("captcha.{$driver}.secret_key"); + + if (empty($secretKey)) { + throw new \RuntimeException("No secret key configured for CAPTCHA driver: {$driver}"); + } + + $params = ['secret' => $secretKey]; + + if ($driver === 'turnstile') { + $params['response'] = $response; + $params['remoteip'] = $remoteIp; + } elseif ($driver === 'hcaptcha') { + $params['response'] = $response; + $params['remoteip'] = $remoteIp; + } elseif ($driver === 'friendly') { + $params['solution'] = $response; + $params['sitekey'] = $this->config->get("captcha.{$driver}.site_key"); + } + + + try { + $res = $this->client->post(self::PROVIDER_ENDPOINTS[$driver], [ + 'timeout' => $this->config->get('captcha.timeout', 5), + 'json' => $params, + ]); + + $body = $res->getBody()->getContents(); + $result = json_decode($body); + + if (json_last_error() !== JSON_ERROR_NONE) { + \Log::error('Invalid JSON response from CAPTCHA provider', [ + 'provider' => $driver, + 'response_body' => $body, + 'json_error' => json_last_error_msg() + ]); + throw new \RuntimeException("Invalid JSON response from {$driver} CAPTCHA provider"); + } + + return $result; + } catch (\Exception $e) { + \Log::error('CAPTCHA verification error', [ + 'provider' => $driver, + 'error' => $e->getMessage(), + 'response' => $e->getResponse() ? $e->getResponse()->getBody()->getContents() : null + ]); + throw $e; + } + } + + private function isResponseValid(\stdClass $result, Request $request, string $driver): bool + { + if (!($result->success ?? false)) { + + return false; + } + + switch ($driver) { + case 'turnstile': + if ($this->config->get('captcha.verify_domain', false)) { + $expectedHost = parse_url($request->url(), PHP_URL_HOST); + if (($result->hostname ?? null) !== $expectedHost) { + \Log::warning('Domain verification failed', [ + 'expected' => $expectedHost, + 'actual' => $result->hostname ?? 'null' + ]); + return false; + } + } + break; + } + + return true; + } + + private function logAndTriggerFailure( + Request $request, + string $driver, + string $reason, + ?\stdClass $result = null + ): void { + $errorCodes = $result->{'error-codes'} ?? []; + + \Log::warning("CAPTCHA verification failed", [ + 'driver' => $driver, + 'reason' => $reason, + 'ip' => $request->ip(), + 'path' => $request->path(), + 'method' => $request->method(), + 'error_codes' => $errorCodes, + ]); + + $this->dispatcher->dispatch(new FailedCaptcha( + $request->ip(), + $driver, + $reason, + $errorCodes + )); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php deleted file mode 100644 index ef251c333..000000000 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ /dev/null @@ -1,72 +0,0 @@ -config->get('recaptcha.enabled')) { - return $next($request); - } - - if ($request->filled('g-recaptcha-response')) { - $client = new Client(); - $res = $client->post($this->config->get('recaptcha.domain'), [ - 'form_params' => [ - 'secret' => $this->config->get('recaptcha.secret_key'), - 'response' => $request->input('g-recaptcha-response'), - ], - ]); - - if ($res->getStatusCode() === 200) { - $result = json_decode($res->getBody()); - - if ($result->success && (!$this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) { - return $next($request); - } - } - } - - $this->dispatcher->dispatch( - new FailedCaptcha( - $request->ip(), - !empty($result) ? ($result->hostname ?? null) : null - ) - ); - - throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.'); - } - - /** - * Determine if the response from the recaptcha servers was valid. - */ - private function isResponseVerified(\stdClass $result, Request $request): bool - { - if (!$this->config->get('recaptcha.verify_domain')) { - return false; - } - - $url = parse_url($request->url()); - - return $result->hostname === array_get($url, 'host'); - } -} diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index 17608d9f2..b3d6f3bb9 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -6,45 +6,39 @@ use Pterodactyl\Http\Requests\Admin\AdminFormRequest; class AdvancedSettingsFormRequest extends AdminFormRequest { - /** - * Return all the rules to apply to this request's data. - */ - public function rules(): array - { - return [ - 'recaptcha:enabled' => 'required|in:true,false', - 'recaptcha:secret_key' => 'required|string|max:191', - 'recaptcha:website_key' => 'required|string|max:191', - 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', - 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', - 'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false', - 'pterodactyl:client_features:allocations:range_start' => [ - 'nullable', - 'required_if:pterodactyl:client_features:allocations:enabled,true', - 'integer', - 'between:1024,65535', - ], - 'pterodactyl:client_features:allocations:range_end' => [ - 'nullable', - 'required_if:pterodactyl:client_features:allocations:enabled,true', - 'integer', - 'between:1024,65535', - 'gt:pterodactyl:client_features:allocations:range_start', - ], - ]; - } + /** + * Return all the rules to apply to this request's data. + */ + public function rules(): array + { + return [ + 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', + 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', + 'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false', + 'pterodactyl:client_features:allocations:range_start' => [ + 'nullable', + 'required_if:pterodactyl:client_features:allocations:enabled,true', + 'integer', + 'between:1024,65535', + ], + 'pterodactyl:client_features:allocations:range_end' => [ + 'nullable', + 'required_if:pterodactyl:client_features:allocations:enabled,true', + 'integer', + 'between:1024,65535', + 'gt:pterodactyl:client_features:allocations:range_start', + ], + ]; + } - public function attributes(): array - { - return [ - 'recaptcha:enabled' => 'reCAPTCHA Enabled', - 'recaptcha:secret_key' => 'reCAPTCHA Secret Key', - 'recaptcha:website_key' => 'reCAPTCHA Website Key', - 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', - 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', - 'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled', - 'pterodactyl:client_features:allocations:range_start' => 'Starting Port', - 'pterodactyl:client_features:allocations:range_end' => 'Ending Port', - ]; - } + public function attributes(): array + { + return [ + 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', + 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', + 'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled', + 'pterodactyl:client_features:allocations:range_start' => 'Starting Port', + 'pterodactyl:client_features:allocations:range_end' => 'Ending Port', + ]; + } } diff --git a/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php new file mode 100644 index 000000000..c5bbfb84e --- /dev/null +++ b/app/Http/Requests/Admin/Settings/CaptchaSettingsFormRequest.php @@ -0,0 +1,59 @@ + 'required|in:none,hcaptcha,mcaptcha,turnstile,friendly,recaptcha', + ]; + + // Only apply validation rules for the selected driver + $driver = $this->input('driver'); + if ($driver !== 'none') { + $rules[$driver] = 'required|array'; + + if ($driver === 'hcaptcha') { + $rules['hcaptcha.site_key'] = 'required|string'; + $rules['hcaptcha.secret_key'] = 'required|string'; + } elseif ($driver === 'mcaptcha') { + $rules['mcaptcha.site_key'] = 'required|string'; + $rules['mcaptcha.secret_key'] = 'required|string'; + $rules['mcaptcha.endpoint'] = 'required|url'; + } elseif ($driver === 'turnstile') { + $rules['turnstile.site_key'] = 'required|string'; + $rules['turnstile.secret_key'] = 'required|string'; + } elseif ($driver === 'friendly') { + $rules['friendly.site_key'] = 'required|string'; + $rules['friendly.secret_key'] = 'required|string'; + } elseif ($driver === 'recaptcha') { + $rules['recaptcha.site_key'] = 'required|string'; + $rules['recaptcha.secret_key'] = 'required|string'; + } + } + + return $rules; + } + + public function attributes(): array + { + return [ + 'hcaptcha.site_key' => 'hCaptcha Site Key', + 'hcaptcha.secret_key' => 'hCaptcha Secret Key', + 'mcaptcha.site_key' => 'mCaptcha Site Key', + 'mcaptcha.secret_key' => 'mCaptcha Secret Key', + 'mcaptcha.endpoint' => 'mCaptcha Endpoint', + 'turnstile.site_key' => 'Turnstile Site Key', + 'turnstile.secret_key' => 'Turnstile Secret Key', + 'proton.site_key' => 'Proton Site Key', + 'proton.secret_key' => 'Proton Secret Key', + 'friendly.site_key' => 'Friendly Site Key', + 'friendly.secret_key' => 'Friendly Secret Key', + 'recaptcha.site_key' => 'Recaptcha Site Key', + 'recaptcha.secret_key' => 'Recaptcha Secret Key', + ]; + } +} \ No newline at end of file diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index 2cd16c500..951226d73 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -7,18 +7,32 @@ use Pterodactyl\Services\Helpers\AssetHashService; class AssetComposer { - /** - * Provide access to the asset service in the views. - */ - public function compose(View $view): void - { - $view->with('siteConfiguration', [ - 'name' => config('app.name') ?? 'Pterodactyl', - 'locale' => config('app.locale') ?? 'en', - 'recaptcha' => [ - 'enabled' => config('recaptcha.enabled', false), - 'siteKey' => config('recaptcha.website_key') ?? '', - ], - ]); - } + /** + * Provide access to the asset service in the views. + */ + public function compose(View $view): void + { + $view->with('siteConfiguration', [ + 'name' => config('app.name') ?? 'Pyrodactyl', + 'locale' => config('app.locale') ?? 'en', + 'captcha' => [ + 'driver' => config('captcha.driver', 'none'), + 'turnstile' => [ + 'siteKey' => config('captcha.turnstile.site_key', '') + ], + 'hcaptcha' => [ + 'siteKey' => config('captcha.hcaptcha.site_key', '') + ], + 'mcaptcha' => [ + 'siteKey' => config('captcha.mcaptcha.site_key', '') + ], + 'friendly' => [ + 'siteKey' => config('captcha.friendly.site_key', '') + ], + 'recaptcha' => [ + 'siteKey' => config('captcha.recaptcha.site_key', '') + ], + ], + ]); + } } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index dbcca85ea..d30ab99fc 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -12,102 +12,111 @@ use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; class SettingsServiceProvider extends ServiceProvider { - /** - * An array of configuration keys to override with database values - * if they exist. - */ - protected array $keys = [ - 'app:name', - 'app:locale', - 'recaptcha:enabled', - 'recaptcha:secret_key', - 'recaptcha:website_key', - 'pterodactyl:guzzle:timeout', - 'pterodactyl:guzzle:connect_timeout', - 'pterodactyl:console:count', - 'pterodactyl:console:frequency', - 'pterodactyl:auth:2fa_required', - 'pterodactyl:client_features:allocations:enabled', - 'pterodactyl:client_features:allocations:range_start', - 'pterodactyl:client_features:allocations:range_end', - ]; + /** + * An array of configuration keys to override with database values + * if they exist. + */ + protected array $keys = [ + 'app:name', + 'app:locale', + 'captcha:driver', + 'captcha:hcaptcha:site_key', + 'captcha:hcaptcha:secret_key', + 'captcha:mcaptcha:site_key', + 'captcha:mcaptcha:secret_key', + 'captcha:mcaptcha:endpoint', + 'captcha:turnstile:site_key', + 'captcha:turnstile:secret_key', + 'captcha:friendly:site_key', + 'captcha:friendly:secret_key', + // existing mail keys, etc... + 'pterodactyl:guzzle:timeout', + 'pterodactyl:guzzle:connect_timeout', + 'pterodactyl:console:count', + 'pterodactyl:console:frequency', + 'pterodactyl:auth:2fa_required', + 'pterodactyl:client_features:allocations:enabled', + 'pterodactyl:client_features:allocations:range_start', + 'pterodactyl:client_features:allocations:range_end', + ]; - /** - * Keys specific to the mail driver that are only grabbed from the database - * when using the SMTP driver. - */ - protected array $emailKeys = [ - 'mail:mailers:smtp:host', - 'mail:mailers:smtp:port', - 'mail:mailers:smtp:encryption', - 'mail:mailers:smtp:username', - 'mail:mailers:smtp:password', - 'mail:from:address', - 'mail:from:name', - ]; - /** - * Keys that are encrypted and should be decrypted when set in the - * configuration array. - */ - protected static array $encrypted = [ - 'mail:mailers:smtp:password', - ]; + /** + * Keys specific to the mail driver that are only grabbed from the database + * when using the SMTP driver. + */ + protected array $emailKeys = [ + 'mail:mailers:smtp:host', + 'mail:mailers:smtp:port', + 'mail:mailers:smtp:encryption', + 'mail:mailers:smtp:username', + 'mail:mailers:smtp:password', + 'mail:from:address', + 'mail:from:name', + ]; - /** - * Boot the service provider. - */ - public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void - { - // Only set the email driver settings from the database if we - // are configured using SMTP as the driver. - if ($config->get('mail.default') === 'smtp') { - $this->keys = array_merge($this->keys, $this->emailKeys); - } + /** + * Keys that are encrypted and should be decrypted when set in the + * configuration array. + */ + protected static array $encrypted = [ + 'mail:mailers:smtp:password', + ]; + /** + * Boot the service provider. + */ + public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void + { + // Only set the email driver settings from the database if we + // are configured using SMTP as the driver. + if ($config->get('mail.default') === 'smtp') { + $this->keys = array_merge($this->keys, $this->emailKeys); + } + + try { + $values = $settings->all()->mapWithKeys(function ($setting) { + return [$setting->key => $setting->value]; + })->toArray(); + } catch (QueryException $exception) { + $log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage()); + + return; + } + + foreach ($this->keys as $key) { + $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); + if (in_array($key, self::$encrypted)) { try { - $values = $settings->all()->mapWithKeys(function ($setting) { - return [$setting->key => $setting->value]; - })->toArray(); - } catch (QueryException $exception) { - $log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage()); - - return; + $value = $encrypter->decrypt($value); + } catch (DecryptException $exception) { } + } - foreach ($this->keys as $key) { - $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); - if (in_array($key, self::$encrypted)) { - try { - $value = $encrypter->decrypt($value); - } catch (DecryptException $exception) { - } - } + switch (strtolower($value)) { + case 'true': + case '(true)': + $value = true; + break; + case 'false': + case '(false)': + $value = false; + break; + case 'empty': + case '(empty)': + $value = ''; + break; + case 'null': + case '(null)': + $value = null; + } - switch (strtolower($value)) { - case 'true': - case '(true)': - $value = true; - break; - case 'false': - case '(false)': - $value = false; - break; - case 'empty': - case '(empty)': - $value = ''; - break; - case 'null': - case '(null)': - $value = null; - } - - $config->set(str_replace(':', '.', $key), $value); - } + $config->set(str_replace(':', '.', $key), $value); } + } - public static function getEncryptedKeys(): array - { - return self::$encrypted; - } + public static function getEncryptedKeys(): array + { + return self::$encrypted; + } } diff --git a/composer.lock b/composer.lock index 9cc28c9eb..32686e826 100644 --- a/composer.lock +++ b/composer.lock @@ -160,16 +160,16 @@ }, { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -178,7 +178,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -208,7 +208,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -216,7 +216,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -597,16 +597,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b115554301161fa21467629f1e1391c1936de517" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", - "reference": "b115554301161fa21467629f1e1391c1936de517", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { @@ -652,7 +652,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -660,7 +660,7 @@ "type": "github" } ], - "time": "2024-12-27T00:36:43+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { "name": "fruitcake/php-cors", @@ -797,16 +797,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { @@ -903,7 +903,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -919,20 +919,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.4", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { @@ -986,7 +986,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" + "source": "https://github.com/guzzle/promises/tree/2.2.0" }, "funding": [ { @@ -1002,20 +1002,20 @@ "type": "tidelift" } ], - "time": "2024-10-17T10:06:22+00:00" + "time": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -1102,7 +1102,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.1" }, "funding": [ { @@ -1118,7 +1118,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1277,20 +1277,20 @@ }, { "name": "laracasts/utilities", - "version": "3.2.3", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "4826fe062f58d07b13e72460141b21c8c07b736d" + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4826fe062f58d07b13e72460141b21c8c07b736d", - "reference": "4826fe062f58d07b13e72460141b21c8c07b736d", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", + "reference": "6bbd3a4c9602c2e44ee6e08d7f3fee62b6aa4146", "shasum": "" }, "require": { - "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": ">=5.5.0|>=7.2.5|>=8.0.0" }, "require-dev": { @@ -1332,9 +1332,9 @@ ], "support": { "issues": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/issues", - "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.3" + "source": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer/tree/3.2.4" }, - "time": "2024-03-03T16:53:37+00:00" + "time": "2025-02-27T15:16:31+00:00" }, { "name": "laravel/framework", @@ -1990,16 +1990,16 @@ }, { "name": "league/commonmark", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad" + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad", - "reference": "d990688c91cedfb69753ffc2512727ec646df2ad", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "shasum": "" }, "require": { @@ -2036,7 +2036,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -2093,7 +2093,7 @@ "type": "tidelift" } ], - "time": "2024-12-29T14:10:59+00:00" + "time": "2025-05-05T12:20:28+00:00" }, { "name": "league/config", @@ -2414,16 +2414,16 @@ }, { "name": "league/fractal", - "version": "0.20.1", + "version": "0.20.2", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62" + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/8b9d39b67624db9195c06f9c1ffd0355151eaf62", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", + "reference": "573ca2e0e348a7fe573a3e8fbc29a6588ece8c4e", "shasum": "" }, "require": { @@ -2432,18 +2432,18 @@ "require-dev": { "doctrine/orm": "^2.5", "illuminate/contracts": "~5.0", + "laminas/laminas-paginator": "~2.12", "mockery/mockery": "^1.3", - "pagerfanta/pagerfanta": "~1.0.0", + "pagerfanta/pagerfanta": "~1.0.0|~4.0.0", "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "~3.4", - "vimeo/psalm": "^4.22", - "zendframework/zend-paginator": "~2.3" + "vimeo/psalm": "^4.30" }, "suggest": { "illuminate/pagination": "The Illuminate Pagination component.", - "pagerfanta/pagerfanta": "Pagerfanta Paginator", - "zendframework/zend-paginator": "Zend Framework Paginator" + "laminas/laminas-paginator": "Laminas Framework Paginator", + "pagerfanta/pagerfanta": "Pagerfanta Paginator" }, "type": "library", "extra": { @@ -2478,9 +2478,9 @@ ], "support": { "issues": "https://github.com/thephpleague/fractal/issues", - "source": "https://github.com/thephpleague/fractal/tree/0.20.1" + "source": "https://github.com/thephpleague/fractal/tree/0.20.2" }, - "time": "2022-04-11T12:47:17+00:00" + "time": "2025-02-14T21:33:14+00:00" }, { "name": "league/mime-type-detection", @@ -2591,16 +2591,16 @@ }, { "name": "monolog/monolog", - "version": "3.8.1", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { @@ -2678,7 +2678,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.1" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -2690,7 +2690,7 @@ "type": "tidelift" } ], - "time": "2024-12-05T17:15:07+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2760,16 +2760,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.5", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4" + "reference": "ced71f79398ece168e24f7f7710462f462310d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/b1a53a27898639579a67de42e8ced5d5386aa9a4", - "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d", "shasum": "" }, "require": { @@ -2862,7 +2862,7 @@ "type": "tidelift" } ], - "time": "2025-02-11T16:28:45+00:00" + "time": "2025-05-01T19:51:51+00:00" }, { "name": "nette/schema", @@ -2928,16 +2928,16 @@ }, { "name": "nette/utils", - "version": "v4.0.5", + "version": "v4.0.6", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + "reference": "ce708655043c7050eb050df361c5e313cf708309" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", + "reference": "ce708655043c7050eb050df361c5e313cf708309", "shasum": "" }, "require": { @@ -3008,9 +3008,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.5" + "source": "https://github.com/nette/utils/tree/v4.0.6" }, - "time": "2024-08-07T15:39:19+00:00" + "time": "2025-03-30T21:06:30+00:00" }, { "name": "nikic/php-parser", @@ -3072,31 +3072,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3139,7 +3139,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -3155,7 +3155,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4104,16 +4104,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.7", + "version": "v0.12.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", "shasum": "" }, "require": { @@ -4177,9 +4177,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" }, - "time": "2024-12-10T01:58:33+00:00" + "time": "2025-03-16T03:05:19+00:00" }, { "name": "ralouphie/getallheaders", @@ -4227,16 +4227,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -4244,25 +4244,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -4300,19 +4297,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -4606,16 +4593,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.19.0", + "version": "1.92.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa" + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", + "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", "shasum": "" }, "require": { @@ -4626,6 +4613,7 @@ "mockery/mockery": "^1.5", "orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", "pestphp/pest": "^1.23|^2.1|^3.1", + "phpunit/php-code-coverage": "^9.0|^10.0|^11.0", "phpunit/phpunit": "^9.5.24|^10.5|^11.5", "spatie/pest-plugin-test-time": "^1.1|^2.2" }, @@ -4654,7 +4642,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.19.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" }, "funding": [ { @@ -4662,7 +4650,7 @@ "type": "github" } ], - "time": "2025-02-06T14:58:20+00:00" + "time": "2025-04-11T15:27:14+00:00" }, { "name": "spatie/laravel-query-builder", @@ -4740,16 +4728,16 @@ }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.16.3", + "version": "v2.16.4", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "33f8b614bf2e84bf818911981326654a4a0c8741" + "reference": "451496e4272c11d87b404791acdd352c606f29c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/33f8b614bf2e84bf818911981326654a4a0c8741", - "reference": "33f8b614bf2e84bf818911981326654a4a0c8741", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/451496e4272c11d87b404791acdd352c606f29c0", + "reference": "451496e4272c11d87b404791acdd352c606f29c0", "shasum": "" }, "require": { @@ -4795,7 +4783,7 @@ "description": "Laravel Eloquent BelongsToThrough relationships", "support": { "issues": "https://github.com/staudenmeir/belongs-to-through/issues", - "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16.3" + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16.4" }, "funding": [ { @@ -4803,7 +4791,7 @@ "type": "custom" } ], - "time": "2025-02-02T11:46:25+00:00" + "time": "2025-02-20T19:20:43+00:00" }, { "name": "symfony/clock", @@ -4881,16 +4869,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", "shasum": "" }, "require": { @@ -4954,7 +4942,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.6" }, "funding": [ { @@ -4970,7 +4958,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/css-selector", @@ -5106,16 +5094,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49" + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", "shasum": "" }, "require": { @@ -5161,7 +5149,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.3" + "source": "https://github.com/symfony/error-handler/tree/v7.2.5" }, "funding": [ { @@ -5177,7 +5165,7 @@ "type": "tidelift" } ], - "time": "2025-01-07T09:39:55+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "symfony/event-dispatcher", @@ -5573,16 +5561,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b", + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b", "shasum": "" }, "require": { @@ -5631,7 +5619,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.6" }, "funding": [ { @@ -5647,20 +5635,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b" + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec", + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec", "shasum": "" }, "require": { @@ -5745,7 +5733,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.6" }, "funding": [ { @@ -5761,20 +5749,20 @@ "type": "tidelift" } ], - "time": "2025-01-29T07:40:13+00:00" + "time": "2025-05-02T09:04:03+00:00" }, { "name": "symfony/mailer", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3" + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3", + "url": "https://api.github.com/repos/symfony/mailer/zipball/998692469d6e698c6eadc7ef37a6530a9eabb356", + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356", "shasum": "" }, "require": { @@ -5825,7 +5813,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.3" + "source": "https://github.com/symfony/mailer/tree/v7.2.6" }, "funding": [ { @@ -5841,7 +5829,7 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-04T09:50:51+00:00" }, { "name": "symfony/mailgun-mailer", @@ -5914,16 +5902,16 @@ }, { "name": "symfony/mime", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204" + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204", + "url": "https://api.github.com/repos/symfony/mime/zipball/706e65c72d402539a072d0d6ad105fff6c161ef1", + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1", "shasum": "" }, "require": { @@ -5978,7 +5966,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.3" + "source": "https://github.com/symfony/mime/tree/v7.2.6" }, "funding": [ { @@ -5994,11 +5982,11 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-27T13:34:41+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -6057,7 +6045,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -6077,7 +6065,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -6135,7 +6123,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -6155,16 +6143,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -6218,7 +6206,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -6234,11 +6222,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6299,7 +6287,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -6319,19 +6307,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -6379,7 +6368,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -6395,20 +6384,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -6459,7 +6448,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -6475,11 +6464,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -6535,7 +6524,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -6555,7 +6544,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6614,7 +6603,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -6704,16 +6693,16 @@ }, { "name": "symfony/process", - "version": "v7.2.0", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -6745,7 +6734,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -6761,7 +6750,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/routing", @@ -6929,16 +6918,16 @@ }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", "shasum": "" }, "require": { @@ -6996,7 +6985,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.2.6" }, "funding": [ { @@ -7012,20 +7001,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:18:16+00:00" }, { "name": "symfony/translation", - "version": "v7.2.2", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", + "url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", "shasum": "" }, "require": { @@ -7091,7 +7080,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.2" + "source": "https://github.com/symfony/translation/tree/v7.2.6" }, "funding": [ { @@ -7107,7 +7096,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:18:10+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/translation-contracts", @@ -7263,16 +7252,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9c46038cd4ed68952166cf7001b54eb539184ccb", + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb", "shasum": "" }, "require": { @@ -7326,7 +7315,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.6" }, "funding": [ { @@ -7342,7 +7331,7 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:39:41+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/yaml", @@ -7472,16 +7461,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -7540,7 +7529,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -7552,7 +7541,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "voku/portable-ascii", @@ -7900,16 +7889,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9" + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ffe442c5974c44a9343e37a0abcb1cc37319f5b9", - "reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", "shasum": "" }, "require": { @@ -7953,7 +7942,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.6.0" + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" }, "funding": [ { @@ -7969,7 +7958,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T10:05:34+00:00" + "time": "2025-03-24T13:50:44+00:00" }, { "name": "composer/pcre", @@ -8199,26 +8188,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -8238,9 +8230,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "evenement/evenement", @@ -8415,16 +8407,16 @@ }, { "name": "filp/whoops", - "version": "2.17.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", "shasum": "" }, "require": { @@ -8474,7 +8466,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.17.0" + "source": "https://github.com/filp/whoops/tree/2.18.0" }, "funding": [ { @@ -8482,7 +8474,7 @@ "type": "github" } ], - "time": "2025-01-25T12:00:00+00:00" + "time": "2025-03-15T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -8589,20 +8581,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -8610,8 +8602,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -8634,9 +8626,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "itsgoingd/clockwork", @@ -9031,16 +9023,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -9079,7 +9071,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -9087,7 +9079,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nunomaduro/collision", @@ -9417,16 +9409,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.10.3", + "version": "5.11.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055" + "reference": "07044bc8c13abd542756c3fd34dc66a5d6dee8e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/5346664973d10cf1abff20837fb1183f3c11a055", - "reference": "5346664973d10cf1abff20837fb1183f3c11a055", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/07044bc8c13abd542756c3fd34dc66a5d6dee8e4", + "reference": "07044bc8c13abd542756c3fd34dc66a5d6dee8e4", "shasum": "" }, "require": { @@ -9441,9 +9433,11 @@ "phpbench/phpbench": "^1.1", "phpmyadmin/coding-standard": "^3.0", "phpmyadmin/motranslator": "^4.0 || ^5.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.9.12", - "phpstan/phpstan-phpunit": "^1.3.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^1.12", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.16.1", "vimeo/psalm": "^4.11", @@ -9500,20 +9494,20 @@ "type": "other" } ], - "time": "2025-01-19T04:14:02+00:00" + "time": "2025-02-22T20:00:59+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", "shasum": "" }, "require": { @@ -9545,22 +9539,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" }, - "time": "2024-10-13T11:29:49+00:00" + "time": "2025-02-19T13:28:12+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.17", + "version": "1.12.25", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7027b3b0270bf392de0cfba12825979768d728bf" + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7027b3b0270bf392de0cfba12825979768d728bf", - "reference": "7027b3b0270bf392de0cfba12825979768d728bf", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e310849a19e02b8bfcbb63147f495d8f872dd96f", + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f", "shasum": "" }, "require": { @@ -9605,7 +9599,7 @@ "type": "github" } ], - "time": "2025-02-07T15:01:57+00:00" + "time": "2025-04-27T12:20:45+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9930,16 +9924,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.45", + "version": "10.5.46", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", "shasum": "" }, "require": { @@ -9949,7 +9943,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -10011,7 +10005,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46" }, "funding": [ { @@ -10022,12 +10016,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-02-06T16:08:12+00:00" + "time": "2025-05-02T06:46:24+00:00" }, { "name": "react/cache", @@ -11473,16 +11475,16 @@ }, { "name": "spatie/backtrace", - "version": "1.7.1", + "version": "1.7.4", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b" + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b", - "reference": "0f2477c520e3729de58e061b8192f161c99f770b", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", "shasum": "" }, "require": { @@ -11520,7 +11522,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.1" + "source": "https://github.com/spatie/backtrace/tree/1.7.4" }, "funding": [ { @@ -11532,34 +11534,34 @@ "type": "other" } ], - "time": "2024-12-02T13:28:15+00:00" + "time": "2025-05-08T15:41:09+00:00" }, { "name": "spatie/error-solutions", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", - "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", "shasum": "" }, "require": { "php": "^8.0" }, "require-dev": { - "illuminate/broadcasting": "^10.0|^11.0", - "illuminate/cache": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "livewire/livewire": "^2.11|^3.3.5", + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", "openai-php/client": "^0.10.1", - "orchestra/testbench": "^7.0|8.22.3|^9.0", - "pestphp/pest": "^2.20", - "phpstan/phpstan": "^1.11", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", "psr/simple-cache": "^3.0", "psr/simple-cache-implementation": "^3.0", "spatie/ray": "^1.28", @@ -11598,7 +11600,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.1.2" + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" }, "funding": [ { @@ -11606,24 +11608,24 @@ "type": "github" } ], - "time": "2024-12-11T09:51:56+00:00" + "time": "2025-02-14T12:29:50+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272" + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", - "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", + "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^8.0", "spatie/backtrace": "^1.6.1", "symfony/http-foundation": "^5.2|^6.0|^7.0", @@ -11667,7 +11669,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.10.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" }, "funding": [ { @@ -11675,20 +11677,20 @@ "type": "github" } ], - "time": "2024-12-02T14:30:06+00:00" + "time": "2025-02-14T13:42:06+00:00" }, { "name": "spatie/ignition", - "version": "1.15.0", + "version": "1.15.1", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2" + "reference": "31f314153020aee5af3537e507fef892ffbf8c85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/e3a68e137371e1eb9edc7f78ffa733f3b98991d2", - "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2", + "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", + "reference": "31f314153020aee5af3537e507fef892ffbf8c85", "shasum": "" }, "require": { @@ -11701,7 +11703,7 @@ "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52|^10.0|^11.0", + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", "mockery/mockery": "^1.4", "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", @@ -11758,7 +11760,7 @@ "type": "github" } ], - "time": "2024-06-12T14:55:22+00:00" + "time": "2025-02-21T14:31:39+00:00" }, { "name": "spatie/laravel-ignition", @@ -11986,7 +11988,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -12042,7 +12044,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -12062,16 +12064,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.2.2", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { @@ -12104,7 +12106,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.4" }, "funding": [ { @@ -12120,7 +12122,7 @@ "type": "tidelift" } ], - "time": "2024-12-18T14:28:33+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index 4eaea69a1..448e42fee 100644 --- a/config/app.php +++ b/config/app.php @@ -3,231 +3,231 @@ use Illuminate\Support\Facades\Facade; return [ - /* - |-------------------------------------------------------------------------- - | Application Version - |-------------------------------------------------------------------------- - | This value is set when creating a Pterodactyl release. You should not - | change this value if you are not maintaining your own internal versions. - */ + /* + |-------------------------------------------------------------------------- + | Application Version + |-------------------------------------------------------------------------- + | This value is set when creating a Pterodactyl release. You should not + | change this value if you are not maintaining your own internal versions. + */ - 'version' => '3.0.0', + 'version' => '3.0.0', + + /* + |-------------------------------------------------------------------------- + | Application Name + |-------------------------------------------------------------------------- + | + | This value is the name of your application, which will be used when the + | framework needs to place the application's name in a notification or + | other UI elements where an application name needs to be displayed. + | + */ + + 'name' => env('APP_NAME', 'Pyrodactyl'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + + /* + |-------------------------------------------------------------------------- + | Exception Reporter Configuration + |-------------------------------------------------------------------------- + | + | If you're encountering weird behavior with the Panel and no exceptions + | are being logged try changing the environment variable below to be true. + | This will override the default "don't report" behavior of the Panel and log + | all exceptions. This will be quite noisy. + | + */ + + 'exceptions' => [ + 'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false), + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, /* - |-------------------------------------------------------------------------- - | Application Name - |-------------------------------------------------------------------------- - | - | This value is the name of your application, which will be used when the - | framework needs to place the application's name in a notification or - | other UI elements where an application name needs to be displayed. - | - */ - - 'name' => env('APP_NAME', 'Pyrodactyl'), + * Application Service Providers... + */ + Pterodactyl\Providers\ActivityLogServiceProvider::class, + Pterodactyl\Providers\AppServiceProvider::class, + Pterodactyl\Providers\AuthServiceProvider::class, + Pterodactyl\Providers\BackupsServiceProvider::class, + Pterodactyl\Providers\BladeServiceProvider::class, + Pterodactyl\Providers\EventServiceProvider::class, + Pterodactyl\Providers\HashidsServiceProvider::class, + Pterodactyl\Providers\RouteServiceProvider::class, + Pterodactyl\Providers\RepositoryServiceProvider::class, + Pterodactyl\Providers\ViewComposerServiceProvider::class, /* - |-------------------------------------------------------------------------- - | Application Environment - |-------------------------------------------------------------------------- - | - | This value determines the "environment" your application is currently - | running in. This may determine how you prefer to configure various - | services the application utilizes. Set this in your ".env" file. - | - */ + * Additional Dependencies + */ + Prologue\Alerts\AlertsServiceProvider::class, + ], - 'env' => env('APP_ENV', 'production'), + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded, so they don't hinder performance. + | + */ - /* - |-------------------------------------------------------------------------- - | Application Debug Mode - |-------------------------------------------------------------------------- - | - | When your application is in debug mode, detailed error messages with - | stack traces will be shown on every error that occurs within your - | application. If disabled, a simple generic error page is shown. - | - */ + 'aliases' => Facade::defaultAliases()->merge([ + 'Alert' => Prologue\Alerts\Facades\Alert::class, + 'Carbon' => Carbon\Carbon::class, + 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, + 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - 'debug' => (bool) env('APP_DEBUG', false), - - /* - |-------------------------------------------------------------------------- - | Application URL - |-------------------------------------------------------------------------- - | - | This URL is used by the console to properly generate URLs when using - | the Artisan command line tool. You should set this to the root of - | the application so that it's available within Artisan commands. - | - */ - - 'url' => env('APP_URL', 'http://localhost'), - - /* - |-------------------------------------------------------------------------- - | Application Timezone - |-------------------------------------------------------------------------- - | - | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. The timezone - | is set to "UTC" by default as it is suitable for most use cases. - | - */ - - 'timezone' => env('APP_TIMEZONE', 'UTC'), - - /* - |-------------------------------------------------------------------------- - | Application Locale Configuration - |-------------------------------------------------------------------------- - | - | The application locale determines the default locale that will be used - | by Laravel's translation / localization methods. This option can be - | set to any locale for which you plan to have translation strings. - | - */ - - 'locale' => env('APP_LOCALE', 'en'), - - 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), - - 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - | This key is utilized by Laravel's encryption services and should be set - | to a random, 32 character string to ensure that all encrypted values - | are secure. You should do this prior to deploying the application. - | - */ - - 'cipher' => 'AES-256-CBC', - - 'key' => env('APP_KEY'), - - 'previous_keys' => [ - ...array_filter( - explode(',', env('APP_PREVIOUS_KEYS', '')) - ), - ], - - /* - |-------------------------------------------------------------------------- - | Maintenance Mode Driver - |-------------------------------------------------------------------------- - | - | These configuration options determine the driver used to determine and - | manage Laravel's "maintenance mode" status. The "cache" driver will - | allow maintenance mode to be controlled across multiple machines. - | - | Supported drivers: "file", "cache" - | - */ - - 'maintenance' => [ - 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), - 'store' => env('APP_MAINTENANCE_STORE', 'database'), - ], - - /* - |-------------------------------------------------------------------------- - | Exception Reporter Configuration - |-------------------------------------------------------------------------- - | - | If you're encountering weird behavior with the Panel and no exceptions - | are being logged try changing the environment variable below to be true. - | This will override the default "don't report" behavior of the Panel and log - | all exceptions. This will be quite noisy. - | - */ - - 'exceptions' => [ - 'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false), - ], - - /* - |-------------------------------------------------------------------------- - | Autoloaded Service Providers - |-------------------------------------------------------------------------- - | - | The service providers listed here will be automatically loaded on the - | request to your application. Feel free to add your own services to - | this array to grant expanded functionality to your applications. - | - */ - - 'providers' => [ - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - - /* - * Application Service Providers... - */ - Pterodactyl\Providers\ActivityLogServiceProvider::class, - Pterodactyl\Providers\AppServiceProvider::class, - Pterodactyl\Providers\AuthServiceProvider::class, - Pterodactyl\Providers\BackupsServiceProvider::class, - Pterodactyl\Providers\BladeServiceProvider::class, - Pterodactyl\Providers\EventServiceProvider::class, - Pterodactyl\Providers\HashidsServiceProvider::class, - Pterodactyl\Providers\RouteServiceProvider::class, - Pterodactyl\Providers\RepositoryServiceProvider::class, - Pterodactyl\Providers\ViewComposerServiceProvider::class, - - /* - * Additional Dependencies - */ - Prologue\Alerts\AlertsServiceProvider::class, - ], - - /* - |-------------------------------------------------------------------------- - | Class Aliases - |-------------------------------------------------------------------------- - | - | This array of class aliases will be registered when this application - | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded, so they don't hinder performance. - | - */ - - 'aliases' => Facade::defaultAliases()->merge([ - 'Alert' => Prologue\Alerts\Facades\Alert::class, - 'Carbon' => Carbon\Carbon::class, - 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, - 'Theme' => Pterodactyl\Extensions\Facades\Theme::class, - - // Custom Facades - 'Activity' => Pterodactyl\Facades\Activity::class, - 'LogBatch' => Pterodactyl\Facades\LogBatch::class, - 'LogTarget' => Pterodactyl\Facades\LogTarget::class, - ])->toArray(), + // Custom Facades + 'Activity' => Pterodactyl\Facades\Activity::class, + 'LogBatch' => Pterodactyl\Facades\LogBatch::class, + 'LogTarget' => Pterodactyl\Facades\LogTarget::class, + ])->toArray(), ]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 000000000..b5412fec8 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,53 @@ + env('CAPTCHA_DRIVER', 'none'), + + 'providers' => [ + 'hcaptcha' => [ + 'enabled' => env('HCAPTCHA_ENABLED', false), + 'site_key' => env('HCAPTCHA_SITE_KEY'), + 'secret_key' => env('HCAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://hcaptcha.com/siteverify', + ], + + 'mcaptcha' => [ + 'enabled' => env('MCAPTCHA_ENABLED', false), + 'site_key' => env('MCAPTCHA_SITE_KEY'), + 'secret_key' => env('MCAPTCHA_SECRET_KEY'), + 'endpoint' => env('MCAPTCHA_ENDPOINT', 'https://mcaptcha.your-instance.com/api/v1/pow/verify'), + ], + + 'turnstile' => [ + 'enabled' => env('TURNSTILE_ENABLED', false), + 'site_key' => env('TURNSTILE_SITE_KEY'), + 'secret_key' => env('TURNSTILE_SECRET_KEY'), + 'endpoint' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', + ], + + 'proton' => [ + 'enabled' => env('PROTON_CAPTCHA_ENABLED', false), + 'site_key' => env('PROTON_CAPTCHA_SITE_KEY'), + 'secret_key' => env('PROTON_CAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://api.proton.me/captcha/v3/verify', + ], + + 'friendly' => [ + 'enabled' => env('FRIENDLY_CAPTCHA_ENABLED', false), + 'site_key' => env('FRIENDLY_CAPTCHA_SITE_KEY'), + 'secret_key' => env('FRIENDLY_CAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://api.friendlycaptcha.com/api/v1/siteverify', + ], + + 'recaptcha' => [ + 'enabled' => env('RECAPTCHA_ENABLED', false), + 'site_key' => env('RECAPTCHA_SITE_KEY'), + 'secret_key' => env('RECAPTCHA_SECRET_KEY'), + 'endpoint' => 'https://www.google.com/recaptcha/api/siteverify', + ], + ], + + // Global settings + 'verify_domain' => false, + 'timeout' => 5, // seconds +]; diff --git a/config/cors.php b/config/cors.php index bf72895e0..2a1eac838 100644 --- a/config/cors.php +++ b/config/cors.php @@ -1,57 +1,57 @@ ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], + /* + * You can enable CORS for 1 or multiple paths. + * Example: ['api/*'] + */ + 'paths' => ['/api/client', '/api/application', '/api/client/*', '/api/application/*'], - /* - * Matches the request method. `['*']` allows all methods. - */ - 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], + /* + * Matches the request method. `['*']` allows all methods. + */ + 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'], - /* - * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` - */ - 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), + /* + * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => explode(',', env('APP_CORS_ALLOWED_ORIGINS') ?? ''), - /* - * Patterns that can be used with `preg_match` to match the origin. - */ - 'allowed_origins_patterns' => [], + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowed_origins_patterns' => [], - /* - * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. - */ - 'allowed_headers' => ['*'], + /* + * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. + */ + 'allowed_headers' => ['*'], - /* - * Sets the Access-Control-Expose-Headers response header with these headers. - */ - 'exposed_headers' => [], + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], - /* - * Sets the Access-Control-Max-Age response header when > 0. - */ - 'max_age' => 0, + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, - /* - * Sets the Access-Control-Allow-Credentials header. - */ - 'supports_credentials' => true, + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => true, ]; diff --git a/config/recaptcha.php b/config/recaptcha.php deleted file mode 100644 index 757e184a5..000000000 --- a/config/recaptcha.php +++ /dev/null @@ -1,31 +0,0 @@ - env('RECAPTCHA_ENABLED', true), - - /* - * API endpoint for recaptcha checks. You should not edit this. - */ - 'domain' => env('RECAPTCHA_DOMAIN', 'https://www.google.com/recaptcha/api/siteverify'), - - /* - * Use a custom secret key, we use our public one by default - */ - 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5'), - '_shipped_secret_key' => '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5', - - /* - * Use a custom website key, we use our public one by default - */ - 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn'), - '_shipped_website_key' => '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn', - - /* - * Domain verification is enabled by default and compares the domain used when solving the captcha - * as public keys can't have domain verification on google's side enabled (obviously). - */ - 'verify_domain' => true, -]; diff --git a/package.json b/package.json index 30794032d..1befaaf86 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,12 @@ "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@friendlycaptcha/sdk": "^0.1.22", + "@hcaptcha/react-hcaptcha": "^1.12.0", "@headlessui/react": "^2.2.2", "@lezer/highlight": "^1.2.1", + "@marsidev/react-turnstile": "^1.1.0", + "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@preact/signals-react": "^3.0.1", "@radix-ui/react-checkbox": "^1.3.1", "@radix-ui/react-context-menu": "^2.2.14", @@ -53,7 +57,9 @@ "formik": "^2.4.6", "framer-motion": "^12.10.5", "globals": "^16.1.0", + "install": "^0.13.0", "laravel-vite-plugin": "^1.2.0", + "lucide-react": "^0.511.0", "million": "^3.1.11", "pathe": "^2.0.3", "qrcode.react": "^4.2.0", @@ -61,6 +67,7 @@ "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-fast-compare": "^3.2.2", + "react-google-recaptcha-v3": "^1.11.0", "react-router-dom": "^7.6.0", "reaptcha": "^1.12.1", "sockette": "^2.0.6", @@ -126,5 +133,5 @@ "firefox esr", "not dead" ], - "packageManager": "pnpm@10.10.0" + "packageManager": "pnpm@10.11.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 162603b95..f689b4dc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,12 +53,24 @@ importers: '@fortawesome/react-fontawesome': specifier: ^0.2.2 version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.1.0) + '@friendlycaptcha/sdk': + specifier: ^0.1.22 + version: 0.1.22 + '@hcaptcha/react-hcaptcha': + specifier: ^1.12.0 + version: 1.12.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@headlessui/react': specifier: ^2.2.2 version: 2.2.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@lezer/highlight': specifier: ^1.2.1 version: 1.2.1 + '@marsidev/react-turnstile': + specifier: ^1.1.0 + version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mcaptcha/vanilla-glue': + specifier: 0.1.0-alpha-3 + version: 0.1.0-alpha-3 '@preact/signals-react': specifier: ^3.0.1 version: 3.0.1(react@19.1.0) @@ -146,9 +158,15 @@ importers: globals: specifier: ^16.1.0 version: 16.1.0 + install: + specifier: ^0.13.0 + version: 0.13.0 laravel-vite-plugin: specifier: ^1.2.0 version: 1.2.0(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.29.2)) + lucide-react: + specifier: ^0.511.0 + version: 0.511.0(react@19.1.0) million: specifier: ^3.1.11 version: 3.1.11(rollup@4.40.2) @@ -170,6 +188,9 @@ importers: react-fast-compare: specifier: ^3.2.2 version: 3.2.2 + react-google-recaptcha-v3: + specifier: ^1.11.0 + version: 1.11.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-router-dom: specifier: ^7.6.0 version: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -713,6 +734,18 @@ packages: '@fortawesome/fontawesome-svg-core': ~1 || ~6 react: '>=16.3' + '@friendlycaptcha/sdk@0.1.22': + resolution: {integrity: sha512-VK/dx5JWd11Y3fDgf9eXFWtZ9zLYrTgRUmHWibfJ1mL18/8OLQzgPXFyT2YSe74iHDG5pllJm4EFF7LW4wpvig==} + + '@hcaptcha/loader@2.0.0': + resolution: {integrity: sha512-fFQH6ApU/zCCl6Y1bnbsxsp1Er/lKX+qlgljrpWDeFcenpEtoP68hExlKSXECospzKLeSWcr06cbTjlR/x3IJA==} + + '@hcaptcha/react-hcaptcha@1.12.0': + resolution: {integrity: sha512-QiHnQQ52k8SJJSHkc3cq4TlYzag7oPd4f5ZqnjVSe4fJDSlZaOQFtu5F5AYisVslwaitdDELPVLRsRJxiiI0Aw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + '@headlessui/react@2.2.2': resolution: {integrity: sha512-zbniWOYBQ8GHSUIOPY7BbdIn6PzUOq0z41RFrF30HbjsxG6Rrfk+6QulR8Kgf2Vwj2a/rE6i62q5vo+2gI5dJA==} engines: {node: '>=10'} @@ -819,6 +852,18 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@marsidev/react-turnstile@1.1.0': + resolution: {integrity: sha512-X7bP9ZYutDd+E+klPYF+/BJHqEyyVkN4KKmZcNRr84zs3DcMoftlMAuoKqNSnqg0HE7NQ1844+TLFSJoztCdSA==} + peerDependencies: + react: ^17.0.2 || ^18.0.0 || ^19.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0 + + '@mcaptcha/core-glue@0.1.0-alpha-5': + resolution: {integrity: sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA==} + + '@mcaptcha/vanilla-glue@0.1.0-alpha-3': + resolution: {integrity: sha512-GT6TJBgmViGXcXiT5VOr+h/6iOnThSlZuCoOWncubyTZU9R3cgU5vWPkF7G6Ob6ee2CBe3yqBxxk24CFVGTVXw==} + '@modelcontextprotocol/sdk@1.11.1': resolution: {integrity: sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==} engines: {node: '>=18'} @@ -2480,6 +2525,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -2743,6 +2792,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.511.0: + resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -3111,6 +3165,12 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-google-recaptcha-v3@1.11.0: + resolution: {integrity: sha512-kLQqpz/77m8+trpBwzqcxNtvWZYoZ/YO6Vm2cVTHW8hs80BWUfDpC7RDwuAvpswwtSYApWfaSpIDFWAIBNIYxQ==} + peerDependencies: + react: ^16.3 || ^17.0 || ^18.0 || ^19.0 + react-dom: ^17.0 || ^18.0 || ^19.0 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4223,6 +4283,17 @@ snapshots: prop-types: 15.8.1 react: 19.1.0 + '@friendlycaptcha/sdk@0.1.22': {} + + '@hcaptcha/loader@2.0.0': {} + + '@hcaptcha/react-hcaptcha@1.12.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + '@hcaptcha/loader': 2.0.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@headlessui/react@2.2.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -4364,6 +4435,17 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@marsidev/react-turnstile@1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@mcaptcha/core-glue@0.1.0-alpha-5': {} + + '@mcaptcha/vanilla-glue@0.1.0-alpha-3': + dependencies: + '@mcaptcha/core-glue': 0.1.0-alpha-5 + '@modelcontextprotocol/sdk@1.11.1': dependencies: content-type: 1.0.5 @@ -6164,6 +6246,8 @@ snapshots: inherits@2.0.4: {} + install@0.13.0: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -6405,6 +6489,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.511.0(react@19.1.0): + dependencies: + react: 19.1.0 + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -6689,6 +6777,12 @@ snapshots: react-fast-compare@3.2.2: {} + react-google-recaptcha-v3@1.11.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + hoist-non-react-statics: 3.3.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-is@16.13.1: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.3)(react@19.1.0): diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index 57552e5ed..78d5789a8 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -1,38 +1,69 @@ import http from '@/api/http'; -export interface LoginResponse { +interface LoginData { + user: string; + password: string; + 'cf-turnstile-response'?: string; + 'h-captcha-response'?: string; + 'frc-captcha-response'?: string; + captchaData?: string; +} + +interface LoginResponse { complete: boolean; intended?: string; confirmationToken?: string; + error?: string; } -export interface LoginData { - username: string; - password: string; - recaptchaData?: string | null; -} +export default async (data: LoginData): Promise => { + try { + await http.get('/sanctum/csrf-cookie'); -export default ({ username, password, recaptchaData }: LoginData): Promise => { - return new Promise((resolve, reject) => { - http.get('/sanctum/csrf-cookie') - .then(() => - http.post('/auth/login', { - user: username, - password, - 'g-recaptcha-response': recaptchaData, - }), - ) - .then((response) => { - if (!(response.data instanceof Object)) { - return reject(new Error('An error occurred while processing the login request.')); - } + const payload: Record = { + user: data.user, + password: data.password, + }; - return resolve({ - complete: response.data.data.complete, - intended: response.data.data.intended || undefined, - confirmationToken: response.data.data.confirmation_token || undefined, - }); - }) - .catch(reject); - }); + if (data['cf-turnstile-response']) { + payload['cf-turnstile-response'] = data['cf-turnstile-response']; + } else if (data['h-captcha-response']) { + payload['h-captcha-response'] = data['h-captcha-response']; + } else if (data['frc-captcha-response']) { + payload['frc-captcha-response'] = data['frc-captcha-response']; + } else if (data['g-captcha-response']) { + payload['g-captcha-response'] = data['g-captcha-response']; + } else if (data.captchaData) { + payload.captchaData = data.captchaData; + } + + const response = await http.post('/auth/login', payload); + + if (!response.data || typeof response.data !== 'object') { + throw new Error('Invalid server response format'); + } + + return { + complete: response.data.complete ?? response.data.data?.complete ?? false, + intended: response.data.intended ?? response.data.data?.intended, + confirmationToken: + response.data.confirmationToken ?? + response.data.data?.confirmation_token ?? + response.data.data?.confirmationToken, + error: response.data.error ?? response.data.message, + }; + } catch (error: any) { + console.error('Login API Error:', { + status: error.response?.status, + data: error.response?.data, + message: error.message, + }); + + throw new Error( + error.response?.data?.error ?? + error.response?.data?.message ?? + error.message ?? + 'Login failed. Please try again.', + ); + } }; diff --git a/resources/scripts/components/Captcha.tsx b/resources/scripts/components/Captcha.tsx new file mode 100644 index 000000000..f33861705 --- /dev/null +++ b/resources/scripts/components/Captcha.tsx @@ -0,0 +1,57 @@ +// components/Captcha.tsx +import { + CreateWidgetOptions, + FRCWidgetCompleteEvent, + FRCWidgetErrorEventData, + FriendlyCaptchaSDK, + WidgetErrorData, +} from '@friendlycaptcha/sdk'; +import HCaptcha from '@hcaptcha/react-hcaptcha'; +import { Turnstile } from '@marsidev/react-turnstile'; +import { useEffect, useState } from 'react'; + +interface CaptchaProps { + sitekey?: string; + endpoint?: string; + driver: 'none' | 'hcaptcha' | 'mcaptcha' | 'turnstile' | 'friendly' | 'recaptcha'; + onVerify: (token: string) => void; + onError: () => void; + onExpire: () => void; +} + +export default ({ driver, sitekey, endpoint, onVerify, onError, onExpire }: CaptchaProps) => { + const [loaded, setLoaded] = useState(false); + + useEffect(() => { + if (driver !== 'none' && !loaded) { + // Load any required external scripts here if needed + setLoaded(true); + } + }, [driver]); + + if (driver === 'hcaptcha') { + return ; + } + + if (driver === 'turnstile') { + return ; + } + + if (driver === 'recaptcha') { + // TODO: Maybe make this work one day + return ; + } + + if (driver === 'friendly') { + return ( + onVerify(e.detail.token)} + onError={onError} + /> + ); + } + + return null; +}; diff --git a/resources/scripts/components/FriendlyCaptcha.tsx b/resources/scripts/components/FriendlyCaptcha.tsx new file mode 100644 index 000000000..56d767b7c --- /dev/null +++ b/resources/scripts/components/FriendlyCaptcha.tsx @@ -0,0 +1,48 @@ +import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; + +interface FriendlyCaptchaProps { + sitekey: string; + onComplete: (response: string) => void; + onError: () => void; + onExpire: () => void; +} + +const FriendlyCaptcha = forwardRef(({ sitekey, onComplete, onError, onExpire }: FriendlyCaptchaProps, ref) => { + const containerRef = React.useRef(null); + const widgetRef = React.useRef(null); + + useImperativeHandle(ref, () => ({ + reset: () => { + if (widgetRef.current) { + widgetRef.current.reset(); + } + }, + })); + + useEffect(() => { + if (!window.friendlyChallenge) return; + + if (containerRef.current) { + widgetRef.current = new window.friendlyChallenge.WidgetInstance( + containerRef.current, + { + startMode: 'auto', + doneCallback: onComplete, + errorCallback: onError, + expiredCallback: onExpire, + }, + { sitekey }, + ); + } + + return () => { + if (widgetRef.current) { + widgetRef.current.destroy(); + } + }; + }, [sitekey]); + + return
; +}); + +export default FriendlyCaptcha; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 89036e6dc..60d40b5b8 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -1,119 +1,160 @@ +import HCaptcha from '@hcaptcha/react-hcaptcha'; +import { Turnstile } from '@marsidev/react-turnstile'; import { useStoreState } from 'easy-peasy'; import type { FormikHelpers } from 'formik'; import { Formik } from 'formik'; import { useEffect, useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import Reaptcha from 'reaptcha'; import { object, string } from 'yup'; +import FriendlyCaptcha from '@/components/FriendlyCaptcha'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import Button from '@/components/elements/Button'; import Field from '@/components/elements/Field'; +import Logo from '@/components/elements/PyroLogo'; import login from '@/api/auth/login'; import useFlash from '@/plugins/useFlash'; -import Logo from '../elements/PyroLogo'; - interface Values { - username: string; + user: string; password: string; } function LoginContainer() { - const ref = useRef(null); const [token, setToken] = useState(''); + const [friendlyLoaded, setFriendlyLoaded] = useState(false); + const turnstileRef = useRef(null); + const friendlyCaptchaRef = useRef<{ reset: () => void }>(null); + const hCaptchaRef = useRef(null); + const mCaptchaRef = useRef<{ reset: () => void }>(null); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); + const { captcha } = useStoreState((state) => state.settings.data!); + const isTurnstileEnabled = captcha.driver === 'turnstile' && captcha.turnstile?.siteKey; + const isFriendlyEnabled = captcha.driver === 'friendly' && captcha.friendly?.siteKey; + const isHCaptchaEnabled = captcha.driver === 'hcaptcha' && captcha.hcaptcha?.siteKey; + const isMCaptchaEnabled = captcha.driver === 'mcaptcha' && captcha.mcaptcha?.siteKey; const navigate = useNavigate(); useEffect(() => { clearFlashes(); + + // Load FriendlyCaptcha script if needed + if (isFriendlyEnabled && !window.friendlyChallenge) { + const script = document.createElement('script'); + script.src = 'https://unpkg.com/friendly-challenge@0.9.12/widget.module.min.js'; + script.async = true; + script.defer = true; + script.onload = () => setFriendlyLoaded(true); + document.body.appendChild(script); + } else if (isFriendlyEnabled) { + setFriendlyLoaded(true); + } }, []); + const resetCaptcha = () => { + setToken(''); + if (isTurnstileEnabled && turnstileRef.current) { + // @ts-ignore - The type doesn't expose the reset method directly + turnstileRef.current.reset(); + } + if (isFriendlyEnabled && friendlyCaptchaRef.current) { + friendlyCaptchaRef.current.reset(); + } + if (isHCaptchaEnabled && hCaptchaRef.current) { + hCaptchaRef.current.resetCaptcha(); + } + }; + + const handleCaptchaComplete = (response: string) => { + setToken(response); + }; + + const handleCaptchaError = (provider: string) => { + setToken(''); + clearAndAddHttpError({ error: new Error(`${provider} challenge failed.`) }); + }; + + const handleCaptchaExpire = () => { + setToken(''); + }; + const onSubmit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - // If there is no token in the state yet, request the token and then abort this submit request - // since it will be re-submitted when the recaptcha data is returned by the component. - if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { - console.error(error); - - setSubmitting(false); - clearAndAddHttpError({ error }); - }); - + if ((isTurnstileEnabled || isFriendlyEnabled || isHCaptchaEnabled) && !token) { + setSubmitting(false); + clearAndAddHttpError({ error: new Error('Please complete the CAPTCHA challenge.') }); return; } - login({ ...values, recaptchaData: token }) + const requestData: Record = { + user: values.user, + password: values.password, + }; + + if (isTurnstileEnabled) { + requestData['cf-turnstile-response'] = token; + if (process.env.NODE_ENV === 'development') { + requestData['cf-turnstile-remoteip'] = 'localhost'; + } + } else if (isHCaptchaEnabled) { + requestData['h-captcha-response'] = token; + } else if (isFriendlyEnabled) { + requestData['frc-captcha-response'] = token; + } + + login(requestData) .then((response) => { if (response.complete) { - // @ts-expect-error this is valid - window.location = response.intended || '/'; + window.location.href = response.intended || '/'; return; } - navigate('/auth/login/checkpoint', { state: { token: response.confirmationToken } }); }) .catch((error) => { - console.error(error); - - setToken(''); - // https://github.com/jsardev/reaptcha/issues/218 - if (ref.current) { - setTimeout(() => { - if (ref.current) { - ref.current.reset(); - } - }, 500); - } - + console.error('Login error details:', { + message: error.message, + response: error.response?.data, + config: error.config, + }); + resetCaptcha(); setSubmitting(false); - clearAndAddHttpError({ error }); + + // Check if the error is specifically about invalid credentials + if (error.response?.data?.errors?.some((e: any) => e.code === 'InvalidCredentials')) { + clearAndAddHttpError({ error: new Error('Invalid username or password. Please try again.') }); + } + if (error.response?.data?.errors?.some((e: any) => e.code === 'DisplayException')) { + clearAndAddHttpError({ error: new Error('Invalid username or password. Please try again.') }); + } else { + // Fall back to the server's error message or a generic CAPTCHA message + const errorMsg = error.response?.data?.message || 'An Unknown Error Occured.'; + clearAndAddHttpError({ error: new Error(errorMsg) }); + } }); }; return ( - {({ isSubmitting, setSubmitting, submitForm }) => ( + {({ isSubmitting }) => (
- {/* temp src */} - {/* */} - {/* */}

Login

- +
+ + {/* CAPTCHA Providers */} + {isTurnstileEnabled && ( +
+ handleCaptchaError('Turnstile')} + onExpire={handleCaptchaExpire} + options={{ + theme: 'dark', + size: 'flexible', + }} + /> +
+ )} + + {isFriendlyEnabled && friendlyLoaded && ( +
+ handleCaptchaError('FriendlyCaptcha')} + onExpire={handleCaptchaExpire} + /> +
+ )} + + {isHCaptchaEnabled && ( +
+ handleCaptchaError('hCaptcha')} + onExpire={handleCaptchaExpire} + theme='dark' + /> +
+ )} + + {isMCaptchaEnabled && ( +
+

mCaptcha implementation needed

+
+ )} +
- {recaptchaEnabled && ( - { - setToken(response); - // Ensure submitForm is called after token is updated - setTimeout(submitForm, 100); - }} - onExpire={() => { - setSubmitting(false); - setToken(''); - }} - /> - )}
)}
diff --git a/resources/scripts/state/settings.ts b/resources/scripts/state/settings.ts index 8087c5b7d..934c3eab3 100644 --- a/resources/scripts/state/settings.ts +++ b/resources/scripts/state/settings.ts @@ -1,12 +1,30 @@ import { Action, action } from 'easy-peasy'; +// Define captcha configuration type +interface CaptchaConfig { + driver: 'none' | 'hcaptcha' | 'mcaptcha' | 'turnstile' | 'friendly'; + hcaptcha: { + siteKey: string; + }; + mcaptcha: { + siteKey: string; + endpoint: string; + }; + turnstile: { + siteKey: string; + }; + friendly: { + siteKey: string; + }; + recaptcha: { + siteKey: string; + }; +} + export interface SiteSettings { name: string; locale: string; - recaptcha: { - enabled: boolean; - siteKey: string; - }; + captcha: CaptchaConfig; } export interface SettingsStore { @@ -16,7 +34,6 @@ export interface SettingsStore { const settings: SettingsStore = { data: undefined, - setSettings: action((state, payload) => { state.data = payload; }), diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php index 927e53c2a..35270c033 100644 --- a/resources/views/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -23,45 +23,45 @@
You are running Pyrodactyl panel version {{ config('app.version') }}.
-
-
-
-

--

-

CPU Usage

-
-
+
+
+

--

+

CPU Usage

+
+
-
-
-

--

-

Memory Usage

-
-
+
+
+

--

+

Memory Usage

+
+
-
-
-

--

-

Storage

-
-
+
+
+

--

+

Storage

+
+
-
-
-

--

-

System Uptime

-
-
+
+
+

--

+

System Uptime

+
--> +
diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php index d63498405..0e035241d 100644 --- a/resources/views/admin/nodes/view/settings.blade.php +++ b/resources/views/admin/nodes/view/settings.blade.php @@ -1,266 +1,385 @@ @extends('layouts.admin') @section('title') - {{ $node->name }}: Settings + {{ $node->name }}: Settings @endsection @section('content-header') -

{{ $node->name }}Configure your node settings.

- +

{{ $node->name }}Configure your node settings.

+ @endsection @section('content') - +
-
-
-
-

Settings

-
-
-
- -
- -

Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- public)) ? 'checked' : '' }} id="public_1" checked>
- public)) ? '' : 'checked' }} id="public_0"> -
-
-
- -
- -
-

- - Domain name that browsers will use to connect to Wings (e.g wings.example.com). - An IP address may be used only if you are not using SSL for this node. - Why? - -

-
-
- -
- -
-

- - Optional: - Leave blank to use the Public FQDN for panel-to-Wings communication. - If specified, this internal domain name will be used for panel-to-Wings communication instead - (e.g wings-internal.example.com or 10.0.0.5). - Useful for internal networks where the panel needs to communicate with Wings using a - different address than what browsers use. - -

-
-
- -
-
- scheme) === 'https') ? 'checked' : '' }}> - -
-
- scheme) !== 'https') ? 'checked' : '' }}> - -
-
-

In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

-
-
- -
-
- behind_proxy) == false) ? 'checked' : '' }}> - -
-
- behind_proxy) == true) ? 'checked' : '' }}> - -
-
-

If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

-
-
- -
-
- maintenance_mode) == false) ? 'checked' : '' }}> - -
-
- maintenance_mode) == true) ? 'checked' : '' }}> - -
-
-

If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.

-
-
-
+
+
+
+

Settings

+
+
+
+ +
+ +

Character limits: a-zA-Z0-9_.- and [Space] (min 1, + max 100 characters).

-
-
-
-

Allocation Limits

-
-
-
-
-
- -
- - MiB -
-
-
- -
- - % -
-
-
-

Enter the total amount of memory available on this node for allocation to servers. You may also provide a percentage that can allow allocation of more than the defined memory.

-
-
-
-
- -
- - MiB -
-
-
- -
- - % -
-
-
-

Enter the total amount of disk space available on this node for server allocation. You may also provide a percentage that will determine the amount of disk space over the set limit to allow.

-
-
-
-
-
-
-

General Configuration

-
-
-
- -
- - MiB -
-

Enter the maximum size of files that can be uploaded through the web-based file manager.

-
-
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-

The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process.

-
-
-
-
-
+
+ +
+
-
-
-
-

Save Settings

-
-
-
-
- -
-

Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

-
-
- -
+
+ +
+ +
+
+
+ +
+ public)) ? 'checked' : '' }} + id="public_1" checked>
+ public)) ? '' : 'checked' }} + id="public_0"> +
+
+
+ +
+ +
+

+ + Domain name that browsers will use to connect to Wings (e.g wings.example.com). + An IP address may be used only if you are not using SSL for this node. + Why? + +

+
+
+ +
+ +
+

+ + Optional: + Leave blank to use the Public FQDN for panel-to-Wings communication. + If specified, this internal domain name will be used for panel-to-Wings communication instead + (e.g wings-internal.example.com or 10.0.0.5). + Useful for internal networks where the panel needs to communicate with Wings using a + different address than what browsers use. + +

+
+
+ +
+
+ scheme) === 'https') ? 'checked' : '' }}> + +
+
+ scheme) !== 'https') ? 'checked' : '' }}> + +
+
+

In most cases you should select to use a SSL connection. If using an IP Address + or you do not wish to use SSL at all, select a HTTP connection.

+
+
+ +
+
+ behind_proxy) == false) ? 'checked' : '' }}> + +
+
+ behind_proxy) == true) ? 'checked' : '' }}> + +
+
+

If you are running the daemon behind a proxy such as Cloudflare, select this to + have the daemon skip looking for certificates on boot.

+
+
+ +
+
+ maintenance_mode) == false) ? 'checked' : '' }}> + +
+
+ maintenance_mode) == true) ? 'checked' : '' }}> + +
+
+

If the node is marked as 'Under Maintenance' users won't be able to access + servers that are on this node.

+
+
+
- +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ public)) ? 'checked' : '' }} id="public_1" + checked>
+ public)) ? '' : 'checked' }} id="public_0"> + +
+
+
+ +
+ +
+

Please enter domain name (e.g node.example.com) to be used for + connecting to the daemon. An IP address may only be used if you are not using SSL for this node. + Why? +

+
+
+ +
+
+ scheme) === 'https') ? 'checked' : '' }}> + +
+
+ scheme) !== 'https') ? 'checked' : '' }}> + +
+
+

In most cases you should select to use a SSL connection. If using an IP Address + or you do not wish to use SSL at all, select a HTTP connection.

+
+
+ +
+
+ behind_proxy) == false) ? 'checked' : '' }}> + +
+
+ behind_proxy) == true) ? 'checked' : '' }}> + +
+
+

If you are running the daemon behind a proxy such as Cloudflare, select this to + have the daemon skip looking for certificates on boot.

+
+
+ +
+
+ maintenance_mode) == false) ? 'checked' : '' }}> + +
+
+ maintenance_mode) == true) ? 'checked' : '' }}> + +
+
+

If the node is marked as 'Under Maintenance' users won't be able to access + servers that are on this node.

+
+
+
+
+
+
+
+

Allocation Limits

+
+
+
+
+
+ +
+ + MiB +
+
+
+ +
+ + % +
+
+
+

Enter the total amount of memory available on this node for allocation to + servers. You may also provide a percentage that can allow allocation of more than the defined memory.

+
+
+
+
+ +
+ + MiB +
+
+
+ +
+ + % +
+
+
+

Enter the total amount of disk space available on this node for server + allocation. You may also provide a percentage that will determine the amount of disk space over the set + limit to allow.

+
+
+
+
+
+
+
+

General Configuration

+
+
+
+ +
+ + MiB +
+

Enter the maximum size of files that can be uploaded through the web-based file + manager.

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+

The daemon runs its own SFTP management container and does not use the SSHd + process on the main physical server. Do not use the same port that you have assigned for + your physical server's SSH process.

+
+
+
+
+
+
+
+
+
+

Save Settings

+
+
+
+
+ +
+

Resetting the daemon master key will void any request coming from the old key. + This key is used for all sensitive operations on the daemon including server creation and deletion. We + suggest changing this key regularly for security.

+
+
+ +
+
+
+ @endsection @section('footer-scripts') - @parent - -@endsection + +@endsection \ No newline at end of file diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php index 4c3428bc3..fb9cf4115 100644 --- a/resources/views/admin/settings/advanced.blade.php +++ b/resources/views/admin/settings/advanced.blade.php @@ -18,54 +18,6 @@
-
-
-

reCAPTCHA

-
-
-
-
- -
- -

If enabled, login forms and password reset forms will do a silent captcha - check and display a visible captcha if needed.

-
-
-
- -
- -
-
-
- -
- -

Used for communication between your site and Google. Be sure to keep it a - secret.

-
-
-
- @if($showRecaptchaWarning) -
-
-
- You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is - recommended to generate new invisible reCAPTCHA - keys that tied specifically to your website. -
-
-
- @endif -
-

HTTP Connections

diff --git a/resources/views/admin/settings/captcha.blade.php b/resources/views/admin/settings/captcha.blade.php index 1c37794d1..4cfdf432f 100644 --- a/resources/views/admin/settings/captcha.blade.php +++ b/resources/views/admin/settings/captcha.blade.php @@ -6,7 +6,7 @@ @endsection @section('content-header') -

Captcha SettingsConfigure captcha settings for Pyrodactyl.

+

Captcha SettingsConfigure captcha settings for your panel.