From c4a6a9366d0506372c27573dc722d3a8dc0b8faf Mon Sep 17 00:00:00 2001 From: Ryan Wold <64987852+ryanwoldatwork@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:33:44 -0700 Subject: [PATCH 1/4] update gems and readme --- Gemfile.lock | 38 +++++++++++++++++++------------------- README.md | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index cdae3c6f3..7feca1338 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/18F/omniauth_login_dot_gov.git - revision: b4d30dc92f2659e46680161bf02550ba424d768e + revision: 3eeff9bfcd57d781467e2ffb6cc11f459d8202ed branch: main specs: omniauth_login_dot_gov (3.0.0) @@ -103,10 +103,10 @@ GEM aes_key_wrap (1.1.0) ast (2.4.3) aws-eventstream (1.3.2) - aws-partitions (1.1074.0) + aws-partitions (1.1078.0) aws-record (2.13.2) aws-sdk-dynamodb (~> 1, >= 1.85.0) - aws-sdk-core (3.221.0) + aws-sdk-core (3.222.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -130,7 +130,7 @@ GEM aws-sessionstore-dynamodb (~> 2) concurrent-ruby (~> 1.3, >= 1.3.1) railties (>= 7.0.0) - aws-sdk-s3 (1.182.0) + aws-sdk-s3 (1.183.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -329,7 +329,7 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.6) + logger (1.7.0) logstop (0.4.1) logger loofah (2.24.0) @@ -370,24 +370,24 @@ GEM net-protocol newrelic_rpm (9.17.0) nio4r (2.7.4) - nokogiri (1.18.6) + nokogiri (1.18.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.6-aarch64-linux-gnu) + nokogiri (1.18.7-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.6-aarch64-linux-musl) + nokogiri (1.18.7-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.6-arm-linux-gnu) + nokogiri (1.18.7-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.18.6-arm-linux-musl) + nokogiri (1.18.7-arm-linux-musl) racc (~> 1.4) - nokogiri (1.18.6-arm64-darwin) + nokogiri (1.18.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.6-x86_64-darwin) + nokogiri (1.18.7-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.6-x86_64-linux-gnu) + nokogiri (1.18.7-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.6-x86_64-linux-musl) + nokogiri (1.18.7-x86_64-linux-musl) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -415,7 +415,7 @@ GEM activerecord (>= 6.1) request_store (~> 1.4) parallel (1.26.3) - parser (3.3.7.3) + parser (3.3.7.4) ast (~> 2.4.1) racc pg (1.5.9) @@ -492,7 +492,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rdoc (6.13.0) + rdoc (6.13.1) psych (>= 4.0.0) redis (5.4.0) redis-client (>= 0.22.0) @@ -529,7 +529,7 @@ GEM rspec-support (3.13.2) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.75.0) + rubocop (1.75.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -543,11 +543,11 @@ GEM rubocop-ast (1.43.0) parser (>= 3.3.7.2) prism (~> 1.4) - rubocop-rails (2.30.3) + rubocop-rails (2.31.0) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.72.1, < 2.0) + rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) rubocop-rspec (3.5.0) lint_roller (~> 1.1) diff --git a/README.md b/README.md index 1f9b3a64a..aae5583df 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ within the Technology Transformation Services' Touchpoints is online at . -A current Demo version is online at , +A Demo environment is online at , and government customers are [encouraged](https://github.com/GSA/touchpoints/wiki/Touchpoints-Demo-Environment/) to sign up and try it out. ## Documentation From 640ec2b2f4e2c0177c9d6e762f934458f4db64c2 Mon Sep 17 00:00:00 2001 From: Ryan Wold <64987852+ryanwoldatwork@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:43:34 -0700 Subject: [PATCH 2/4] add turnstile * show ID after submission --- app/controllers/admin/forms_controller.rb | 2 ++ app/controllers/submissions_controller.rb | 31 ++++++++++++++++++- .../forms/_form_manager_options.html.erb | 24 ++++++++++++++ app/views/components/forms/_custom.html.erb | 16 ++++++++++ app/views/components/widget/_fba.js.erb | 15 ++++++--- ...0250401201045_append_id_to_success_text.rb | 5 +++ .../20250401223209_add_form_turnstile.rb | 5 +++ db/schema.rb | 4 ++- 8 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20250401201045_append_id_to_success_text.rb create mode 100644 db/migrate/20250401223209_add_form_turnstile.rb diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb index 7bce2a7b0..06b6fc151 100644 --- a/app/controllers/admin/forms_controller.rb +++ b/app/controllers/admin/forms_controller.rb @@ -573,6 +573,8 @@ def form_admin_options_params :kind, :aasm_state, :early_submission, + :enable_turnstile, + :append_id_to_success_text, :notes, :status, :title, diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 3d61bffd6..2144c782f 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -81,13 +81,21 @@ def create private def create_in_local_database(submission) + if submission.form.enable_turnstile? && !verify_turnstile(params["cf-turnstile-response"]) + submission.errors.add(:base, "Turnstile verification failed") + end + respond_to do |format| - if submission.save + if submission.errors.empty? && submission.save format.html do redirect_to submit_touchpoint_path(submission.form), notice: 'Thank You. Response was submitted successfully.' end format.json do + form_success_text = submission.form.append_id_to_success_text? ? + submission.form.success_text + "

