From b059c7e7ea246c63fa0474e8fcfa2b1f394fb278 Mon Sep 17 00:00:00 2001 From: Ian Skelskey Date: Mon, 17 Feb 2025 20:51:13 -0500 Subject: [PATCH] Add centralized reCAPTCHA v3 handling and improve OPAC form scripts - Centralized reCAPTCHA v3 logic into a new `recaptcha.js` module for streamlined verification across forms. - Corrected setting name for enabling reCAPTCHA: `recaptcha.enable` to `recaptcha.enabled`. - Refactored OPAC searchbar template to improve form submission handling and removed inline scripts. - Enhanced `recaptcha.js` with detailed logging for better debugging. - Updated documentation with comprehensive setup SQL and placeholder reCAPTCHA keys for security. Release-Note: Centralize reCAPTCHA v3 handling, improve form scripts, and update documentation. Testing Plan: - Verify reCAPTCHA appears on registration and search forms when enabled. - Confirm form submission is blocked on failed reCAPTCHA. - Check correct reCAPTCHA response logging in the console. Signed-off-by: Ian Skelskey --- .../opac/parts/recaptcha.tt2 | 68 ++---------------- .../opac/parts/searchbar.tt2 | 51 +++++++++---- .../src/templates-bootstrap/opac/register.tt2 | 2 +- Open-ILS/web/opac/common/js/recaptcha.js | 71 +++++++++++++++++++ docs/modules/admin/pages/recaptcha.adoc | 40 ++++++++++- 5 files changed, 151 insertions(+), 81 deletions(-) create mode 100644 Open-ILS/web/opac/common/js/recaptcha.js diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 830a30249e..038c194d67 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -1,17 +1,10 @@ - [% org_unit = ctx.search_ou; recaptcha_site_key = ctx.get_org_setting(org_unit, 'recaptcha.site_key'); action_name = action_name || 'register'; # Default action name if none is provided submit_action = submit_action || 'submit'; # Default submit action if none is provided target_element_id = target_element_id || 'recaptcha-form'; # Default target element ID - recaptcha_enabled = ctx.get_org_setting(ctx.search_ou, 'recaptcha.enable'); + recaptcha_enabled = ctx.get_org_setting(ctx.search_ou, 'recaptcha.enabled'); %] [% IF recaptcha_enabled && recaptcha_enabled == 1 %] @@ -19,64 +12,11 @@ + [% ELSE %] @@ -85,4 +25,4 @@ console.log('recaptcha_enabled:', '[% recaptcha_enabled %]'); console.log('reCAPTCHA is not enabled for this organization unit.'); -[% END %] +[% END %] \ No newline at end of file diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/searchbar.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/searchbar.tt2 index d28701eaf5..f814b7a309 100755 --- a/Open-ILS/src/templates-bootstrap/opac/parts/searchbar.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/searchbar.tt2 @@ -31,7 +31,7 @@ END;
[% UNLESS took_care_of_form -%] -
+ [%- END %] [% IF ctx.page == 'rresult' && ctx.metarecord && search.metarecord_default %] @@ -110,17 +110,22 @@ END;
- - + [%- IF ctx.depth_sel_button AND NOT took_care_of_form %] - + [%- END %]
+ [% INCLUDE "opac/parts/recaptcha.tt2" + action_name="opac_search" + submit_action="submit" + target_element_id="opac-search-form" + %] + [% IF ctx.bookbag %]
- - +
\ No newline at end of file diff --git a/Open-ILS/src/templates-bootstrap/opac/register.tt2 b/Open-ILS/src/templates-bootstrap/opac/register.tt2 index 474475d094..7d2777a0a3 100755 --- a/Open-ILS/src/templates-bootstrap/opac/register.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/register.tt2 @@ -95,7 +95,7 @@ END; ) | html %] [% END %] - +
diff --git a/Open-ILS/web/opac/common/js/recaptcha.js b/Open-ILS/web/opac/common/js/recaptcha.js new file mode 100644 index 0000000000..0597e2e476 --- /dev/null +++ b/Open-ILS/web/opac/common/js/recaptcha.js @@ -0,0 +1,71 @@ +document.addEventListener('DOMContentLoaded', () => { + function initializeRecaptcha(formId, siteKey, actionName, submitAction) { + console.log('Initializing reCAPTCHA for form:', formId); + const form = document.getElementById(formId); + if (!form) { + console.log('Form not found:', formId); + return; + } + + // Create and append reCAPTCHA container dynamically + const recaptchaContainer = createRecaptchaContainer(); + form.appendChild(recaptchaContainer); + console.log('reCAPTCHA container appended to form:', formId); + + // Add event listener to the form for reCAPTCHA validation + form.addEventListener('submit', event => { + event.preventDefault(); + console.log('Form submit action intercepted:', submitAction); + grecaptcha.ready(() => { + console.log('Executing reCAPTCHA with siteKey:', siteKey, 'and actionName:', actionName); + grecaptcha.execute(siteKey, { action: actionName }) + .then(token => handleRecaptchaToken(token, form)); + }); + }); + + function createRecaptchaContainer() { + const container = document.createElement('div'); + container.id = 'recaptcha-container'; + console.log('Created reCAPTCHA container'); + return container; + } + + function handleRecaptchaToken(token, form) { + console.log('Received reCAPTCHA token:', token); + console.log('Sending reCAPTCHA verification request...'); + const session = new OpenSRF.ClientSession('biblio.recaptcha'); + const request = session.request('biblio.recaptcha.verify', { + token, + org_unit: form.dataset.orgUnit + }); + + request.oncomplete = response => processRecaptchaResponse(response, form); + request.send(); + console.log('reCAPTCHA verification request sent'); + } + + function processRecaptchaResponse(response, form) { + console.log('Processing reCAPTCHA response...'); + let msg; + while ((msg = response.recv())) { + try { + const responseContent = JSON.parse(msg.content()); + console.log('reCAPTCHA response:', responseContent); + if (responseContent.success === 1) { + console.log('reCAPTCHA validation successful'); + form.submit(); + } else { + console.log('reCAPTCHA validation failed'); + alert('reCAPTCHA validation failed. Please try again.'); + } + } catch (error) { + console.error('Error parsing reCAPTCHA response as JSON:', error); + alert('Error in reCAPTCHA validation. Please try again.'); + } + } + } + } + + // Export the function to be used in other scripts + window.initializeRecaptcha = initializeRecaptcha; +}); \ No newline at end of file diff --git a/docs/modules/admin/pages/recaptcha.adoc b/docs/modules/admin/pages/recaptcha.adoc index 3bebc579b3..27373504a6 100644 --- a/docs/modules/admin/pages/recaptcha.adoc +++ b/docs/modules/admin/pages/recaptcha.adoc @@ -11,7 +11,7 @@ To enable reCAPTCHA v3, follow these steps: 1. **Obtain reCAPTCHA Keys**: - Register your site with Google reCAPTCHA to obtain the site key and secret key. - - Visit the [Google reCAPTCHA Admin Console](https://www.google.com/recaptcha/admin) to register your site. +- Visit the link:https://www.google.com/recaptcha/admin[Google reCAPTCHA Admin Console^] to register your site. 2. **Configure reCAPTCHA in Evergreen**: - Add the reCAPTCHA keys to your organizational settings in Evergreen. @@ -54,6 +54,44 @@ Added the reCAPTCHA service to the list of services: biblio.recaptcha ``` +== Adding Library Settings == + +To enable reCAPTCHA v3, you need to add the following library settings to Evergreen: + +* `recaptcha.site_key`: The site key obtained from Google reCAPTCHA. +* `recaptcha.secret_key`: The secret key obtained from Google reCAPTCHA. +* `recaptcha.enabled`: A flag to enable or disable reCAPTCHA (set to true to enable). + +These settings should be applied at the consortium level and will automatically apply to all children. However, supplying different keys for different organizational units will override the consortial setting by default. + +Here are the SQL insert statements to create these library settings: + +```sql +-- Insert reCAPTCHA site key setting type +INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype) VALUES +('recaptcha.site_key', 'reCAPTCHA site key', 'sec', 'The site key obtained from Google reCAPTCHA.', 'string'); + +-- Insert reCAPTCHA secret key setting type +INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype) VALUES +('recaptcha.secret_key', 'reCAPTCHA secret key', 'sec', 'The secret key obtained from Google reCAPTCHA.', 'string'); + +-- Insert reCAPTCHA enabled setting type +INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype) VALUES +('recaptcha.enabled', 'Enable reCAPTCHA', 'sec', 'A flag to enable or disable reCAPTCHA (set to true to enable).', 'bool'); + +-- Insert reCAPTCHA site key setting +INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES +(1, 'recaptcha.site_key', '"'); + +-- Insert reCAPTCHA secret key setting +INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES +(1, 'recaptcha.secret_key', '""'); + +-- Insert reCAPTCHA enabled setting +INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES +(1, 'recaptcha.enabled', 'true'); +``` + == Template Modifications == The following template files were modified to include reCAPTCHA validation: