generated from boboko/starter
67 lines
2.3 KiB
PHP
67 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace App\Rules;
|
|
|
|
use Closure;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
use Illuminate\Http\Client\ConnectionException;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Verifies the `h-captcha-response` token the hCaptcha widget adds to a form.
|
|
* Use it as `'h-captcha-response' => ['required', new HCaptcha]`.
|
|
*
|
|
* Fails closed: if hCaptcha can't be reached the submission is rejected, since
|
|
* letting it through would reopen the hole this exists to close (bots making
|
|
* us send email to arbitrary addresses).
|
|
*/
|
|
class HCaptcha implements ValidationRule
|
|
{
|
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
{
|
|
if (! is_string($value) || $value === '') {
|
|
$fail(__('storefront.auth.captcha_failed'));
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$response = Http::asForm()
|
|
->timeout(5)
|
|
->post('https://api.hcaptcha.com/siteverify', [
|
|
'secret' => config('services.hcaptcha.secret'),
|
|
'response' => $value,
|
|
// Rejects tokens solved against someone else's sitekey.
|
|
'sitekey' => config('services.hcaptcha.sitekey'),
|
|
'remoteip' => request()->ip(),
|
|
]);
|
|
} catch (ConnectionException $e) {
|
|
Log::warning('hCaptcha siteverify unreachable', ['error' => $e->getMessage()]);
|
|
$fail(__('storefront.auth.captcha_failed'));
|
|
|
|
return;
|
|
}
|
|
|
|
if (! $response->successful() || $response->json('success') !== true) {
|
|
// A bad/missing secret or sitekey would otherwise look like every
|
|
// shopper failing the captcha.
|
|
$configErrors = array_intersect((array) $response->json('error-codes'), [
|
|
'missing-input-secret',
|
|
'invalid-input-secret',
|
|
'sitekey-secret-mismatch',
|
|
'invalid-sitekey',
|
|
]);
|
|
|
|
if ($response->failed() || $configErrors) {
|
|
Log::warning('hCaptcha siteverify error', [
|
|
'status' => $response->status(),
|
|
'error-codes' => $response->json('error-codes'),
|
|
]);
|
|
}
|
|
|
|
$fail(__('storefront.auth.captcha_failed'));
|
|
}
|
|
}
|
|
}
|