Core 1435 add recaptcha to forms#2827
Conversation
64594f0 to
26fa22e
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds Google reCAPTCHA v3 protection to user-facing forms in the OpenStax application to prevent spam and abuse. The implementation introduces a custom React hook (useRecaptchaToken) that manages token generation and provides a gating mechanism where users must click an "I am a human" button before accessing the form's submit button.
Changes:
- Added reCAPTCHA v3 integration with a custom React hook for token management
- Protected contact form and multi-page forms (adoption/interest) with reCAPTCHA gating
- Added comprehensive test coverage including unit tests and mock helper for testing
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/components/recaptcha.tsx | Core implementation of the useRecaptchaToken hook that manages token lifecycle and UI gating |
| src/app/pages/contact/form.tsx | Integrates reCAPTCHA into the contact form with hidden token field |
| src/app/components/multi-page-form/buttons.tsx | Wraps submit button in Recaptcha component for multi-page forms |
| src/index.html | Loads Google reCAPTCHA v3 script with site key |
| test/src/components/recaptcha.test.tsx | Unit tests for the reCAPTCHA hook functionality |
| test/src/helpers/mock-recaptcha.tsx | Mock implementation for testing components that use reCAPTCHA |
| test/src/pages/*.test.tsx | Updates existing tests to import the reCAPTCHA mock |
| test/src/components/multi-page-form.test.tsx | Updates test assertion to account for reCAPTCHA changes |
| package.json | Adds @types/grecaptcha type definitions |
| yarn.lock | Lockfile update for new dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
71b3f0b to
374a2d9
Compare
374a2d9 to
9cac8d3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // @ts-expect-error not all properties defined | ||
| window.grecaptcha = { | ||
| ready: mockReady, | ||
| execute: mockExecute | ||
| }; | ||
|
|
There was a problem hiding this comment.
useRecaptchaToken checks the global identifier grecaptcha (not window.grecaptcha). In Jest/jsdom, assigning window.grecaptcha = ... does not necessarily define a global grecaptcha, so fetchToken may treat it as unavailable and never call execute. To make the test environment match browser semantics, assign the mock to globalThis.grecaptcha (or explicitly define a global grecaptcha) in addition to window.grecaptcha.
| // @ts-expect-error not all properties defined | |
| window.grecaptcha = { | |
| ready: mockReady, | |
| execute: mockExecute | |
| }; | |
| const grecaptchaMock = { | |
| ready: mockReady, | |
| execute: mockExecute | |
| }; | |
| // @ts-expect-error not all properties defined | |
| // Ensure both window.grecaptcha and the global grecaptcha identifier are set | |
| // so that useRecaptchaToken can find the mock in the Jest/jsdom environment. | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (window as any).grecaptcha = grecaptchaMock; | |
| // @ts-expect-error not all properties defined | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (globalThis as any).grecaptcha = grecaptchaMock; |
| <p>{error}</p> | ||
| </div>; | ||
| } | ||
| return <button type="button" disabled={fetching} onClick={fetchToken}> |
There was a problem hiding this comment.
When error is set, Recaptcha renders only the error message and removes the button, but the only way to clear the error is by calling fetchToken again—which is no longer possible. Consider rendering a retry button (or keeping the existing button) while showing the error so users can re-attempt token generation.
| return <button type="button" disabled={fetching} onClick={fetchToken}> | |
| <p>{error}</p> | |
| <button type="button" disabled={fetching} onClick={fetchToken}> | |
| Try again | |
| </button> |
Add hidden fields to submit token Name token field Handle grecaptcha failure to load Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com>
9cac8d3 to
5d88860
Compare
CORE-1435
Uses the custom grecaptcha routines to put the Submit button behind an "I am human" button, which, when clicked, creates the token, which is populated into a hidden field and included in the form data.