mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-19 14:04:45 +02: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