diff --git a/Gemfile b/Gemfile index 186f926..2527d71 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } gemspec gem "faraday", "~> 1.0" -gem "rails", "~> 6.1" +gem "rails", "~> 6.1.7.1" gem "rake", "~> 13.0" group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index ac3657a..c125cce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,60 +8,60 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7) - actionpack (= 6.1.7) - activesupport (= 6.1.7) + actioncable (6.1.7.1) + actionpack (= 6.1.7.1) + activesupport (= 6.1.7.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7) - actionpack (= 6.1.7) - activejob (= 6.1.7) - activerecord (= 6.1.7) - activestorage (= 6.1.7) - activesupport (= 6.1.7) + actionmailbox (6.1.7.1) + actionpack (= 6.1.7.1) + activejob (= 6.1.7.1) + activerecord (= 6.1.7.1) + activestorage (= 6.1.7.1) + activesupport (= 6.1.7.1) mail (>= 2.7.1) - actionmailer (6.1.7) - actionpack (= 6.1.7) - actionview (= 6.1.7) - activejob (= 6.1.7) - activesupport (= 6.1.7) + actionmailer (6.1.7.1) + actionpack (= 6.1.7.1) + actionview (= 6.1.7.1) + activejob (= 6.1.7.1) + activesupport (= 6.1.7.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7) - actionview (= 6.1.7) - activesupport (= 6.1.7) + actionpack (6.1.7.1) + actionview (= 6.1.7.1) + activesupport (= 6.1.7.1) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7) - actionpack (= 6.1.7) - activerecord (= 6.1.7) - activestorage (= 6.1.7) - activesupport (= 6.1.7) + actiontext (6.1.7.1) + actionpack (= 6.1.7.1) + activerecord (= 6.1.7.1) + activestorage (= 6.1.7.1) + activesupport (= 6.1.7.1) nokogiri (>= 1.8.5) - actionview (6.1.7) - activesupport (= 6.1.7) + actionview (6.1.7.1) + activesupport (= 6.1.7.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.7) - activesupport (= 6.1.7) + activejob (6.1.7.1) + activesupport (= 6.1.7.1) globalid (>= 0.3.6) - activemodel (6.1.7) - activesupport (= 6.1.7) - activerecord (6.1.7) - activemodel (= 6.1.7) - activesupport (= 6.1.7) - activestorage (6.1.7) - actionpack (= 6.1.7) - activejob (= 6.1.7) - activerecord (= 6.1.7) - activesupport (= 6.1.7) + activemodel (6.1.7.1) + activesupport (= 6.1.7.1) + activerecord (6.1.7.1) + activemodel (= 6.1.7.1) + activesupport (= 6.1.7.1) + activestorage (6.1.7.1) + actionpack (= 6.1.7.1) + activejob (= 6.1.7.1) + activerecord (= 6.1.7.1) + activesupport (= 6.1.7.1) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7) + activesupport (6.1.7.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -107,7 +107,7 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - globalid (1.0.0) + globalid (1.0.1) activesupport (>= 5.0) hashdiff (1.0.1) i18n (1.12.0) @@ -117,7 +117,7 @@ GEM loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.8.0) + mail (2.8.0.1) mini_mime (>= 0.1.1) net-imap net-pop @@ -137,9 +137,9 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.13.10-arm64-darwin) + nokogiri (1.14.0-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.10-x86_64-linux) + nokogiri (1.14.0-x86_64-linux) racc (~> 1.4) parallel (1.22.1) parser (3.2.0.0) @@ -149,32 +149,32 @@ GEM method_source (~> 1.0) public_suffix (5.0.1) racc (1.6.2) - rack (2.2.5) + rack (2.2.6.2) rack-test (2.0.2) rack (>= 1.3) - rails (6.1.7) - actioncable (= 6.1.7) - actionmailbox (= 6.1.7) - actionmailer (= 6.1.7) - actionpack (= 6.1.7) - actiontext (= 6.1.7) - actionview (= 6.1.7) - activejob (= 6.1.7) - activemodel (= 6.1.7) - activerecord (= 6.1.7) - activestorage (= 6.1.7) - activesupport (= 6.1.7) + rails (6.1.7.1) + actioncable (= 6.1.7.1) + actionmailbox (= 6.1.7.1) + actionmailer (= 6.1.7.1) + actionpack (= 6.1.7.1) + actiontext (= 6.1.7.1) + actionview (= 6.1.7.1) + activejob (= 6.1.7.1) + activemodel (= 6.1.7.1) + activerecord (= 6.1.7.1) + activestorage (= 6.1.7.1) + activesupport (= 6.1.7.1) bundler (>= 1.15.0) - railties (= 6.1.7) + railties (= 6.1.7.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.4.4) loofah (~> 2.19, >= 2.19.1) - railties (6.1.7) - actionpack (= 6.1.7) - activesupport (= 6.1.7) + railties (6.1.7.1) + actionpack (= 6.1.7.1) + activesupport (= 6.1.7.1) method_source rake (>= 12.2) thor (~> 1.0) @@ -258,7 +258,7 @@ DEPENDENCIES bundle-audit (~> 0.1.0) faraday (~> 1.0) pry (~> 0.14.1) - rails (~> 6.1) + rails (~> 6.1.7.1) rails_cloudflare_turnstile! rake (~> 13.0) rspec (~> 3.0) diff --git a/app/assets/images/turnstile-logo.svg b/app/assets/images/turnstile-logo.svg new file mode 100644 index 0000000..f12870b --- /dev/null +++ b/app/assets/images/turnstile-logo.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/mock_cloudflare_turnstile_api.js b/app/assets/javascripts/mock_cloudflare_turnstile_api.js new file mode 100644 index 0000000..0fec7d9 --- /dev/null +++ b/app/assets/javascripts/mock_cloudflare_turnstile_api.js @@ -0,0 +1,20 @@ +(function() { + function mock_cloudflare_turnstile_response() { + setTimeout(function() { + console.log("setting mock cloudflare turnstile to ✓"); + for (let elem of document.getElementsByClassName("cf-turnstile")) { + elem.getElementsByTagName("p")[0].style.color = 'green'; + elem.getElementsByTagName("p").innerHTML = "Mocked CAPTCHA succeeded" + if (elem.dataset.callback !== undefined) { + eval(elem.dataset.callback).call("mocked"); + } + } + }, 1500); + } + + if (document.readyState !== 'loading') { + mock_cloudflare_turnstile_response() + } else { + document.addEventListener('DOMContentLoaded', mock_cloudflare_turnstile_response); + } +})(); diff --git a/lib/rails_cloudflare_turnstile.rb b/lib/rails_cloudflare_turnstile.rb index e82366c..38e612e 100644 --- a/lib/rails_cloudflare_turnstile.rb +++ b/lib/rails_cloudflare_turnstile.rb @@ -1,4 +1,5 @@ require "rails_cloudflare_turnstile/version" +require "rails_cloudflare_turnstile/engine" require "rails_cloudflare_turnstile/configuration" require "rails_cloudflare_turnstile/errors" require "rails_cloudflare_turnstile/controller_helpers" diff --git a/lib/rails_cloudflare_turnstile/configuration.rb b/lib/rails_cloudflare_turnstile/configuration.rb index a877224..b7c1ac0 100644 --- a/lib/rails_cloudflare_turnstile/configuration.rb +++ b/lib/rails_cloudflare_turnstile/configuration.rb @@ -16,9 +16,12 @@ class Configuration # Timeout for operations with Cloudflare attr_accessor :timeout - # size for the widget ("regular" or "compact") + # size for the widget (:regular or :compact) attr_accessor :size + # theme for the widget (:auto, :light, or :dark) + attr_accessor :theme + attr_accessor :enabled attr_accessor :mock_enabled @@ -31,6 +34,7 @@ def initialize @mock_enabled = nil @timeout = 5.0 @size = :regular + @theme = :auto @validation_url = "https://challenges.cloudflare.com/turnstile/v0/siteverify" end @@ -39,6 +43,7 @@ def validate! raise "Must set secret key" if @secret_key.nil? @size = @size.to_sym raise "Size must be one of ':regular' or ':compact'" unless [:regular, :compact].include? @size + raise "Theme must be one of :auto, :light, or :dark" unless [:auto, :light, :dark].include? @theme end def disabled? diff --git a/lib/rails_cloudflare_turnstile/engine.rb b/lib/rails_cloudflare_turnstile/engine.rb new file mode 100644 index 0000000..8d3fceb --- /dev/null +++ b/lib/rails_cloudflare_turnstile/engine.rb @@ -0,0 +1,9 @@ +module RailsCloudflareTurnstile + class Engine < ::Rails::Engine + initializer "rails_cloudflare_turnstile.precompile" do |app| + %w[javascripts images].each do |sub| + app.config.assets.paths << root.join("assets", sub).to_s + end + end + end +end diff --git a/lib/rails_cloudflare_turnstile/railtie.rb b/lib/rails_cloudflare_turnstile/railtie.rb index da7a502..0c58617 100644 --- a/lib/rails_cloudflare_turnstile/railtie.rb +++ b/lib/rails_cloudflare_turnstile/railtie.rb @@ -1,6 +1,6 @@ module RailsCloudflareTurnstile class Railtie < ::Rails::Railtie - initializer "rails_cloudflare_turnstile" do + initializer "rails_cloudflare_turnstile" do |app| ActiveSupport.on_load(:action_controller) do include RailsCloudflareTurnstile::ControllerHelpers end @@ -8,6 +8,8 @@ class Railtie < ::Rails::Railtie ActiveSupport.on_load(:action_view) do include RailsCloudflareTurnstile::ViewHelpers end + + app.config.assets.precompile += %w[mock_cloudflare_turnstile_api.js turnstile-logo.svg] end end end diff --git a/lib/rails_cloudflare_turnstile/view_helpers.rb b/lib/rails_cloudflare_turnstile/view_helpers.rb index ef69311..7cd83cd 100644 --- a/lib/rails_cloudflare_turnstile/view_helpers.rb +++ b/lib/rails_cloudflare_turnstile/view_helpers.rb @@ -2,39 +2,45 @@ module RailsCloudflareTurnstile module ViewHelpers - def cloudflare_turnstile(action: "other") + def cloudflare_turnstile(action: "other", data_callback: nil) if RailsCloudflareTurnstile.enabled? content_tag(:div, class: "cloudflare-turnstile") do - concat turnstile_div(action) + concat turnstile_div(action, data_callback: data_callback) end elsif RailsCloudflareTurnstile.mock_enabled? content_tag(:div, class: "cloudflare-turnstile") do - concat mock_turnstile_div(action) + concat mock_turnstile_div(action, data_callback: data_callback) end end end def cloudflare_turnstile_script_tag - return nil unless RailsCloudflareTurnstile.enabled? - content_tag(:script, :src => js_src, "async" => true) do - "" + if RailsCloudflareTurnstile.enabled? + content_tag(:script, :src => js_src, "async" => true) do + "" + end + elsif RailsCloudflareTurnstile.mock_enabled? + content_tag(:script, :src => mock_js, "async" => true) do + "" + end end end private - def turnstile_div(action) + def turnstile_div(action, data_callback: nil) config = RailsCloudflareTurnstile.configuration - content_tag(:div, :class => "cf-turnstile", "data-sitekey" => site_key, "data-size" => config.size, "data-action" => action) do + content_tag(:div, :class => "cf-turnstile", "data-sitekey" => site_key, "data-size" => config.size, "data-action" => action, "data-callback" => data_callback, "data-theme" => config.theme) do "" end end - def mock_turnstile_div(action) - content_tag(:div, class: "cf-turnstile", style: "width: 300px; height: 65px: border: 1px solid gray") do + def mock_turnstile_div(action, data_callback: nil) + content_tag(:div, :class => "cf-turnstile", :style => "width: 300px; height: 65px; border: 1px solid gray; display: flex; flex-direction: row; justify-content: center; align-items: center; margin: 10px;", "data-callback" => data_callback) do [ tag.input(type: "hidden", name: "cf-turnstile-response", value: "mocked"), - content_tag(:p) do + image_tag("turnstile-logo.svg"), + content_tag(:p, style: "margin: 0") do "CAPTCHA goes here in production" end ].reduce(:<<) @@ -48,5 +54,9 @@ def site_key def js_src "https://challenges.cloudflare.com/turnstile/v0/api.js" end + + def mock_js + javascript_path "mock_cloudflare_turnstile_api.js" + end end end diff --git a/rails-cloudflare-turnstile.gemspec b/rails-cloudflare-turnstile.gemspec index 4a704ff..0eb52bc 100644 --- a/rails-cloudflare-turnstile.gemspec +++ b/rails-cloudflare-turnstile.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = "https://github.com/instrumentl/rails-cloudflare-turnstile" spec.metadata["changelog_uri"] = "https://github.com/instrumentl/rails-cloudflare-turnstile/blob/main/CHANGELOG.md" - spec.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md", "CHANGELOG.md"] + spec.files = Dir["{app,lib}/**/*", "LICENSE", "Rakefile", "README.md", "CHANGELOG.md"] spec.require_paths = ["lib"] spec.add_dependency "rails", ">= 5.0", "< 7.1" diff --git a/spec/rails_cloudflare_turnstile/view_helpers_spec.rb b/spec/rails_cloudflare_turnstile/view_helpers_spec.rb index 9e22877..df9e0d3 100644 --- a/spec/rails_cloudflare_turnstile/view_helpers_spec.rb +++ b/spec/rails_cloudflare_turnstile/view_helpers_spec.rb @@ -11,6 +11,18 @@ include RailsCloudflareTurnstile::ViewHelpers attr_accessor :output_buffer + + def javascript_path(name) + "/mock/#{name}" + end + + def image_path(name) + "/mock/#{name}" + end + + def image_tag(name) + "".html_safe + end end.new end @@ -38,7 +50,7 @@ describe "#cloudflare_turnstile" do it do - expect(subject.cloudflare_turnstile(action: "an-action")).to eq "
" + expect(subject.cloudflare_turnstile(action: "an-action")).to eq "
" end end end @@ -51,15 +63,16 @@ end end - its(:cloudflare_turnstile_script_tag) { should eq nil } + its(:cloudflare_turnstile_script_tag) { should eq "" } describe "#cloudflare_turnstile" do it do expect(subject.cloudflare_turnstile(action: "an-action")).to eq <<-EOF
-
+
-

CAPTCHA goes here in production

+ +

CAPTCHA goes here in production

EOF