diff --git a/composer.json b/composer.json index 91e8def..c00fefe 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,9 @@ "php": "^8.2", "barryvdh/laravel-form-bridge": "^0.7.4" }, + "suggest": { + "josiasmontag/laravel-recaptchav3": "Required to use the RecaptchaV3 field type (Laravel only)" + }, "require-dev": { "phpstan/phpstan": "^2.1", "phpunit/phpunit": ">10", diff --git a/docs/README.md b/docs/README.md index afe62d5..e00471d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,17 +34,16 @@ To notify a user of an error with their form submission: Symfony Form will automatically display an error message next to the input field. Error messages should have a unique ID and be associated with the form input via the `aria-describedby` attribute. This is provided in the accessible form theme. - ## Docs - [Using accessible forms with Laravel](laravel.md) - [Using accessible forms with Symfony](symfony.md) -- [Form options](form-options.md) - Form helpers to create a form - Accessible form theme - GOV.UK Design System form theme - [Indicate form errors in the page title](page-title.md) - [Error summary](error-summary.md) +- [reCAPTCHA v3 field](recaptcha.md) - Every input field needs a label - Indicate required fields - Use field hints instead of placeholders diff --git a/docs/recaptcha.md b/docs/recaptcha.md new file mode 100644 index 0000000..e88a21e --- /dev/null +++ b/docs/recaptcha.md @@ -0,0 +1,79 @@ +# reCAPTCHA v3 field + +The `RecaptchaV3` field type adds invisible [Google reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) spam protection to a form. +It is currently Laravel only. + +reCAPTCHA v3 does not ask the user to solve a challenge, so it adds no extra steps for keyboard, screen reader or other assistive technology users. + +The token is requested when the form is submitted rather than when the page loads. reCAPTCHA tokens expire after 2 minutes, so fetching +one on page load would cause the form to fail for anyone who takes longer than that to complete it. + +## Installation + +Install [laravel-recaptchav3](https://github.com/josiasmontag/laravel-recaptchav3) via [Composer](https://getcomposer.org/): + +```shell +composer require josiasmontag/laravel-recaptchav3 +``` + +Add your keys to `.env`: + +``` +RECAPTCHAV3_SITEKEY=sitekey +RECAPTCHAV3_SECRET=secret +``` + +Load the reCAPTCHA script on any page that uses the field, for example in your layout template: + +```twig +{{ recaptchaJs()|raw }} +``` + +Where `recaptchaJs()` is a Twig function that returns `RecaptchaV3::initJs()`. + +## Usage + +Add the field to your form class, usually just before the submit button: + +```php +use Studio24\AccessibleForms\FieldTypes\RecaptchaV3; + +$builder + ->add('recaptcha', RecaptchaV3::class, [ + 'action' => 'contact', + ]) + ->add('save', SubmitType::class, ['label' => 'Send']); +``` + +The token is validated when the form is submitted via `$form->isValid()`. No extra controller code is needed. + +## Options + +| Option | Default | Description | +|-----------|------------|-------------------------------------------------------------------------------------------------------| +| `action` | `submit` | reCAPTCHA action name, shown in the reCAPTCHA admin console. Letters, numbers, `/` and `_` only. | +| `score` | `0.5` | Minimum score (0.0 to 1.0) needed to pass validation. | +| `message` | See below | Error message shown in the error summary and above the field if verification fails. | +| `nonce` | `null` | Content Security Policy nonce added to the inline ` +{%- endblock recaptcha_v3_row -%} + diff --git a/src/Validation/RecaptchaV3Rule.php b/src/Validation/RecaptchaV3Rule.php new file mode 100644 index 0000000..2268711 --- /dev/null +++ b/src/Validation/RecaptchaV3Rule.php @@ -0,0 +1,57 @@ +message); + return; + } + + try { + $score = app(RecaptchaV3::class)->verify($value, $this->action); + } catch (Throwable $e) { + report($e); + $score = false; + } + + if ($score === false || $score < $this->score) { + $fail($this->message); + } + } + + /** + * laravel-form-bridge's RulesParser treats every rule as a string when building the view + */ + public function __toString(): string + { + return 'recaptcha_v3'; + } +}