mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 15:56:51 +01:00 
			
		
		
		
	Improve turnstile error handling (+ cleanup) (#1501)
This commit is contained in:
		
							parent
							
								
									5e8cccef19
								
							
						
					
					
						commit
						5a7c6ac6e5
					
				@ -56,9 +56,4 @@ abstract class BaseSchema
 | 
			
		||||
                ->default(env("CAPTCHA_{$id}_SECRET_KEY")),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,10 +26,5 @@ interface CaptchaSchemaInterface
 | 
			
		||||
 | 
			
		||||
    public function getIcon(): ?string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|bool>
 | 
			
		||||
     */
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): array;
 | 
			
		||||
 | 
			
		||||
    public function verifyDomain(string $hostname, ?string $requestUrl = null): bool;
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ namespace App\Extensions\Captcha\Schemas\Turnstile;
 | 
			
		||||
 | 
			
		||||
use App\Extensions\Captcha\CaptchaService;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Illuminate\Contracts\Validation\ValidationRule;
 | 
			
		||||
use Illuminate\Support\Facades\App;
 | 
			
		||||
 | 
			
		||||
@ -11,10 +12,12 @@ class Rule implements ValidationRule
 | 
			
		||||
{
 | 
			
		||||
    public function validate(string $attribute, mixed $value, Closure $fail): void
 | 
			
		||||
    {
 | 
			
		||||
        $response = App::call(fn (CaptchaService $service) => $service->getActiveSchema()->validateResponse($value));
 | 
			
		||||
        try {
 | 
			
		||||
            App::call(fn (CaptchaService $service) => $service->get('turnstile')->validateResponse($value));
 | 
			
		||||
        } catch (Exception $exception) {
 | 
			
		||||
            report($exception);
 | 
			
		||||
 | 
			
		||||
        if (!$response['success']) {
 | 
			
		||||
            $fail($response['message'] ?? 'Unknown error occurred, please try again');
 | 
			
		||||
            $fail('Captcha validation failed: ' . $exception->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -66,9 +66,9 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array<string, string|bool>
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): array
 | 
			
		||||
    public function validateResponse(?string $captchaResponse = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $captchaResponse ??= request()->get('cf-turnstile-response');
 | 
			
		||||
 | 
			
		||||
@ -83,22 +83,33 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
 | 
			
		||||
            ->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
 | 
			
		||||
                'secret' => $secret,
 | 
			
		||||
                'response' => $captchaResponse,
 | 
			
		||||
            ]);
 | 
			
		||||
            ])
 | 
			
		||||
            ->json();
 | 
			
		||||
 | 
			
		||||
        return count($response->json()) ? $response->json() : [
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'message' => 'Unknown error occurred, please try again',
 | 
			
		||||
        ];
 | 
			
		||||
        if (!$response['success']) {
 | 
			
		||||
            match ($response['error-codes'][0] ?? null) {
 | 
			
		||||
                'missing-input-secret' => throw new Exception('The secret parameter was not passed.'),
 | 
			
		||||
                'invalid-input-secret' => throw new Exception('The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.'),
 | 
			
		||||
                'missing-input-response' => throw new Exception('The response parameter (token) was not passed.'),
 | 
			
		||||
                'invalid-input-response' => throw new Exception('The response parameter (token) is invalid or has expired.'),
 | 
			
		||||
                'bad-request' => throw new Exception('The request was rejected because it was malformed.'),
 | 
			
		||||
                'timeout-or-duplicate' => throw new Exception('The response parameter (token) has already been validated before.'),
 | 
			
		||||
                default => throw new Exception('An internal error happened while validating the response.'),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$this->verifyDomain($response['hostname'] ?? '')) {
 | 
			
		||||
            throw new Exception('Domain verification failed.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
 | 
			
		||||
    private function verifyDomain(string $hostname): bool
 | 
			
		||||
    {
 | 
			
		||||
        if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $requestUrl ??= request()->url;
 | 
			
		||||
        $requestUrl = parse_url($requestUrl);
 | 
			
		||||
        $requestUrl = parse_url(request()->url());
 | 
			
		||||
 | 
			
		||||
        return $hostname === array_get($requestUrl, 'host');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,39 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Middleware;
 | 
			
		||||
 | 
			
		||||
use App\Extensions\Captcha\CaptchaService;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Foundation\Application;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use App\Events\Auth\FailedCaptcha;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\HttpException;
 | 
			
		||||
 | 
			
		||||
readonly class VerifyCaptcha
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private Application $app) {}
 | 
			
		||||
 | 
			
		||||
    public function handle(Request $request, Closure $next, CaptchaService $captchaService): mixed
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->app->isLocal()) {
 | 
			
		||||
            return $next($request);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $schemas = $captchaService->getActiveSchemas();
 | 
			
		||||
        foreach ($schemas as $schema) {
 | 
			
		||||
            $response = $schema->validateResponse();
 | 
			
		||||
 | 
			
		||||
            if ($response['success'] && $schema->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 {$schema->getId()} captcha data.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No captcha enabled
 | 
			
		||||
        return $next($request);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -44,7 +44,6 @@ 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,
 | 
			
		||||
            'captcha' => \App\Http\Middleware\VerifyCaptcha::class,
 | 
			
		||||
        ]);
 | 
			
		||||
    })
 | 
			
		||||
    ->withSingletons([
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user