[% IF ctx.register.invalid.bad_home_ou %]
@@ -284,6 +284,7 @@ FOR field_def IN register_fields;
[%- END %]
+[% INCLUDE "opac/parts/recaptcha.tt2"
+ action_name="register"
+ submit_action="submit"
+ target_element_id="registration-form"
+%]
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 -%]
-
+[% INCLUDE "opac/parts/recaptcha.tt2"
+ action_name="opac_search"
+ submit_action="submit"
+ target_element_id="opac-search-form"
+%]
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/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..a3460ac0db
--- /dev/null
+++ b/docs/modules/admin/pages/recaptcha.adoc
@@ -0,0 +1,200 @@
+= 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 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.
+ - 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
+```
+
+== 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:
+
+=== 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/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"
}
}
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