From 1fe881fc6deb451f4553665b68e09002cce167d6 Mon Sep 17 00:00:00 2001 From: Robert Ristroph Date: Thu, 10 Oct 2024 17:26:46 +0000 Subject: [PATCH 1/3] WS-50: Add recaptcha to FOIA request form, WIP. --- js/components/foia_request_form.jsx | 14 +++- package-lock.json | 112 ++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/js/components/foia_request_form.jsx b/js/components/foia_request_form.jsx index fd39870a..4f0df62b 100644 --- a/js/components/foia_request_form.jsx +++ b/js/components/foia_request_form.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef } from 'react'; import PropTypes from 'prop-types'; import Form from '@rjsf/core'; import validator from '@rjsf/validator-ajv8'; @@ -15,10 +15,13 @@ import { dataUrlToAttachment, findFileFields } from '../util/attachment'; import UploadProgress from './upload_progress'; import { scrollOffset } from '../util/dom'; import dispatcher from '../util/dispatcher'; +import ReCAPTCHA from 'react-google-recaptcha'; function FoiaRequestForm({ formData, upload, onSubmit, requestForm, submissionResult, }) { + const recaptchaRef = useRef(); + // Helper function to jump to the first form error. function focusOnFirstError() { const fieldErrors = document.getElementsByClassName('usa-input-error'); @@ -62,6 +65,10 @@ function FoiaRequestForm({ } function onFormSubmit({ formData: data }) { + const recaptchaValue = recaptchaRef.current.getValue(); + // Now you can use the recaptchaValue for your form submission + console.log(recaptchaValue); + // Merge the sections into a single payload const payload = rf.mergeSectionFormData(data); // Transform file fields to attachments @@ -149,12 +156,17 @@ function FoiaRequestForm({ /> ) : ( +
+ + {/* */} + {/* */} +
)} {submissionResult.errorMessage && ( diff --git a/package-lock.json b/package-lock.json index 98e9d06d..3fe7ee7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "prop-types": "15.x", "react": "16.x", "react-dom": "16.x", + "react-google-recaptcha": "^3.1.0", "react-modal": "3.x", "react-router-dom": "5.x", "react-url-query": "1.x", @@ -48,6 +49,7 @@ "@babel/preset-env": "^7.25.4", "@babel/preset-react": "^7.24.7", "@cucumber/cucumber": "^9.1.0", + "@types/react-google-recaptcha": "^2.1.9", "babel-loader": "9.x", "btoa": "^1.2.1", "buffer": "^6.0.3", @@ -3043,6 +3045,13 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/ramda": { "version": "0.28.23", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.28.23.tgz", @@ -3051,6 +3060,27 @@ "ts-toolbelt": "^6.15.1" } }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-google-recaptcha": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz", + "integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -5099,6 +5129,13 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cucumber": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/cucumber/-/cucumber-6.0.7.tgz", @@ -11326,6 +11363,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-debounce-input": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", @@ -11352,6 +11402,19 @@ "react": "^16.14.0" } }, + "node_modules/react-google-recaptcha": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", + "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.0", + "react-async-script": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-immutable-proptypes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", @@ -17536,6 +17599,12 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" }, + "@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "devOptional": true + }, "@types/ramda": { "version": "0.28.23", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.28.23.tgz", @@ -17544,6 +17613,25 @@ "ts-toolbelt": "^6.15.1" } }, + "@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "devOptional": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-google-recaptcha": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz", + "integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -19170,6 +19258,12 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true + }, "cucumber": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/cucumber/-/cucumber-6.0.7.tgz", @@ -23912,6 +24006,15 @@ "prop-types": "^15.6.2" } }, + "react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "requires": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + } + }, "react-debounce-input": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", @@ -23932,6 +24035,15 @@ "scheduler": "^0.19.1" } }, + "react-google-recaptcha": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", + "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", + "requires": { + "prop-types": "^15.5.0", + "react-async-script": "^1.2.0" + } + }, "react-immutable-proptypes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", diff --git a/package.json b/package.json index b62ac12b..7f605081 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "prop-types": "15.x", "react": "16.x", "react-dom": "16.x", + "react-google-recaptcha": "^3.1.0", "react-modal": "3.x", "react-router-dom": "5.x", "react-url-query": "1.x", @@ -69,6 +70,7 @@ "@babel/preset-env": "^7.25.4", "@babel/preset-react": "^7.24.7", "@cucumber/cucumber": "^9.1.0", + "@types/react-google-recaptcha": "^2.1.9", "babel-loader": "9.x", "btoa": "^1.2.1", "buffer": "^6.0.3", From eee746f524e9baae3b4e552cf2a6081021b8248c Mon Sep 17 00:00:00 2001 From: Robert Ristroph Date: Sat, 19 Oct 2024 18:20:46 +0000 Subject: [PATCH 2/3] WS-50: Initial working cut of front end for ReCAPTCHA. --- js/actions/index.js | 4 +++ js/components/foia_request_form.jsx | 41 ++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/js/actions/index.js b/js/actions/index.js index b3086315..7b1e2214 100644 --- a/js/actions/index.js +++ b/js/actions/index.js @@ -243,6 +243,10 @@ export const requestActions = { submissionResult.errorMessage = 'Sorry, there was a problem with the information you provided, please check the form and correct any errors.'; } + if (error.response && error.response.status === 401) { + submissionResult.errorMessage = 'Sorry, there was a problem with captcha confirming you are human, please check the captcha and re-submit the form.'; + } + return Promise.resolve(submissionResult); }) .then(requestActions.completeSubmitRequestForm); diff --git a/js/components/foia_request_form.jsx b/js/components/foia_request_form.jsx index 4f0df62b..64af94bb 100644 --- a/js/components/foia_request_form.jsx +++ b/js/components/foia_request_form.jsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import Form from '@rjsf/core'; import validator from '@rjsf/validator-ajv8'; @@ -22,6 +22,15 @@ function FoiaRequestForm({ }) { const recaptchaRef = useRef(); + const [data, setData] = useState(null); + + useEffect(() => { + fetch('/files/settings.json') + .then(response => response.json()) + .then(result => setData(result)) + .catch(error => console.error('Error fetching recaptcha site key:', error)); + }, []); + // Helper function to jump to the first form error. function focusOnFirstError() { const fieldErrors = document.getElementsByClassName('usa-input-error'); @@ -67,8 +76,16 @@ function FoiaRequestForm({ function onFormSubmit({ formData: data }) { const recaptchaValue = recaptchaRef.current.getValue(); // Now you can use the recaptchaValue for your form submission + // TODO: Remove debugging + console.log("recaptchaValue follows:"); console.log(recaptchaValue); + console.log("form data follows:"); + console.log(data); + // TODO - probably not needed -- remove ? + // The captcha field is added to the Expedited Processing section. + data.expedited_processing.captcha = recaptchaValue; + // Merge the sections into a single payload const payload = rf.mergeSectionFormData(data); // Transform file fields to attachments @@ -117,6 +134,8 @@ function FoiaRequestForm({ ObjectFieldTemplate: CustomObjectFieldTemplate, }; + console.log(data); + return (
) : ( -
- - - {/* */} - {/* */} +
)} {submissionResult.errorMessage From edcc518a8c9c17de7f2a2824bca1377712b83673 Mon Sep 17 00:00:00 2001 From: Robert Ristroph Date: Tue, 22 Oct 2024 19:04:27 +0000 Subject: [PATCH 3/3] WS-50: Fixing validation errors. --- js/components/foia_request_form.jsx | 30 ++++++++++------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/js/components/foia_request_form.jsx b/js/components/foia_request_form.jsx index 64af94bb..26758344 100644 --- a/js/components/foia_request_form.jsx +++ b/js/components/foia_request_form.jsx @@ -6,6 +6,7 @@ import { Map } from 'immutable'; import CustomFieldTemplate from 'components/request_custom_field_template'; import USWDSRadioWidget from 'components/uswds_radio_widget'; import USWDSCheckboxWidget from 'components/uswds_checkbox_widget'; +import ReCAPTCHA from 'react-google-recaptcha'; import { requestActions } from '../actions'; import { SubmissionResult } from '../models'; import CustomObjectFieldTemplate from './object_field_template'; @@ -15,20 +16,19 @@ import { dataUrlToAttachment, findFileFields } from '../util/attachment'; import UploadProgress from './upload_progress'; import { scrollOffset } from '../util/dom'; import dispatcher from '../util/dispatcher'; -import ReCAPTCHA from 'react-google-recaptcha'; function FoiaRequestForm({ formData, upload, onSubmit, requestForm, submissionResult, }) { const recaptchaRef = useRef(); - const [data, setData] = useState(null); + const [settingsdata, setData] = useState(null); useEffect(() => { fetch('/files/settings.json') - .then(response => response.json()) - .then(result => setData(result)) - .catch(error => console.error('Error fetching recaptcha site key:', error)); + .then((response) => response.json()) + .then((result) => setData(result)) + .catch((error) => console.error('Error fetching recaptcha site key:', error)); }, []); // Helper function to jump to the first form error. @@ -76,16 +76,11 @@ function FoiaRequestForm({ function onFormSubmit({ formData: data }) { const recaptchaValue = recaptchaRef.current.getValue(); // Now you can use the recaptchaValue for your form submission - // TODO: Remove debugging - console.log("recaptchaValue follows:"); - console.log(recaptchaValue); - console.log("form data follows:"); - console.log(data); // TODO - probably not needed -- remove ? // The captcha field is added to the Expedited Processing section. data.expedited_processing.captcha = recaptchaValue; - + // Merge the sections into a single payload const payload = rf.mergeSectionFormData(data); // Transform file fields to attachments @@ -134,8 +129,6 @@ function FoiaRequestForm({ ObjectFieldTemplate: CustomObjectFieldTemplate, }; - console.log(data); - return ( - {data && data.RECAPTCHA_SITE_KEY - ? - - : -

Inavlid Site Key

- } )}