mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 08:56:52 +01:00 
			
		
		
		
	Refactor captcha (#1068)
* refactor captcha * add default error message * prevent rule from being called multiple times * fixes * use config * Update this to latest * Remove this --------- Co-authored-by: Lance Pioch <git@lance.sh>
This commit is contained in:
		
							parent
							
								
									3e26a1cf09
								
							
						
					
					
						commit
						45db06a1bd
					
				
							
								
								
									
										118
									
								
								app/Extensions/Captcha/Providers/CaptchaProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								app/Extensions/Captcha/Providers/CaptchaProvider.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Extensions\Captcha\Providers;
 | 
			
		||||
 | 
			
		||||
use Filament\Forms\Components\Component;
 | 
			
		||||
use Filament\Forms\Components\TextInput;
 | 
			
		||||
use Illuminate\Foundation\Application;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
 | 
			
		||||
abstract class CaptchaProvider
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var array<string, static>
 | 
			
		||||
     */
 | 
			
		||||
    protected static array $providers = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return self|static[]
 | 
			
		||||
     */
 | 
			
		||||
    public static function get(?string $id = null): array|self
 | 
			
		||||
    {
 | 
			
		||||
        return $id ? static::$providers[$id] : static::$providers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function __construct(protected Application $app)
 | 
			
		||||
    {
 | 
			
		||||
        if (array_key_exists($this->getId(), static::$providers)) {
 | 
			
		||||
            if (!$this->app->runningUnitTests()) {
 | 
			
		||||
                logger()->warning("Tried to create duplicate Captcha provider with id '{$this->getId()}'");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        config()->set('captcha.' . Str::lower($this->getId()), $this->getConfig());
 | 
			
		||||
 | 
			
		||||
        static::$providers[$this->getId()] = $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract public function getId(): string;
 | 
			
		||||
 | 
			
		||||
    abstract public function getComponent(): Component;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|string[]|bool|null>
 | 
			
		||||
     */
 | 
			
		||||
    public function getConfig(): array
 | 
			
		||||
    {
 | 
			
		||||
        $id = Str::upper($this->getId());
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
 | 
			
		||||
            'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Component[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getSettingsForm(): array
 | 
			
		||||
    {
 | 
			
		||||
        $id = Str::upper($this->getId());
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            TextInput::make("CAPTCHA_{$id}_SITE_KEY")
 | 
			
		||||
                ->label('Site Key')
 | 
			
		||||
                ->placeholder('Site Key')
 | 
			
		||||
                ->columnSpan(2)
 | 
			
		||||
                ->required()
 | 
			
		||||
                ->password()
 | 
			
		||||
                ->revealable()
 | 
			
		||||
                ->autocomplete(false)
 | 
			
		||||
                ->default(env("CAPTCHA_{$id}_SITE_KEY")),
 | 
			
		||||
            TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
 | 
			
		||||
                ->label('Secret Key')
 | 
			
		||||
                ->placeholder('Secret Key')
 | 
			
		||||
                ->columnSpan(2)
 | 
			
		||||
                ->required()
 | 
			
		||||
                ->password()
 | 
			
		||||
                ->revealable()
 | 
			
		||||
                ->autocomplete(false)
 | 
			
		||||
                ->default(env("CAPTCHA_{$id}_SECRET_KEY")),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getName(): string
 | 
			
		||||
    {
 | 
			
		||||
        return Str::title($this->getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getIcon(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isEnabled(): bool
 | 
			
		||||
    {
 | 
			
		||||
        $id = Str::upper($this->getId());
 | 
			
		||||
 | 
			
		||||
        return env("CAPTCHA_{$id}_ENABLED", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|bool>
 | 
			
		||||
     */
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'message' => 'validateResponse not defined',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								app/Extensions/Captcha/Providers/TurnstileProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								app/Extensions/Captcha/Providers/TurnstileProvider.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Extensions\Captcha\Providers;
 | 
			
		||||
 | 
			
		||||
use App\Filament\Components\Forms\Fields\TurnstileCaptcha;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Filament\Forms\Components\Component;
 | 
			
		||||
use Filament\Forms\Components\Placeholder;
 | 
			
		||||
use Filament\Forms\Components\Toggle;
 | 
			
		||||
use Illuminate\Foundation\Application;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Illuminate\Support\HtmlString;
 | 
			
		||||
 | 
			
		||||
class TurnstileProvider extends CaptchaProvider
 | 
			
		||||
{
 | 
			
		||||
    public function getId(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'turnstile';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getComponent(): Component
 | 
			
		||||
    {
 | 
			
		||||
        return TurnstileCaptcha::make('turnstile');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|string[]|bool|null>
 | 
			
		||||
     */
 | 
			
		||||
    public function getConfig(): array
 | 
			
		||||
    {
 | 
			
		||||
        return array_merge(parent::getConfig(), [
 | 
			
		||||
            'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Component[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getSettingsForm(): array
 | 
			
		||||
    {
 | 
			
		||||
        return array_merge(parent::getSettingsForm(), [
 | 
			
		||||
            Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.verify'))
 | 
			
		||||
                ->columnSpan(2)
 | 
			
		||||
                ->inline(false)
 | 
			
		||||
                ->onIcon('tabler-check')
 | 
			
		||||
                ->offIcon('tabler-x')
 | 
			
		||||
                ->onColor('success')
 | 
			
		||||
                ->offColor('danger')
 | 
			
		||||
                ->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
 | 
			
		||||
            Placeholder::make('info')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.info_label'))
 | 
			
		||||
                ->columnSpan(2)
 | 
			
		||||
                ->content(new HtmlString(trans('admin/setting.captcha.info'))),
 | 
			
		||||
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getIcon(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'tabler-brand-cloudflare';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function register(Application $app): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self($app);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|bool>
 | 
			
		||||
     */
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): array
 | 
			
		||||
    {
 | 
			
		||||
        $captchaResponse ??= request()->get('cf-turnstile-response');
 | 
			
		||||
 | 
			
		||||
        if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
 | 
			
		||||
            throw new Exception('Turnstile secret key is not defined.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = Http::asJson()
 | 
			
		||||
            ->timeout(15)
 | 
			
		||||
            ->connectTimeout(5)
 | 
			
		||||
            ->throw()
 | 
			
		||||
            ->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
 | 
			
		||||
                'secret' => $secret,
 | 
			
		||||
                'response' => $captchaResponse,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        return count($response->json()) ? $response->json() : [
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'message' => 'Unknown error occurred, please try again',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $requestUrl ??= request()->url;
 | 
			
		||||
        $requestUrl = parse_url($requestUrl);
 | 
			
		||||
 | 
			
		||||
        return $hostname === array_get($requestUrl, 'host');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Filament\Admin\Pages;
 | 
			
		||||
 | 
			
		||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
 | 
			
		||||
use App\Extensions\OAuth\Providers\OAuthProvider;
 | 
			
		||||
use App\Models\Backup;
 | 
			
		||||
use App\Notifications\MailTested;
 | 
			
		||||
@ -13,7 +14,6 @@ use Filament\Forms\Components\Actions\Action as FormAction;
 | 
			
		||||
use Filament\Forms\Components\Component;
 | 
			
		||||
use Filament\Forms\Components\Group;
 | 
			
		||||
use Filament\Forms\Components\Hidden;
 | 
			
		||||
use Filament\Forms\Components\Placeholder;
 | 
			
		||||
use Filament\Forms\Components\Section;
 | 
			
		||||
use Filament\Forms\Components\Select;
 | 
			
		||||
use Filament\Forms\Components\Tabs;
 | 
			
		||||
@ -34,7 +34,6 @@ use Filament\Support\Enums\MaxWidth;
 | 
			
		||||
use Illuminate\Http\Client\Factory;
 | 
			
		||||
use Illuminate\Support\Facades\Artisan;
 | 
			
		||||
use Illuminate\Support\Facades\Notification as MailNotification;
 | 
			
		||||
use Illuminate\Support\HtmlString;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -219,47 +218,49 @@ class Settings extends Page implements HasForms
 | 
			
		||||
     */
 | 
			
		||||
    private function captchaSettings(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            Toggle::make('TURNSTILE_ENABLED')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.enable'))
 | 
			
		||||
                ->inline(false)
 | 
			
		||||
                ->columnSpan(1)
 | 
			
		||||
                ->onIcon('tabler-check')
 | 
			
		||||
                ->offIcon('tabler-x')
 | 
			
		||||
                ->onColor('success')
 | 
			
		||||
                ->offColor('danger')
 | 
			
		||||
                ->live()
 | 
			
		||||
                ->formatStateUsing(fn ($state): bool => (bool) $state)
 | 
			
		||||
                ->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_ENABLED', (bool) $state))
 | 
			
		||||
                ->default(env('TURNSTILE_ENABLED', config('turnstile.turnstile_enabled'))),
 | 
			
		||||
            Placeholder::make('info')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.info_label'))
 | 
			
		||||
                ->columnSpan(2)
 | 
			
		||||
                ->content(new HtmlString('<u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Link to Cloudflare Dashboard</a></u><br> ' . trans('admin/setting.captcha.info'))),
 | 
			
		||||
            TextInput::make('TURNSTILE_SITE_KEY')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.site_key'))
 | 
			
		||||
                ->required()
 | 
			
		||||
                ->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
 | 
			
		||||
                ->default(env('TURNSTILE_SITE_KEY', config('turnstile.turnstile_site_key')))
 | 
			
		||||
                ->placeholder('1x00000000000000000000AA'),
 | 
			
		||||
            TextInput::make('TURNSTILE_SECRET_KEY')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.secret_key'))
 | 
			
		||||
                ->required()
 | 
			
		||||
                ->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
 | 
			
		||||
                ->default(env('TURNSTILE_SECRET_KEY', config('turnstile.secret_key')))
 | 
			
		||||
                ->placeholder('1x0000000000000000000000000000000AA'),
 | 
			
		||||
            Toggle::make('TURNSTILE_VERIFY_DOMAIN')
 | 
			
		||||
                ->label(trans('admin/setting.captcha.verify'))
 | 
			
		||||
                ->inline(false)
 | 
			
		||||
                ->onIcon('tabler-check')
 | 
			
		||||
                ->offIcon('tabler-x')
 | 
			
		||||
                ->onColor('success')
 | 
			
		||||
                ->offColor('danger')
 | 
			
		||||
                ->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
 | 
			
		||||
                ->formatStateUsing(fn ($state): bool => (bool) $state)
 | 
			
		||||
                ->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_VERIFY_DOMAIN', (bool) $state))
 | 
			
		||||
                ->default(env('TURNSTILE_VERIFY_DOMAIN', config('turnstile.turnstile_verify_domain'))),
 | 
			
		||||
        ];
 | 
			
		||||
        $formFields = [];
 | 
			
		||||
 | 
			
		||||
        $captchaProviders = CaptchaProvider::get();
 | 
			
		||||
        foreach ($captchaProviders as $captchaProvider) {
 | 
			
		||||
            $id = Str::upper($captchaProvider->getId());
 | 
			
		||||
            $name = Str::title($captchaProvider->getId());
 | 
			
		||||
 | 
			
		||||
            $formFields[] = Section::make($name)
 | 
			
		||||
                ->columns(5)
 | 
			
		||||
                ->icon($captchaProvider->getIcon() ?? 'tabler-shield')
 | 
			
		||||
                ->collapsed(fn () => !env("CAPTCHA_{$id}_ENABLED", false))
 | 
			
		||||
                ->collapsible()
 | 
			
		||||
                ->schema([
 | 
			
		||||
                    Hidden::make("CAPTCHA_{$id}_ENABLED")
 | 
			
		||||
                        ->live()
 | 
			
		||||
                        ->default(env("CAPTCHA_{$id}_ENABLED")),
 | 
			
		||||
                    Actions::make([
 | 
			
		||||
                        FormAction::make("disable_captcha_$id")
 | 
			
		||||
                            ->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
 | 
			
		||||
                            ->label(trans('admin/setting.captcha.disable'))
 | 
			
		||||
                            ->color('danger')
 | 
			
		||||
                            ->action(function (Set $set) use ($id) {
 | 
			
		||||
                                $set("CAPTCHA_{$id}_ENABLED", false);
 | 
			
		||||
                            }),
 | 
			
		||||
                        FormAction::make("enable_captcha_$id")
 | 
			
		||||
                            ->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
 | 
			
		||||
                            ->label(trans('admin/setting.captcha.enable'))
 | 
			
		||||
                            ->color('success')
 | 
			
		||||
                            ->action(function (Set $set) use ($id, $captchaProviders) {
 | 
			
		||||
                                foreach ($captchaProviders as $captchaProvider) {
 | 
			
		||||
                                    $loopId = Str::upper($captchaProvider->getId());
 | 
			
		||||
                                    $set("CAPTCHA_{$loopId}_ENABLED", $loopId === $id);
 | 
			
		||||
                                }
 | 
			
		||||
                            }),
 | 
			
		||||
                    ])->columnSpan(1),
 | 
			
		||||
                    Group::make($captchaProvider->getSettingsForm())
 | 
			
		||||
                        ->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
 | 
			
		||||
                        ->columns(4)
 | 
			
		||||
                        ->columnSpan(4),
 | 
			
		||||
                ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $formFields;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										26
									
								
								app/Filament/Components/Forms/Fields/TurnstileCaptcha.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Filament/Components/Forms/Fields/TurnstileCaptcha.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Filament\Components\Forms\Fields;
 | 
			
		||||
 | 
			
		||||
use App\Rules\ValidTurnstileCaptcha;
 | 
			
		||||
use Filament\Forms\Components\Field;
 | 
			
		||||
 | 
			
		||||
class TurnstileCaptcha extends Field
 | 
			
		||||
{
 | 
			
		||||
    protected string $viewIdentifier = 'turnstile';
 | 
			
		||||
 | 
			
		||||
    protected string $view = 'filament.components.turnstile-captcha';
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 | 
			
		||||
        $this->hiddenLabel();
 | 
			
		||||
 | 
			
		||||
        $this->required();
 | 
			
		||||
 | 
			
		||||
        $this->after(function (TurnstileCaptcha $component) {
 | 
			
		||||
            $component->rule(new ValidTurnstileCaptcha());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Filament\Pages\Auth;
 | 
			
		||||
 | 
			
		||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
 | 
			
		||||
use App\Extensions\OAuth\Providers\OAuthProvider;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
 | 
			
		||||
use Filament\Facades\Filament;
 | 
			
		||||
use Filament\Forms\Components\Actions;
 | 
			
		||||
use Filament\Forms\Components\Actions\Action;
 | 
			
		||||
@ -83,22 +83,22 @@ class Login extends BaseLogin
 | 
			
		||||
 | 
			
		||||
    protected function getForms(): array
 | 
			
		||||
    {
 | 
			
		||||
        $schema = [
 | 
			
		||||
            $this->getLoginFormComponent(),
 | 
			
		||||
            $this->getPasswordFormComponent(),
 | 
			
		||||
            $this->getRememberFormComponent(),
 | 
			
		||||
            $this->getOAuthFormComponent(),
 | 
			
		||||
            $this->getTwoFactorAuthenticationComponent(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if ($captchaProvider = $this->getCaptchaComponent()) {
 | 
			
		||||
            $schema = array_merge($schema, [$captchaProvider]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'form' => $this->form(
 | 
			
		||||
                $this->makeForm()
 | 
			
		||||
                    ->schema([
 | 
			
		||||
                        $this->getLoginFormComponent(),
 | 
			
		||||
                        $this->getPasswordFormComponent(),
 | 
			
		||||
                        $this->getRememberFormComponent(),
 | 
			
		||||
                        $this->getOAuthFormComponent(),
 | 
			
		||||
                        $this->getTwoFactorAuthenticationComponent(),
 | 
			
		||||
                        Turnstile::make('captcha')
 | 
			
		||||
                            ->hidden(!config('turnstile.turnstile_enabled'))
 | 
			
		||||
                            ->validationMessages([
 | 
			
		||||
                                'required' => config('turnstile.error_messages.turnstile_check_message'),
 | 
			
		||||
                            ])
 | 
			
		||||
                            ->view('filament.plugins.turnstile'),
 | 
			
		||||
                    ])
 | 
			
		||||
                    ->schema($schema)
 | 
			
		||||
                    ->statePath('data'),
 | 
			
		||||
            ),
 | 
			
		||||
        ];
 | 
			
		||||
@ -113,6 +113,17 @@ class Login extends BaseLogin
 | 
			
		||||
            ->live();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getCaptchaComponent(): ?Component
 | 
			
		||||
    {
 | 
			
		||||
        $captchaProvider = collect(CaptchaProvider::get())->filter(fn (CaptchaProvider $provider) => $provider->isEnabled())->first();
 | 
			
		||||
 | 
			
		||||
        if (!$captchaProvider) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $captchaProvider->getComponent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function throwFailureValidationException(): never
 | 
			
		||||
    {
 | 
			
		||||
        $this->dispatch('reset-captcha');
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								app/Http/Middleware/VerifyCaptcha.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/Http/Middleware/VerifyCaptcha.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Middleware;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Foundation\Application;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Events\Auth\FailedCaptcha;
 | 
			
		||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\HttpException;
 | 
			
		||||
 | 
			
		||||
readonly class VerifyCaptcha
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private Application $app) {}
 | 
			
		||||
 | 
			
		||||
    public function handle(Request $request, Closure $next): mixed
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->app->isLocal()) {
 | 
			
		||||
            return $next($request);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $captchaProviders = collect(CaptchaProvider::get())->filter(fn (CaptchaProvider $provider) => $provider->isEnabled())->all();
 | 
			
		||||
        foreach ($captchaProviders as $captchaProvider) {
 | 
			
		||||
            $response = $captchaProvider->validateResponse();
 | 
			
		||||
 | 
			
		||||
            if ($response['success'] && $captchaProvider->verifyDomain($response['hostname'] ?? '', $request->url())) {
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            event(new FailedCaptcha($request->ip(), $response['message'] ?? null));
 | 
			
		||||
 | 
			
		||||
            throw new HttpException(Response::HTTP_BAD_REQUEST, "Failed to validate {$captchaProvider->getId()} captcha data.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No captcha enabled
 | 
			
		||||
        return $next($request);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,52 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Middleware;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Application;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Events\Auth\FailedCaptcha;
 | 
			
		||||
use Coderflex\LaravelTurnstile\Facades\LaravelTurnstile;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\HttpException;
 | 
			
		||||
 | 
			
		||||
readonly class VerifyReCaptcha
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private Application $app) {}
 | 
			
		||||
 | 
			
		||||
    public function handle(Request $request, \Closure $next): mixed
 | 
			
		||||
    {
 | 
			
		||||
        if (!config('turnstile.turnstile_enabled')) {
 | 
			
		||||
            return $next($request);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->app->isLocal()) {
 | 
			
		||||
            return $next($request);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($request->filled('cf-turnstile-response')) {
 | 
			
		||||
            $response = LaravelTurnstile::validate($request->get('cf-turnstile-response'));
 | 
			
		||||
 | 
			
		||||
            if ($response['success'] && $this->isResponseVerified($response['hostname'] ?? '', $request)) {
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        event(new FailedCaptcha($request->ip(), $response['message'] ?? null));
 | 
			
		||||
 | 
			
		||||
        throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate turnstile captcha data.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the response from the recaptcha servers was valid.
 | 
			
		||||
     */
 | 
			
		||||
    private function isResponseVerified(string $hostname, Request $request): bool
 | 
			
		||||
    {
 | 
			
		||||
        if (!config('turnstile.turnstile_verify_domain')) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $url = parse_url($request->url());
 | 
			
		||||
 | 
			
		||||
        return $hostname === array_get($url, 'host');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -38,6 +38,7 @@ use App\Checks\DatabaseCheck;
 | 
			
		||||
use App\Checks\DebugModeCheck;
 | 
			
		||||
use App\Checks\EnvironmentCheck;
 | 
			
		||||
use App\Checks\ScheduleCheck;
 | 
			
		||||
use App\Extensions\Captcha\Providers\TurnstileProvider;
 | 
			
		||||
use Spatie\Health\Facades\Health;
 | 
			
		||||
 | 
			
		||||
class AppServiceProvider extends ServiceProvider
 | 
			
		||||
@ -108,6 +109,9 @@ class AppServiceProvider extends ServiceProvider
 | 
			
		||||
        DiscordProvider::register($app);
 | 
			
		||||
        SteamProvider::register($app);
 | 
			
		||||
 | 
			
		||||
        // Default Captcha provider
 | 
			
		||||
        TurnstileProvider::register($app);
 | 
			
		||||
 | 
			
		||||
        FilamentColor::register([
 | 
			
		||||
            'danger' => Color::Red,
 | 
			
		||||
            'gray' => Color::Zinc,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								app/Rules/ValidTurnstileCaptcha.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Rules/ValidTurnstileCaptcha.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Rules;
 | 
			
		||||
 | 
			
		||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Contracts\Validation\ValidationRule;
 | 
			
		||||
 | 
			
		||||
class ValidTurnstileCaptcha implements ValidationRule
 | 
			
		||||
{
 | 
			
		||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
			
		||||
    {
 | 
			
		||||
        $response = CaptchaProvider::get('turnstile')->validateResponse($value);
 | 
			
		||||
 | 
			
		||||
        if (!$response['success']) {
 | 
			
		||||
            $fail($response['message'] ?? 'Unknown error occurred, please try again');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -44,7 +44,7 @@ return Application::configure(basePath: dirname(__DIR__))
 | 
			
		||||
            'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
 | 
			
		||||
            'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
 | 
			
		||||
            'node.maintenance' => \App\Http\Middleware\MaintenanceMiddleware::class,
 | 
			
		||||
            'recaptcha' => \App\Http\Middleware\VerifyReCaptcha::class,
 | 
			
		||||
            'captcha' => \App\Http\Middleware\VerifyCaptcha::class,
 | 
			
		||||
        ]);
 | 
			
		||||
    })
 | 
			
		||||
    ->withSingletons([
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
        "aws/aws-sdk-php": "^3.341",
 | 
			
		||||
        "calebporzio/sushi": "^2.5",
 | 
			
		||||
        "chillerlan/php-qrcode": "^5.0.2",
 | 
			
		||||
        "coderflex/filament-turnstile": "^2.2",
 | 
			
		||||
        "dedoc/scramble": "^0.12.10",
 | 
			
		||||
        "doctrine/dbal": "~3.6.0",
 | 
			
		||||
        "filament/filament": "3.3.3",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										155
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										155
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							@ -4,7 +4,7 @@
 | 
			
		||||
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
 | 
			
		||||
        "This file is @generated automatically"
 | 
			
		||||
    ],
 | 
			
		||||
    "content-hash": "b630e514f50a47d69caf251330dcbc01",
 | 
			
		||||
    "content-hash": "7bdf17dde3abc6f87fd48674c3034f49",
 | 
			
		||||
    "packages": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "abdelhamiderrahmouni/filament-monaco-editor",
 | 
			
		||||
@ -1607,159 +1607,6 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2024-07-16T11:13:48+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "coderflex/filament-turnstile",
 | 
			
		||||
            "version": "v2.3.1",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/coderflexx/filament-turnstile.git",
 | 
			
		||||
                "reference": "a49cf626c7ba88457761b7594daf16c532f6adb1"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/coderflexx/filament-turnstile/zipball/a49cf626c7ba88457761b7594daf16c532f6adb1",
 | 
			
		||||
                "reference": "a49cf626c7ba88457761b7594daf16c532f6adb1",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "coderflex/laravel-turnstile": "^1.0|^2.0",
 | 
			
		||||
                "illuminate/contracts": "^10.0|^11.0|^12.0",
 | 
			
		||||
                "php": "^8.2|^8.3",
 | 
			
		||||
                "spatie/laravel-package-tools": "^1.14.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "filament/filament": "^3.0",
 | 
			
		||||
                "larastan/larastan": "^2.8|^3.0",
 | 
			
		||||
                "laravel/pint": "^1.0",
 | 
			
		||||
                "nunomaduro/collision": "^7.0|^8.0",
 | 
			
		||||
                "nunomaduro/larastan": "^2.8.0|^3.1.0",
 | 
			
		||||
                "orchestra/testbench": "^8.0|^9.0|^10.0",
 | 
			
		||||
                "pestphp/pest": "^2.0|^3.7",
 | 
			
		||||
                "pestphp/pest-plugin-arch": "^2.0|^3.0",
 | 
			
		||||
                "pestphp/pest-plugin-livewire": "^2.0|^3.0",
 | 
			
		||||
                "phpstan/extension-installer": "^1.1",
 | 
			
		||||
                "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
 | 
			
		||||
                "phpstan/phpstan-phpunit": "^1.0|^2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "laravel": {
 | 
			
		||||
                    "aliases": {
 | 
			
		||||
                        "FilamentTurnstile": "Coderflex\\FilamentTurnstile\\Facades\\FilamentTurnstile"
 | 
			
		||||
                    },
 | 
			
		||||
                    "providers": [
 | 
			
		||||
                        "Coderflex\\FilamentTurnstile\\FilamentTurnstileServiceProvider"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Coderflex\\FilamentTurnstile\\": "src/",
 | 
			
		||||
                    "Coderflex\\FilamentTurnstile\\Database\\Factories\\": "database/factories/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Oussama",
 | 
			
		||||
                    "email": "oussama@coderflex.com",
 | 
			
		||||
                    "role": "Developer"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Filament Plugin to help you implement Cloudflare Turnstile",
 | 
			
		||||
            "homepage": "https://github.com/coderflex/filament-turnstile",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "cloudflare",
 | 
			
		||||
                "coderflex",
 | 
			
		||||
                "filament",
 | 
			
		||||
                "filament-turnstile",
 | 
			
		||||
                "laravel",
 | 
			
		||||
                "laravel-turnstile",
 | 
			
		||||
                "turnstile"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/coderflexx/filament-turnstile/issues",
 | 
			
		||||
                "source": "https://github.com/coderflexx/filament-turnstile/tree/v2.3.1"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2025-03-01T16:11:30+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "coderflex/laravel-turnstile",
 | 
			
		||||
            "version": "2.1.1",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/coderflexx/laravel-turnstile.git",
 | 
			
		||||
                "reference": "1c1d0c5829851efaa1febcde34733537780eb0a9"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/coderflexx/laravel-turnstile/zipball/1c1d0c5829851efaa1febcde34733537780eb0a9",
 | 
			
		||||
                "reference": "1c1d0c5829851efaa1febcde34733537780eb0a9",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "guzzlehttp/guzzle": "^7.7",
 | 
			
		||||
                "illuminate/contracts": "^10.0|^11.0|^12.0",
 | 
			
		||||
                "php": "^8.2|^8.3",
 | 
			
		||||
                "spatie/laravel-package-tools": "^1.14.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "laravel/pint": "^1.0",
 | 
			
		||||
                "nunomaduro/collision": "^7.0|^8.0",
 | 
			
		||||
                "nunomaduro/larastan": "^2.8.0|^3.1.0",
 | 
			
		||||
                "orchestra/testbench": "^8.0|^9.0|^10.0",
 | 
			
		||||
                "pestphp/pest": "^2.0|^3.7",
 | 
			
		||||
                "pestphp/pest-plugin-arch": "^2.0|^3.0",
 | 
			
		||||
                "phpstan/extension-installer": "^1.1",
 | 
			
		||||
                "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
 | 
			
		||||
                "phpstan/phpstan-phpunit": "^1.0|^2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "laravel": {
 | 
			
		||||
                    "aliases": {
 | 
			
		||||
                        "LaravelTurnstile": "Coderflex\\LaravelTurnstile\\Facades\\LaravelTurnstile"
 | 
			
		||||
                    },
 | 
			
		||||
                    "providers": [
 | 
			
		||||
                        "Coderflex\\LaravelTurnstile\\LaravelTurnstileServiceProvider"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Coderflex\\LaravelTurnstile\\": "src/",
 | 
			
		||||
                    "Coderflex\\LaravelTurnstile\\Database\\Factories\\": "database/factories/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "ousid",
 | 
			
		||||
                    "email": "oussama@coderflex.com",
 | 
			
		||||
                    "role": "Developer"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "A package to help you implement the Cloudflare turnstile \"CAPTCHA Alternative\"",
 | 
			
		||||
            "homepage": "https://github.com/coderflexx/laravel-turnstile",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "cloudflare",
 | 
			
		||||
                "coderflex",
 | 
			
		||||
                "laravel",
 | 
			
		||||
                "laravel-turnstile",
 | 
			
		||||
                "turnstile"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/coderflexx/laravel-turnstile/issues",
 | 
			
		||||
                "source": "https://github.com/coderflexx/laravel-turnstile/tree/2.1.1"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2025-03-01T13:04:55+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "danharrin/date-format-converter",
 | 
			
		||||
            "version": "v0.3.1",
 | 
			
		||||
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    'turnstile_enabled' => env('TURNSTILE_ENABLED', false),
 | 
			
		||||
 | 
			
		||||
    'turnstile_site_key' => env('TURNSTILE_SITE_KEY', null),
 | 
			
		||||
    'turnstile_secret_key' => env('TURNSTILE_SECRET_KEY', null),
 | 
			
		||||
 | 
			
		||||
    'turnstile_verify_domain' => env('TURNSTILE_VERIFY_DOMAIN', true),
 | 
			
		||||
 | 
			
		||||
    'error_messages' => [
 | 
			
		||||
        'turnstile_check_message' => 'Captcha failed! Please refresh and try again.',
 | 
			
		||||
    ],
 | 
			
		||||
];
 | 
			
		||||
@ -34,9 +34,10 @@ return [
 | 
			
		||||
        'display_width' => 'Display Width',
 | 
			
		||||
    ],
 | 
			
		||||
    'captcha' => [
 | 
			
		||||
        'enable' => 'Enable Turnstile Captcha?',
 | 
			
		||||
        'enable' => 'Enable',
 | 
			
		||||
        'disable' => 'Disable',
 | 
			
		||||
        'info_label' => 'Info',
 | 
			
		||||
        'info' => 'You can generate the keys on your Cloudflare Dashboard. A Cloudflare account is required.',
 | 
			
		||||
        'info' => 'You can generate the keys on your <u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Cloudflare Dashboard</a></u>. A Cloudflare account is required.',
 | 
			
		||||
        'site_key' => 'Site Key',
 | 
			
		||||
        'secret_key' => 'Secret Key',
 | 
			
		||||
        'verify' => 'Verify Domain?',
 | 
			
		||||
 | 
			
		||||
@ -20,5 +20,6 @@ parameters:
 | 
			
		||||
            identifier: larastan.noEnvCallsOutsideOfConfig
 | 
			
		||||
            paths:
 | 
			
		||||
                - app/Console/Commands/Environment/*.php
 | 
			
		||||
                - app/Extensions/Captcha/Providers/*.php
 | 
			
		||||
                - app/Extensions/OAuth/Providers/*.php
 | 
			
		||||
                - app/Filament/Admin/Pages/Settings.php
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,14 @@
 | 
			
		||||
@php
 | 
			
		||||
    $statePath = $getStatePath();
 | 
			
		||||
    $fieldWrapperView = $getFieldWrapperView();
 | 
			
		||||
 | 
			
		||||
    $theme = $getTheme();
 | 
			
		||||
    $size = $getSize();
 | 
			
		||||
    $language = $getLanguage();
 | 
			
		||||
@endphp
 | 
			
		||||
 | 
			
		||||
<x-dynamic-component class="flex justify-center" :component="$fieldWrapperView" :field="$turnstile">
 | 
			
		||||
 | 
			
		||||
    <div x-data="{
 | 
			
		||||
            state: $wire.entangle('{{ $statePath }}').defer 
 | 
			
		||||
            state: $wire.entangle('{{ $statePath }}').defer
 | 
			
		||||
        }"
 | 
			
		||||
        wire:ignore
 | 
			
		||||
        x-init="(() => {
 | 
			
		||||
         wire:ignore
 | 
			
		||||
         x-init="(() => {
 | 
			
		||||
            let options= {
 | 
			
		||||
                callback: function (token) {
 | 
			
		||||
                    $wire.set('{{ $statePath }}', token)
 | 
			
		||||
@ -33,13 +28,7 @@
 | 
			
		||||
            }
 | 
			
		||||
        })()"
 | 
			
		||||
    >
 | 
			
		||||
        <div data-sitekey="{{config('turnstile.turnstile_site_key')}}"
 | 
			
		||||
            data-theme="{{ $theme }}"
 | 
			
		||||
            data-language="{{ $language }}"
 | 
			
		||||
            data-size="{{ $size }}"
 | 
			
		||||
            x-ref="turnstile"
 | 
			
		||||
            >
 | 
			
		||||
        </div>
 | 
			
		||||
        <div data-sitekey="{{config('captcha.turnstile.site_key')}}" x-ref="turnstile"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
 | 
			
		||||
@ -48,9 +37,9 @@
 | 
			
		||||
        <script>
 | 
			
		||||
            document.addEventListener('livewire:init', () => {
 | 
			
		||||
                Livewire.on('reset-captcha', (event) => {
 | 
			
		||||
                    resetCaptcha()
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
                    resetCaptcha();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        </script>
 | 
			
		||||
    @endpush
 | 
			
		||||
</x-dynamic-component>
 | 
			
		||||
</x-dynamic-component>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user