From 0f3bc6e40be00bbf1002d7f08bdda4e3f06bc44d Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Tue, 19 Nov 2024 11:57:18 -0500 Subject: [PATCH 01/14] Add reCAPTCHA v3 verification to user registration Integrate Google's reCAPTCHA v3 into the OPAC user registration process for enhanced security. This implementation verifies user input through a new OpenSRF service and updates related configuration files. - Introduced `OpenILS::Application::Recaptcha` module for reCAPTCHA verification - Updated `opensrf.xml.example` and `opensrf_core.xml.example` to include `biblio.recaptcha` service - Added new script `recaptcha.tt2` and placeholder `recaptcha-placeholder.tt2` for reCAPTCHA integration in the frontend - Modified `register.tt2` to include the reCAPTCHA scripts and placeholders Release-Note: Add reCAPTCHA v3 to user registration for security enhancement Signed-off-by: Ian Skelskey --- Open-ILS/examples/opensrf.xml.example | 19 ++++ Open-ILS/examples/opensrf_core.xml.example | 2 + .../lib/OpenILS/Application/Recaptcha.pm | 87 +++++++++++++++++++ .../opac/parts/recaptcha-placeholder.tt2 | 2 + .../opac/parts/recaptcha.tt2 | 69 +++++++++++++++ .../src/templates-bootstrap/opac/register.tt2 | 4 +- 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm create mode 100644 Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 create mode 100644 Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example index ec87c9804d..88c3402257 100644 --- a/Open-ILS/examples/opensrf.xml.example +++ b/Open-ILS/examples/opensrf.xml.example @@ -1491,6 +1491,24 @@ vim:et:ts=4:sw=4: + + + 3 + 1 + perl + OpenILS::Application::Recaptcha + 100 + + biblio.recaptcha.log + biblio.recaptcha.sock + biblio.recaptcha.pid + 2 + 10 + 1 + 3 + + + @@ -1543,6 +1561,7 @@ vim:et:ts=4:sw=4: open-ils.curbside open-ils.geo open-ils.sip2 + biblio.recaptcha diff --git a/Open-ILS/examples/opensrf_core.xml.example b/Open-ILS/examples/opensrf_core.xml.example index 943ad8b290..dbdd5f5daf 100644 --- a/Open-ILS/examples/opensrf_core.xml.example +++ b/Open-ILS/examples/opensrf_core.xml.example @@ -42,6 +42,7 @@ Example OpenSRF bootstrap configuration file for Evergreen open-ils.serial open-ils.ebook_api open-ils.sip2 + biblio.recaptcha @@ -110,6 +111,7 @@ Example OpenSRF bootstrap configuration file for Evergreen open-ils.auth_mfa open-ils.collections open-ils.reporter + biblio.recaptcha diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm new file mode 100644 index 0000000000..ef7254b7a5 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm @@ -0,0 +1,87 @@ +# ------------------------------------------------------------------------------ +# Module: OpenILS::Application::Recaptcha +# Author: Ian Skelskey +# Organization: Bibliomation, Inc. +# Year: 2024 +# Description: This module implements the reCAPTCHA v3 verification process within +# the Evergreen ILS, as an OpenSRF service, using Google's reCAPTCHA +# API to validate user input. +# ------------------------------------------------------------------------------ + +package OpenILS::Application::Recaptcha; + +use strict; +use warnings; +use base 'OpenSRF::Application'; +use LWP::UserAgent; +use JSON; +use OpenSRF::Utils::Logger qw($logger); + +# Register the OpenSRF method +__PACKAGE__->register_method( + method => 'recaptcha_verify', + api_name => 'biblio.recaptcha.verify', + argc => 1, # Expecting one argument (the reCAPTCHA token) + stream => 0 # It's not a streaming method +); + +sub recaptcha_verify { + my ($self, $client, $recaptcha_response) = @_; + + $logger->info("Verifying reCAPTCHA token"); + + my $response = send_recaptcha_request($recaptcha_response); + my $result = process_recaptcha_response($response); + + return encode_json($result); # Always return encoded JSON +} + +sub send_recaptcha_request { + my ($recaptcha_response) = @_; + + my $secret = 'GET_FROM_LIBRARY_SETTING'; # Your reCAPTCHA secret key + my $url = 'https://www.google.com/recaptcha/api/siteverify'; + + my $ua = LWP::UserAgent->new(timeout => 10); + my $response = $ua->post($url, { + secret => $secret, + response => $recaptcha_response + }); + + unless ($response->is_success) { + $logger->error("Failed to reach Google reCAPTCHA server: " . $response->status_line); + return { success => 0, error => 'Failed to reach reCAPTCHA server' }; + } + + return $response; +} + +sub process_recaptcha_response { + my ($response) = @_; + + my $content = $response->decoded_content || '{}'; + my $json; + my $result; + + eval { + $json = decode_json($content); + }; + if ($@) { # Catch JSON decoding errors + $logger->error("Error decoding JSON response from Google: $@"); + return { success => 0, error => 'Invalid JSON response from Google' }; + } + unless ($json->{'success'}) { # reCAPTCHA failed + $logger->error("reCAPTCHA failed: " . join(", ", @{$json->{'error-codes'}})); + return { success => 0, 'error-codes' => $json->{'error-codes'} }; + } + # reCAPTCHA verified successfully + if ($json->{'score'} < 0.5) { + $logger->warn("reCAPTCHA score is below threshold: " . $json->{'score'}); + return { success => 0, error => 'reCAPTCHA score is below threshold' }; + } + + $logger->info("reCAPTCHA verified successfully"); + return { success => 1 }; +} + +1; diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 new file mode 100644 index 0000000000..aa335dfc16 --- /dev/null +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 @@ -0,0 +1,2 @@ + +
\ No newline at end of file diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 new file mode 100644 index 0000000000..55898a5ede --- /dev/null +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -0,0 +1,69 @@ + + + + + + + + diff --git a/Open-ILS/src/templates-bootstrap/opac/register.tt2 b/Open-ILS/src/templates-bootstrap/opac/register.tt2 index 6ce95c0012..763272df82 100755 --- a/Open-ILS/src/templates-bootstrap/opac/register.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/register.tt2 @@ -105,7 +105,8 @@ END; can_have_users_only=1 valid_org_list=ctx.register.valid_orgs %] - + + [% INCLUDE "opac/parts/recaptcha-placeholder.tt2" %]
[% IF ctx.register.invalid.bad_home_ou %] @@ -311,3 +312,4 @@ $(document).ready(function(){ $('.datepicker').datepicker("setDate", ""); }); +[% INCLUDE "opac/parts/recaptcha.tt2" %] From 712c0f19b57f50cacc62d793cbd468b469fb967e Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Tue, 19 Nov 2024 14:34:53 -0500 Subject: [PATCH 02/14] Improve reCAPTCHA integration by retrieving keys from settings Add functions to retrieve reCAPTCHA secret and site keys from library settings, ensuring the correct keys are used for verification and rendering on the client side. Enhance error logging for missing secret keys. Update HTML templates to dynamically use the configured site key. - Introduced `get_secret_key` function in `Recaptcha.pm` to fetch secret key. - Modified `send_recaptcha_request` to use the retrieved secret key, with error handling. - Updated `recaptcha.tt2` to obtain and utilize the site key setting. - Enhanced form handling to ensure dynamic key usage in `register.tt2`. Release-Note: Dynamic retrieval of reCAPTCHA keys from settings for improved security and configurability. Signed-off-by: Ian Skelskey --- .../lib/OpenILS/Application/Recaptcha.pm | 16 +++++++++++++- .../opac/parts/recaptcha.tt2 | 21 +++++++------------ .../src/templates-bootstrap/opac/register.tt2 | 4 ++-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm index ef7254b7a5..fbec0d55d0 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm @@ -25,6 +25,14 @@ __PACKAGE__->register_method( stream => 0 # It's not a streaming method ); +# Retrieve the reCAPTCHA secret key from library settings +sub get_secret_key { + my ($org) = @_; + my $mgr = OpenSRF::Utils::SettingsClient->new; + my $secret_key = $mgr->ou_ancestor_setting_value($org, 'recaptcha.secret_key'); + return $secret_key; +} + sub recaptcha_verify { my ($self, $client, $recaptcha_response) = @_; @@ -39,7 +47,13 @@ sub recaptcha_verify { sub send_recaptcha_request { my ($recaptcha_response) = @_; - my $secret = 'GET_FROM_LIBRARY_SETTING'; # Your reCAPTCHA secret key + my $secret = get_secret_key($client->session->requestor->home_ou); + + unless ($secret) { + $logger->error("reCAPTCHA secret key not found in library settings"); + return { success => 0, error => 'reCAPTCHA secret key not found' }; + } + my $url = 'https://www.google.com/recaptcha/api/siteverify'; my $ua = LWP::UserAgent->new(timeout => 10); diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 55898a5ede..172b046ea7 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -1,10 +1,13 @@ +[% + recaptcha_site_key = ctx.get_org_setting(ctx.search_ou, 'recaptcha.site_key'); +%] + - - + - + diff --git a/Open-ILS/src/templates-bootstrap/opac/register.tt2 b/Open-ILS/src/templates-bootstrap/opac/register.tt2 index 763272df82..1177704ef4 100755 --- a/Open-ILS/src/templates-bootstrap/opac/register.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/register.tt2 @@ -95,13 +95,13 @@ END; ) | html %] [% END %] -
+
[% INCLUDE build_org_selector name='stgu.home_ou' - value=value || ctx_org + value=ctx_org can_have_users_only=1 valid_org_list=ctx.register.valid_orgs %] From fcae328e3359431b57b67be1cf2ae5e2ef6df2ba Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Tue, 19 Nov 2024 14:56:26 -0500 Subject: [PATCH 03/14] Remove reCAPTCHA placeholder and improve dynamic form handling - Deleted 'recaptcha-placeholder.tt2' as it's no longer needed - Updated 'recaptcha.tt2' to dynamically insert the reCAPTCHA container - Introduced defaults: action_name, submit_action, and target_element_id for flexibility - Enhanced form validation by attaching event listeners dynamically Release-Note: Remove obsolete reCAPTCHA placeholder and improve dynamic integration. Signed-off-by: Ian Skelskey --- .../opac/parts/recaptcha-placeholder.tt2 | 2 -- .../opac/parts/recaptcha.tt2 | 24 ++++++++++++------- .../src/templates-bootstrap/opac/register.tt2 | 7 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) delete mode 100644 Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 deleted file mode 100644 index aa335dfc16..0000000000 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha-placeholder.tt2 +++ /dev/null @@ -1,2 +0,0 @@ - -
\ No newline at end of file diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 172b046ea7..745572c8d5 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -1,5 +1,9 @@ [% + # Retrieve the reCAPTCHA site key from library settings recaptcha_site_key = ctx.get_org_setting(ctx.search_ou, '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 %] @@ -10,11 +14,13 @@ -[% INCLUDE "opac/parts/recaptcha.tt2" %] +[% INCLUDE "opac/parts/recaptcha.tt2" + action_name="register" + submit_action="submit" + target_element_id="registration-form" +%] From 653c428e88e94e1f926dca82656c355d1a4cd65a Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Wed, 20 Nov 2024 08:49:34 -0500 Subject: [PATCH 04/14] Add file header comment to recaptcha.tt2 template Included a header comment in the recaptcha.tt2 template to provide metadata about the module, author, and its purpose. This enhancement improves code readability and maintainability by documenting key information directly within the file. No functional changes were made to the existing template logic or layout. Signed-off-by: Ian Skelskey --- Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 745572c8d5..04626ec39a 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -1,5 +1,11 @@ + [% - # Retrieve the reCAPTCHA site key from library settings recaptcha_site_key = ctx.get_org_setting(ctx.search_ou, '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 From 28ffc727bd6fbbce6ed37de127aef82e6a5b1839 Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Wed, 20 Nov 2024 08:57:11 -0500 Subject: [PATCH 05/14] Remove redundant comments in Recaptcha.pm Clean up unnecessary comments in the Recaptcha.pm file by removing redundant and superfluous lines. This enhances code readability by eliminating comments that do not provide additional value. Signed-off-by: Ian Skelskey --- .../src/perlmods/lib/OpenILS/Application/Recaptcha.pm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm index fbec0d55d0..1721f4048f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm @@ -25,7 +25,6 @@ __PACKAGE__->register_method( stream => 0 # It's not a streaming method ); -# Retrieve the reCAPTCHA secret key from library settings sub get_secret_key { my ($org) = @_; my $mgr = OpenSRF::Utils::SettingsClient->new; @@ -41,7 +40,7 @@ sub recaptcha_verify { my $response = send_recaptcha_request($recaptcha_response); my $result = process_recaptcha_response($response); - return encode_json($result); # Always return encoded JSON + return encode_json($result); } sub send_recaptcha_request { @@ -80,15 +79,14 @@ sub process_recaptcha_response { eval { $json = decode_json($content); }; - if ($@) { # Catch JSON decoding errors + if ($@) { $logger->error("Error decoding JSON response from Google: $@"); return { success => 0, error => 'Invalid JSON response from Google' }; } - unless ($json->{'success'}) { # reCAPTCHA failed + unless ($json->{'success'}) { $logger->error("reCAPTCHA failed: " . join(", ", @{$json->{'error-codes'}})); return { success => 0, 'error-codes' => $json->{'error-codes'} }; } - # reCAPTCHA verified successfully if ($json->{'score'} < 0.5) { $logger->warn("reCAPTCHA score is below threshold: " . $json->{'score'}); return { success => 0, error => 'reCAPTCHA score is below threshold' }; From 90293bfb781cef692b8e91f4fc3d7e5efd13dc1f Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Wed, 20 Nov 2024 10:33:32 -0500 Subject: [PATCH 06/14] Enhance org selector default value handling in registration page Improves the logic to initialize default values in the organization selector on the OPAC registration page. This update ensures the org selector uses `value` if available, or falls back to `ctx_org`. Release-Note: Improves organization selector initialization on registration. Signed-off-by: Ian Skelskey --- Open-ILS/src/templates-bootstrap/opac/register.tt2 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Open-ILS/src/templates-bootstrap/opac/register.tt2 b/Open-ILS/src/templates-bootstrap/opac/register.tt2 index 332dee5ace..b8cfc65623 100755 --- a/Open-ILS/src/templates-bootstrap/opac/register.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/register.tt2 @@ -101,7 +101,7 @@ END;
[% INCLUDE build_org_selector name='stgu.home_ou' - value=ctx_org + value=value || ctx_org can_have_users_only=1 valid_org_list=ctx.register.valid_orgs %] @@ -284,6 +284,7 @@ FOR field_def IN register_fields; [%- END %] [% INCLUDE "opac/parts/recaptcha.tt2" action_name="register" From a3f47f3a093389a0591741c04472e7db52d70c4f Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Wed, 20 Nov 2024 13:50:32 -0500 Subject: [PATCH 07/14] Enhance reCAPTCHA verification with org_unit support This commit updates the recaptcha_verify method to accept a hash argument containing a token and org_unit, instead of just the token. The OpenILS::Utils::CStoreEditor is initialized for better access to server-side utilities, and logging has been added throughout for improved diagnostics. - Adjusted get_secret_key to log retrieval operations. - Updated send_recaptcha_request to handle org_unit and log errors. - Enhanced process_recaptcha_response with error and success logs. - Updated OPAC templates to pass org_unit and improved console logging. Release-Note: Enhance reCAPTCHA verification with organization unit (org_unit) support and improved logging. Signed-off-by: IanSkelskey --- .../lib/OpenILS/Application/Recaptcha.pm | 56 +++++++++++++------ .../opac/parts/recaptcha.tt2 | 22 +++++++- .../src/templates-bootstrap/opac/register.tt2 | 1 + 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm index 1721f4048f..6555ed184f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Recaptcha.pm @@ -16,45 +16,65 @@ use base 'OpenSRF::Application'; use LWP::UserAgent; use JSON; use OpenSRF::Utils::Logger qw($logger); +use OpenILS::Utils::CStoreEditor; # Register the OpenSRF method __PACKAGE__->register_method( method => 'recaptcha_verify', api_name => 'biblio.recaptcha.verify', - argc => 1, # Expecting one argument (the reCAPTCHA token) + argc => 1, # Expecting one argument (a hash with token and org_unit) stream => 0 # It's not a streaming method ); +# Initialize CStore Editor +my $editor; +sub editor { + return $editor ||= OpenILS::Utils::CStoreEditor->new; +} + sub get_secret_key { my ($org) = @_; - my $mgr = OpenSRF::Utils::SettingsClient->new; - my $secret_key = $mgr->ou_ancestor_setting_value($org, 'recaptcha.secret_key'); - return $secret_key; + + my $U = 'OpenILS::Application::AppUtils'; + my $secret_key = $U->ou_ancestor_setting_value($org, 'recaptcha.secret_key'); + + if ($secret_key) { + $logger->info("Retrieved reCAPTCHA secret key for org ID $org"); + return $secret_key; + } + + $logger->error("No reCAPTCHA secret key found for org ID $org"); + return undef; } +# Verify the reCAPTCHA token sub recaptcha_verify { - my ($self, $client, $recaptcha_response) = @_; + my ($self, $client, $args) = @_; + + my $token = $args->{token}; + my $org_unit = $args->{org_unit} || 1; # Default to consortium (org_unit 1) - $logger->info("Verifying reCAPTCHA token"); + $logger->info("Starting reCAPTCHA verification for org_unit: $org_unit"); + $logger->info("Received reCAPTCHA token: $token"); - my $response = send_recaptcha_request($recaptcha_response); + # Send verification request to Google + my $response = send_recaptcha_request($token, $org_unit); my $result = process_recaptcha_response($response); return encode_json($result); } +# Send request to Google's reCAPTCHA API sub send_recaptcha_request { - my ($recaptcha_response) = @_; - - my $secret = get_secret_key($client->session->requestor->home_ou); + my ($recaptcha_response, $org) = @_; + my $secret = get_secret_key($org); unless ($secret) { - $logger->error("reCAPTCHA secret key not found in library settings"); + $logger->error("reCAPTCHA secret key not found for organization ID: $org"); return { success => 0, error => 'reCAPTCHA secret key not found' }; } my $url = 'https://www.google.com/recaptcha/api/siteverify'; - my $ua = LWP::UserAgent->new(timeout => 10); my $response = $ua->post($url, { secret => $secret, @@ -66,16 +86,16 @@ sub send_recaptcha_request { return { success => 0, error => 'Failed to reach reCAPTCHA server' }; } + $logger->info("Successfully sent reCAPTCHA verification request"); return $response; } +# Process the response from Google's reCAPTCHA API sub process_recaptcha_response { my ($response) = @_; my $content = $response->decoded_content || '{}'; my $json; - my $result; - eval { $json = decode_json($content); }; @@ -83,16 +103,20 @@ sub process_recaptcha_response { $logger->error("Error decoding JSON response from Google: $@"); return { success => 0, error => 'Invalid JSON response from Google' }; } + + $logger->info("Decoded reCAPTCHA response: $content"); + unless ($json->{'success'}) { - $logger->error("reCAPTCHA failed: " . join(", ", @{$json->{'error-codes'}})); + $logger->error("reCAPTCHA verification failed: " . join(", ", @{$json->{'error-codes'}})); return { success => 0, 'error-codes' => $json->{'error-codes'} }; } + if ($json->{'score'} < 0.5) { $logger->warn("reCAPTCHA score is below threshold: " . $json->{'score'}); return { success => 0, error => 'reCAPTCHA score is below threshold' }; } - $logger->info("reCAPTCHA verified successfully"); + $logger->info("reCAPTCHA verified successfully with score: " . $json->{'score'}); return { success => 1 }; } diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 04626ec39a..e0db02588b 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -6,13 +6,14 @@ Description: Template Toolkit file for reCAPTCHA form validation in the OPAC. --------------------------------------------------------------------------------> [% - recaptcha_site_key = ctx.get_org_setting(ctx.search_ou, 'recaptcha.site_key'); + 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 %] - + @@ -20,7 +21,13 @@ + + + - - - - - + + +[% ELSE %] + +[% END %] \ No newline at end of file From d65c0f8b8546666d1ab6fc5c68f3f402fe36bedb Mon Sep 17 00:00:00 2001 From: Ian Skelskey Date: Thu, 21 Nov 2024 00:12:33 -0500 Subject: [PATCH 09/14] Remove debug logging from reCAPTCHA script This commit removes unnecessary console.log statements from the reCAPTCHA implementation in the OPAC templates, enhancing performance and user privacy. - Deleted debug logs for org_unit, site_key, action_name, submit_action, target_element_id, form, reCAPTCHA token, and verification requests - Retained essential logic for reCAPTCHA validation and form handling Release-Note: Remove debug logging from OPAC reCAPTCHA script to improve performance and security. Signed-off-by: Ian Skelskey --- .../src/templates-bootstrap/opac/parts/recaptcha.tt2 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 index 3507f71202..7ae30fca8a 100644 --- a/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/recaptcha.tt2 @@ -22,13 +22,7 @@ - - - + + + [% ELSE %] @@ -85,4 +85,4 @@ console.log('recaptcha_enabled:', '[% recaptcha_enabled %]'); console.log('reCAPTCHA is not enabled for this organization unit.'); -[% END %] \ No newline at end of file +[% END %] diff --git a/Open-ILS/src/templates/opac/parts/searchbar.tt2 b/Open-ILS/src/templates/opac/parts/searchbar.tt2 index 8d454f45f1..bb1a2b7cca 100644 --- a/Open-ILS/src/templates/opac/parts/searchbar.tt2 +++ b/Open-ILS/src/templates/opac/parts/searchbar.tt2 @@ -31,7 +31,7 @@ END;
[% UNLESS took_care_of_form -%] - + [%- END %] [% IF ctx.page == 'rresult' && ctx.metarecord && search.metarecord_default %] @@ -229,3 +229,8 @@ END; -->
+[% INCLUDE "opac/parts/recaptcha.tt2" + action_name="opac_search" + submit_action="submit" + target_element_id="opac-search-form" +%] From ab9100597606bd048cc365f6e0f71935dc8e8075 Mon Sep 17 00:00:00 2001 From: Ian Skelskey Date: Mon, 17 Feb 2025 19:06:23 -0500 Subject: [PATCH 11/14] Add reCAPTCHA v3 and documentation build support - Implement Google reCAPTCHA v3 within Evergreen ILS to enhance security by verifying user interactions. - Update configuration files (`opensrf.xml`, `opensrf_core.xml`) to include reCAPTCHA services. - Create a new Template Toolkit file (`recaptcha.tt2`) for reCAPTCHA form validation. - Modify existing templates to integrate reCAPTCHA. - Introduce a `gulpfile.js` for building and serving Antora documentation with live reload capabilities. - Add documentation navigation and `site-working.yml` required for Antora site generation. Release-Note: reCAPTCHA v3 integration and Antora doc build support added. Signed-off-by: Ian Skelskey --- docs/gulpfile.js | 35 +++++ docs/modules/admin/pages/recaptcha.adoc | 162 ++++++++++++++++++++++++ docs/modules/local_admin/nav.adoc | 1 + docs/site-working.yml | 20 +++ 4 files changed, 218 insertions(+) create mode 100644 docs/gulpfile.js create mode 100644 docs/modules/admin/pages/recaptcha.adoc create mode 100644 docs/site-working.yml diff --git a/docs/gulpfile.js b/docs/gulpfile.js new file mode 100644 index 0000000000..85b885e5e7 --- /dev/null +++ b/docs/gulpfile.js @@ -0,0 +1,35 @@ +'use strict'; + +const gulp = require('gulp'); +const connect = require('gulp-connect'); +const { exec } = require('child_process'); + +const buildDir = 'output'; + +// Task to build Antora documentation +gulp.task('build', (done) => { + exec('antora site-working.yml', (err, stdout, stderr) => { + console.log(stdout); + console.error(stderr); + done(err); + }); +}); + +// Task to serve files with live reload +gulp.task('serve', (done) => { + connect.server({ + root: buildDir, + livereload: true, + port: 8080 + }); + done(); +}); + +// Watch for changes and rebuild +gulp.task('watch', (done) => { + gulp.watch(['**/*.adoc', 'site-working.yml'], gulp.series('build')); + done(); +}); + +// Default task +gulp.task('preview', gulp.series('build', gulp.parallel('serve', 'watch'))); \ No newline at end of file diff --git a/docs/modules/admin/pages/recaptcha.adoc b/docs/modules/admin/pages/recaptcha.adoc new file mode 100644 index 0000000000..3bebc579b3 --- /dev/null +++ b/docs/modules/admin/pages/recaptcha.adoc @@ -0,0 +1,162 @@ += Google reCAPTCHA v3 Integration = +:toc: + +== Overview == + +This document describes the integration of Google reCAPTCHA v3 into the Evergreen ILS system. The reCAPTCHA service helps protect the system from spam and abuse by verifying that user interactions are performed by humans. + +== Enabling reCAPTCHA v3 == + +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. + +2. **Configure reCAPTCHA in Evergreen**: + - Add the reCAPTCHA keys to your organizational settings in Evergreen. + - Update the `opensrf.xml` and `opensrf_core.xml` configuration files to include the reCAPTCHA service. + +3. **Modify Templates**: + - Update the relevant template files to include reCAPTCHA validation. + +== Configuration Changes == + +The following changes were made to integrate reCAPTCHA v3: + +=== `opensrf.xml` === + +Added the reCAPTCHA service configuration: +```xml + + 3 + 1 + perl + OpenILS::Application::Recaptcha + 100 + + biblio.recaptcha.log + biblio.recaptcha.sock + biblio.recaptcha.pid + 2 + 10 + 1 + 3 + + +``` + +=== `opensrf_core.xml` === + +Added the reCAPTCHA service to the list of services: + +```xml +biblio.recaptcha +``` + +== Template Modifications == + +The following template files were modified to include reCAPTCHA validation: + +=== recaptcha.tt2 === + +A new template file was created to handle reCAPTCHA validation: + +```tt2 + +[% + org_unit = ctx.search_ou; + recaptcha_site_key = ctx.get_org_setting(org_unit, 'recaptcha.site_key'); + action_name = action_name || 'register'; + submit_action = submit_action || 'submit'; + target_element_id = target_element_id || 'recaptcha-form'; + recaptcha_enabled = ctx.get_org_setting(ctx.search_ou, 'recaptcha.enable'); +%] +[% IF recaptcha_enabled && recaptcha_enabled == 1 %] + + + + + + +[% ELSE %] + +[% END %] +``` + +=== register.tt2 === + +Included the reCAPTCHA template: + +```tt2 +[% INCLUDE "opac/parts/recaptcha.tt2" + action_name="register" + submit_action="submit" + target_element_id="registration-form" +%] +``` + +== Conclusion == + +The integration of Google reCAPTCHA v3 into Evergreen ILS enhances security by verifying user interactions. Follow the steps outlined above to enable and configure reCAPTCHA for your Evergreen instance. \ No newline at end of file diff --git a/docs/modules/local_admin/nav.adoc b/docs/modules/local_admin/nav.adoc index 414cce2c8d..e568e77a70 100644 --- a/docs/modules/local_admin/nav.adoc +++ b/docs/modules/local_admin/nav.adoc @@ -18,3 +18,4 @@ ** xref:local_admin:staff_portal_page.adoc[Staff Portal Page] ** xref:local_admin:transit_list.adoc[Transit List] ** xref:admin:lsa-work_log.adoc[Work Log] +** xref:admin:recaptcha.adoc[reCAPTCHA] diff --git a/docs/site-working.yml b/docs/site-working.yml new file mode 100644 index 0000000000..266c55748b --- /dev/null +++ b/docs/site-working.yml @@ -0,0 +1,20 @@ +site: + title: Evergreen Documentation + start_page: docs:shared:about_this_documentation.adoc + url: http://example.com +content: + sources: + - url: ../ +# - url: git://git.evergreen-ils.org/Evergreen.git + start_path: docs +ui: + bundle: + url: https://github.com/IanSkelskey/eg-antora/releases/latest/download/ui-bundle.zip + +output: + dir: ./output + +antora: + extensions: + - require: '@antora/lunr-extension' + index_latest_only: true From 3da0774f5d2f505b56bca6d52e0ebf792c7b34f5 Mon Sep 17 00:00:00 2001 From: Ian Skelskey Date: Mon, 17 Feb 2025 19:08:30 -0500 Subject: [PATCH 12/14] Add dependencies gulp and gulp-connect to package.json Added \"gulp\" and \"gulp-connect\" dependencies to the package.json file to support automated documentation tasks and live-reload functionality. Release-Note: Add gulp and gulp-connect to support automated tasks Signed-off-by: Ian Skelskey --- docs/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 7a6eb77857..d08f57bdc3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,8 @@ { "dependencies": { "@antora/lunr-extension": "^1.0.0-alpha.8", - "antora": "^3.1.7" + "antora": "^3.1.7", + "gulp": "^5.0.0", + "gulp-connect": "^5.7.0" } } From b059c7e7ea246c63fa0474e8fcfa2b1f394fb278 Mon Sep 17 00:00:00 2001 From: Ian Skelskey Date: Mon, 17 Feb 2025 20:51:13 -0500 Subject: [PATCH 13/14] 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: From c55fd081d2fb4fe423deeffbacfbebfd4888c2bc Mon Sep 17 00:00:00 2001 From: IanSkelskey Date: Mon, 24 Feb 2025 11:47:49 -0500 Subject: [PATCH 14/14] Fix syntax error in reCAPTCHA site key example Corrected typo in the INSERT statement for the reCAPTCHA site key example within the documentation. - Original version had a misplaced quote character causing syntax errors. Release-Note: fix syntax error in reCAPTCHA configuration example Signed-off-by: Ian Skelskey --- docs/modules/admin/pages/recaptcha.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/admin/pages/recaptcha.adoc b/docs/modules/admin/pages/recaptcha.adoc index 27373504a6..a3460ac0db 100644 --- a/docs/modules/admin/pages/recaptcha.adoc +++ b/docs/modules/admin/pages/recaptcha.adoc @@ -81,7 +81,7 @@ INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatyp -- Insert reCAPTCHA site key setting INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES -(1, 'recaptcha.site_key', '"'); +(1, 'recaptcha.site_key', '""'); -- Insert reCAPTCHA secret key setting INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES