diff --git a/.env.example b/.env.example index 794aa7b2..d8b6f6f7 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,10 @@ AWS_SECRET_ACCESS_KEY= STRIPE_SECRET_KEY= STRIPE_ENDPOINT_SECRET= ROLLBAR_ACCESS_TOKEN= -ROLLBAR_POST_CLIENT_ITEM_ACCESS_TOKEN= \ No newline at end of file +ROLLBAR_POST_CLIENT_ITEM_ACCESS_TOKEN= + +# These keys always pass the registration turnstile check. Other keys +# are availble to test various failure scenarios +# https://developers.cloudflare.com/turnstile/troubleshooting/testing/ +CLOUDFLARE_TURNSTILE_SITE_KEY=1x00000000000000000000AA +CLOUDFLARE_TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA diff --git a/Gemfile b/Gemfile index bc48cf1e..523f562b 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'pundit', '~> 2.3' gem 'rack-maintenance' gem 'rails', '~> 7.1' gem 'rails_autolink', '~> 1.1' +gem 'rails_cloudflare_turnstile' gem 'redcarpet', '~> 3.6' gem 'redis', '~> 5.0' gem 'rollbar' diff --git a/Gemfile.lock b/Gemfile.lock index b2a6a0c7..a9e1ea19 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,6 +144,11 @@ GEM railties (>= 5.0.0) faker (3.3.1) i18n (>= 1.8.11, < 2) + faraday (2.10.0) + faraday-net_http (>= 2.0, < 3.2) + logger + faraday-net_http (3.1.0) + net-http ferrum (0.14) addressable (~> 2.5) concurrent-ruby (~> 1.1) @@ -183,6 +188,7 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.0) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -202,6 +208,8 @@ GEM ruby2_keywords (>= 0.0.5) msgpack (1.7.2) mutex_m (0.2.0) + net-http (0.4.1) + uri net-imap (0.4.10) date net-protocol @@ -272,6 +280,9 @@ GEM actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) + rails_cloudflare_turnstile (0.2.1) + faraday (>= 1.0, < 3.0) + rails (>= 6.0, < 8) railties (7.1.3.1) actionpack (= 7.1.3.1) activesupport (= 7.1.3.1) @@ -360,6 +371,7 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -407,6 +419,7 @@ DEPENDENCIES rack-maintenance rails (~> 7.1) rails_autolink (~> 1.1) + rails_cloudflare_turnstile redcarpet (~> 3.6) redis (~> 5.0) rollbar diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index ba255f13..df08bc2c 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -2,6 +2,7 @@ class RegistrationsController < ApplicationController skip_before_action :authenticate + before_action :validate_cloudflare_turnstile, only: :create def new skip_authorization diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index b770e6cc..ad367c8b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,6 +14,7 @@ <% if Rails.env.production? %> <% end %> + <%= yield :head %>
diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb index e0023f66..1069539d 100644 --- a/app/views/registrations/new.html.erb +++ b/app/views/registrations/new.html.erb @@ -1,9 +1,14 @@ +<% content_for :head do %> + +<% end %> + <%= render 'shared/page_header', text: 'Sign up' %> -<%= form_with(url: sign_up_path, builder: TailwindFormBuilder) do |form| %> +<%= form_with(url: sign_up_path, builder: TailwindFormBuilder, data: { turbo: false }) do |form| %> <%= render('shared/errors', model: @user) %> <%= form.email_field :email, value: @user.email, required: true, autofocus: true, autocomplete: "email", class: 'w-full mb-3' %> <%= form.password_field :password, required: true, autocomplete: "new-password", class: 'w-full mb-3' %> - <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", class: 'w-full mb-3' %> + <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", class: 'w-full mb-6' %> + <%= cloudflare_turnstile %> <%= form.submit "Sign up", class: 'w-full mt-6' %> <% end %> diff --git a/config/initializers/cloudflare_turnstile.rb b/config/initializers/cloudflare_turnstile.rb new file mode 100644 index 00000000..fca13aba --- /dev/null +++ b/config/initializers/cloudflare_turnstile.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RailsCloudflareTurnstile.configure do |c| + c.site_key = ENV.fetch('CLOUDFLARE_TURNSTILE_SITE_KEY') + c.secret_key = ENV.fetch('CLOUDFLARE_TURNSTILE_SECRET_KEY') + c.fail_open = true + c.mock_enabled = false +end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 24bbf61f..01f46829 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -13,6 +13,10 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :cuprite + def setup + stub_successful_cloudflare_turnstile_request + end + def log_in_as(user) visit log_in_url fill_in :email, with: user.email @@ -26,4 +30,11 @@ def sign_out click_on 'avatar' click_on 'Log out' end + + private + + def stub_successful_cloudflare_turnstile_request + stub_request(:post, 'https://challenges.cloudflare.com/turnstile/v0/siteverify') + .to_return(status: 200, body: { success: true }.to_json) + end end diff --git a/test/controllers/registrations_controller_test.rb b/test/controllers/registrations_controller_test.rb index fc5fedc8..c808e026 100644 --- a/test/controllers/registrations_controller_test.rb +++ b/test/controllers/registrations_controller_test.rb @@ -4,6 +4,9 @@ class RegistrationsControllerTest < ActionDispatch::IntegrationTest def setup + stub_request(:post, 'https://challenges.cloudflare.com/turnstile/v0/siteverify') + .to_return(status: 200, body: { success: true }.to_json) + @album = create(:album) log_in_as(create(:user, admin: true)) end @@ -58,4 +61,14 @@ def setup assert_response :unprocessable_entity assert_select 'h2', text: /errors prohibited this user from being saved/ end + + test '#create makes a call to the cloudflare turnstile API to validate the request' do + post sign_up_url, params: { + email: 'user@example.com', + password: 'Secret1*3*5*', + password_confirmation: 'Secret1*3*5*' + } + + assert_requested :post, 'https://challenges.cloudflare.com/turnstile/v0/siteverify' + end end