Your Response ID is: #{submission.uuid[-12..-1]}" : + submission.form.success_text + render json: { submission: { id: submission.uuid, @@ -115,6 +123,8 @@ def create_in_local_database(submission) id: submission.form.uuid, name: submission.form.name, organization_name: submission.organization_name, + success_text_heading: submission.form.success_text_heading, + success_text: form_success_text, }, }, }, @@ -151,10 +161,29 @@ def set_form def submission_params permitted_fields = @form.questions.collect(&:answer_field) permitted_fields << %i[language location_code referer hostname page query_string fba_directive] + permitted_fields << %i[cf-turnstile-response] params.require(:submission).permit(permitted_fields) end def form_requires_verification @form.verify_csrf? end + + + private + + def verify_turnstile(response_token) + secret_key = ENV.fetch("TURNSTILE_SECRET_KEY", nil) + uri = URI("https://challenges.cloudflare.com/turnstile/v0/siteverify") + + response = Net::HTTP.post_form(uri, { + "secret" => secret_key, + "response" => response_token, + "remoteip" => request.remote_ip + }) + + json = JSON.parse(response.body) + json["success"] == true + false + end end diff --git a/app/views/admin/forms/_form_manager_options.html.erb b/app/views/admin/forms/_form_manager_options.html.erb index db6b91ac0..5815d269b 100644 --- a/app/views/admin/forms/_form_manager_options.html.erb +++ b/app/views/admin/forms/_form_manager_options.html.erb @@ -95,6 +95,30 @@ <%= f.text_field :expiration_date, class: "usa-input" %> +
+ Enable Cloudfront Turnstile +
+ <%= f.check_box :enable_turnstile, class: "usa-checkbox__input" %> + <%= f.label :enable_turnstile, class: "usa-checkbox__label" do %> + Enable Cloudfront Turnstile + + As a spam prevention mechanism + + <% end %> +
+
+
+ Append ID to the form's Success Text +
+ <%= f.check_box :append_id_to_success_text, class: "usa-checkbox__input" %> + <%= f.label :append_id_to_success_text, class: "usa-checkbox__label" do %> + Append ID to Form's Success Text + + Append "Your Response ID is: 1307" to the Form's Success Text + + <% end %> +
+
Display Organization Tag Logo
diff --git a/app/views/components/forms/_custom.html.erb b/app/views/components/forms/_custom.html.erb index 2a28b518a..cb0b401eb 100644 --- a/app/views/components/forms/_custom.html.erb +++ b/app/views/components/forms/_custom.html.erb @@ -107,6 +107,12 @@ <% end %>
<% end %> + <% if form.enable_turnstile? %> +
+
" data-callback="onTurnstileSuccess">
+ <%= hidden_field_tag "cf-turnstile-response", nil %> +
+ <% end %> <%- if section == form.form_sections.last && !form.suppress_submit_button %> <% end %> @@ -125,3 +131,13 @@ autocomplete="off"> + +<% if form.enable_turnstile? %> + + + +<% end %> \ No newline at end of file diff --git a/app/views/components/widget/_fba.js.erb b/app/views/components/widget/_fba.js.erb index f7e1b4075..2b6482449 100644 --- a/app/views/components/widget/_fba.js.erb +++ b/app/views/components/widget/_fba.js.erb @@ -413,7 +413,7 @@ function FBAform(d, N) { successText: function() { return this.options.successText; }, - showFormSuccess: function(e) { + showFormSuccess: function(headerText, bodyHTML) { var formComponent = this.formComponent(); var formElement = this.formElement(); var alertElement = formComponent.querySelector(".fba-alert"); @@ -421,8 +421,8 @@ function FBAform(d, N) { var alertElementBody = formComponent.querySelector(".usa-alert__text"); // Display success Message - alertElementHeading.innerHTML += this.successHeadingText(); - alertElementBody.innerHTML = this.successText(); + alertElementHeading.innerHTML = headerText; + alertElementBody.innerHTML = bodyHTML alertElement.removeAttribute("hidden"); this.formComponent().scrollIntoView(); @@ -489,7 +489,11 @@ function FBAform(d, N) { if(submitButton) { submitButton.disabled = true; } - this.showFormSuccess(); + + const submission = JSON.parse(e.target.response).submission; + const successHeaderText = submission.form.success_text_heading; + const successBodyText = submission.form.success_text; + this.showFormSuccess(successHeaderText, successBodyText); } else if (e.target.status === 422) { // FORM ERRORS this.successState = false; d.dispatchEvent(new Event('onTouchpointsFormSubmissionError')); @@ -548,6 +552,9 @@ function FBAform(d, N) { xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8;"); xhr.onload = callback.bind(this); xhr.send(JSON.stringify({ + <% if form.enable_turnstile? %> + "cf-turnstile-response" : form.querySelector("input[name='cf-turnstile-response']") ? form.querySelector("input[name='cf-turnstile-response']").value : null, + <% end %> "submission": params, <%- if form.verify_csrf? %> "authenticity_token": form.querySelector("#authenticity_token") ? diff --git a/db/migrate/20250401201045_append_id_to_success_text.rb b/db/migrate/20250401201045_append_id_to_success_text.rb new file mode 100644 index 000000000..ec5b11d30 --- /dev/null +++ b/db/migrate/20250401201045_append_id_to_success_text.rb @@ -0,0 +1,5 @@ +class AppendIdToSuccessText < ActiveRecord::Migration[8.0] + def change + add_column :forms, :append_id_to_success_text, :boolean, default: false, comment: "Set to true to append a response ID to the form's success_text" + end +end diff --git a/db/migrate/20250401223209_add_form_turnstile.rb b/db/migrate/20250401223209_add_form_turnstile.rb new file mode 100644 index 000000000..fa86b93ed --- /dev/null +++ b/db/migrate/20250401223209_add_form_turnstile.rb @@ -0,0 +1,5 @@ +class AddFormTurnstile < ActiveRecord::Migration[8.0] + def change + add_column :forms, :enable_turnstile, :boolean, default: false, comment: "Set to true to enable Cloudfront Turnstile" + end +end diff --git a/db/schema.rb b/db/schema.rb index 543986efe..35f2e180f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_03_07_211304) do +ActiveRecord::Schema[8.0].define(version: 2025_04_01_223209) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -302,6 +302,8 @@ t.string "short_uuid", limit: 8 t.boolean "enforce_new_submission_validations", default: true t.integer "service_stage_id" + t.boolean "append_id_to_success_text", default: false, comment: "Set to true to append a response ID to the form's success_text" + t.boolean "enable_turnstile", default: false, comment: "Set to true to enable Cloudfront Turnstile" t.index ["legacy_touchpoint_id"], name: "index_forms_on_legacy_touchpoint_id" t.index ["legacy_touchpoint_uuid"], name: "index_forms_on_legacy_touchpoint_uuid" t.index ["organization_id"], name: "index_forms_on_organization_id" From 1fc3a5453499213abc8908d0bc2c34f295fbd504 Mon Sep 17 00:00:00 2001 From: Ryan Wold Date: Tue, 1 Apr 2025 17:19:53 -0700 Subject: [PATCH 3/4] enable turnstile --- app/controllers/submissions_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 2144c782f..6c645a431 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -184,6 +184,5 @@ def verify_turnstile(response_token) json = JSON.parse(response.body) json["success"] == true - false end end From 00396337df65646b222962af91c6571de0b09113 Mon Sep 17 00:00:00 2001 From: Ryan Wold Date: Tue, 1 Apr 2025 18:07:24 -0700 Subject: [PATCH 4/4] load turnstile when embedded --- app/views/components/forms/_custom.html.erb | 12 +----------- app/views/components/widget/_fba.js.erb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/views/components/forms/_custom.html.erb b/app/views/components/forms/_custom.html.erb index cb0b401eb..e1214be26 100644 --- a/app/views/components/forms/_custom.html.erb +++ b/app/views/components/forms/_custom.html.erb @@ -109,7 +109,7 @@ <% end %> <% if form.enable_turnstile? %>
-
" data-callback="onTurnstileSuccess">
+
">
<%= hidden_field_tag "cf-turnstile-response", nil %>
<% end %> @@ -131,13 +131,3 @@ autocomplete="off"> - -<% if form.enable_turnstile? %> - - - -<% end %> \ No newline at end of file diff --git a/app/views/components/widget/_fba.js.erb b/app/views/components/widget/_fba.js.erb index 2b6482449..35e3ee987 100644 --- a/app/views/components/widget/_fba.js.erb +++ b/app/views/components/widget/_fba.js.erb @@ -38,6 +38,9 @@ function FBAform(d, N) { if (this.options.formSpecificScript) { this.options.formSpecificScript(); } + <% if form.enable_turnstile? %> + this.loadTurnstile() + <% end %> d.dispatchEvent(new CustomEvent('onTouchpointsFormLoaded', { detail: { formComponent: this @@ -642,6 +645,18 @@ function FBAform(d, N) { modalId: function() { return `fba-modal-${this.options.formId}`; }, + <% if form.enable_turnstile? %> + loadTurnstile: function() { + let script = document.createElement("script"); + script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js"; + script.async = true; + script.defer = true; + script.onload = function() { + document.querySelector("input[name='cf-turnstile-response']").value = token; + }; + document.head.appendChild(script); + }, + <% end %> }; };