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:
Boy132 2025-03-15 20:52:38 +01:00 committed by GitHub
parent 3e26a1cf09
commit 45db06a1bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 395 additions and 301 deletions

View 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;
}
}

View 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');
}
}

View File

@ -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;
}
/**

View 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());
});
}
}

View File

@ -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');

View 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);
}
}

View File

@ -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');
}
}

View File

@ -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,

View 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');
}
}
}

View File

@ -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([

View File

@ -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
View File

@ -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",

View File

@ -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.',
],
];

View File

@ -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?',

View File

@ -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

View File

@ -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>