diff --git a/lib/recaptcha/adapters/controller_methods.rb b/lib/recaptcha/adapters/controller_methods.rb index b9b962a..8292755 100644 --- a/lib/recaptcha/adapters/controller_methods.rb +++ b/lib/recaptcha/adapters/controller_methods.rb @@ -17,6 +17,11 @@ def verify_recaptcha(options = {}) begin verified = if Recaptcha.invalid_response?(recaptcha_response) + @_recaptcha_failure_reason = if recaptcha_response.nil? + "No recaptcha response/param(:action) found." + else + "Recaptcha response/param(:action) was invalid." + end false else unless options[:skip_remote_ip] @@ -26,10 +31,21 @@ def verify_recaptcha(options = {}) success, @_recaptcha_reply = Recaptcha.verify_via_api_call(recaptcha_response, options.merge(with_reply: true)) + unless success + @_recaptcha_failure_reason = if @_recaptcha_reply["score"] && + @_recaptcha_reply["score"].to_f < options[:minimum_score].to_f + "Recaptcha score didn't exceed the minimum: #{@_recaptcha_reply["score"]} < #{options[:minimum_score]}." + elsif @_recaptcha_reply['error-codes'] + "Recaptcha api call returned with error-codes: #{@_recaptcha_reply['error-codes']}." + else + "Recaptcha failure after api call. Api reply: #{@_recaptcha_reply}." + end + end success end if verified + @_recaptcha_failure_reason = nil flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model true else @@ -41,6 +57,7 @@ def verify_recaptcha(options = {}) false end rescue Timeout::Error + @_recaptcha_failure_reason = "Recaptcha server unreachable." if Recaptcha.configuration.handle_timeouts_gracefully recaptcha_error( model, @@ -57,13 +74,17 @@ def verify_recaptcha(options = {}) end def verify_recaptcha!(options = {}) - verify_recaptcha(options) || raise(VerifyError) + verify_recaptcha(options) || raise(VerifyError, @_recaptcha_failure_reason) end def recaptcha_reply @_recaptcha_reply if defined?(@_recaptcha_reply) end + def recaptcha_failure_reason + @_recaptcha_failure_reason + end + def recaptcha_error(model, attribute, message) if model model.errors.add(attribute, message) diff --git a/test/verify_test.rb b/test/verify_test.rb index 2fb9953..4fddd72 100644 --- a/test/verify_test.rb +++ b/test/verify_test.rb @@ -13,6 +13,7 @@ def initialize public :verify_recaptcha! public :recaptcha_reply public :recaptcha_response_token + public :recaptcha_failure_reason end describe 'controller helpers' do @@ -36,6 +37,22 @@ def initialize assert_equal :foo, @controller.verify_recaptcha! end + + it "raise with informative error message when it fails" do + response_hash = { + success: true, + action: 'homepage', + score: 0.4 + } + + expect_http_post.to_return(body: response_hash.to_json) + + error = assert_raises Recaptcha::VerifyError do + @controller.verify_recaptcha!(minimum_score: 0.9) + end + + assert_equal "Recaptcha score didn't exceed the minimum: 0.4 < 0.9.", error.message + end end describe "#verify_recaptcha" do @@ -59,6 +76,7 @@ def initialize refute @controller.verify_recaptcha assert_equal "reCAPTCHA verification failed, please try again.", @controller.flash[:recaptcha_error] + assert_equal "Recaptcha failure after api call. Api reply: {\"foo\"=>\"false\", \"bar\"=>\"invalid-site-secret-key\"}.", @controller.recaptcha_failure_reason end it "adds an error to the model" do @@ -79,6 +97,7 @@ def initialize assert @controller.verify_recaptcha(secret_key: key) assert_nil @controller.flash[:recaptcha_error] + assert_nil @controller.recaptcha_failure_reason end it "returns true on success without remote_ip" do @@ -304,6 +323,7 @@ def initialize it "fails when score is below minimum_score" do refute verify_recaptcha(minimum_score: 0.5) assert_flash_error + assert_equal "Recaptcha score didn't exceed the minimum: 0.4 < 0.5.", @controller.recaptcha_failure_reason end it "fails when response doesn't include a score" do @@ -387,6 +407,19 @@ def initialize end end + describe "recaptcha_failure_reason" do + let(:default_response_hash) { { + success: true, + score: 0.97, + 'error-codes': ['some-api-error'] + } } + it "contains the error-codes when reply has those" do + expect_http_post.to_return(body: success_body) + refute verify_recaptcha() + assert_equal "Recaptcha api call returned with error-codes: [\"some-api-error\"].", @controller.recaptcha_failure_reason + end + end + describe "#recaptcha_response_token" do it "returns an empty string when params are empty and no action is provided" do @controller.params = {}