invisible_captcha 0.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ControllerExt
3
5
  module ClassMethods
4
6
  def invisible_captcha(options = {})
5
- if respond_to?(:before_action)
6
- before_action(options) do
7
+ if options.key?(:prepend)
8
+ prepend_before_action(options) do
7
9
  detect_spam(options)
8
10
  end
9
11
  else
10
- before_filter(options) do
12
+ before_action(options) do
11
13
  detect_spam(options)
12
14
  end
13
15
  end
@@ -19,7 +21,7 @@ module InvisibleCaptcha
19
21
  def detect_spam(options = {})
20
22
  if timestamp_spam?(options)
21
23
  on_timestamp_spam(options)
22
- elsif honeypot_spam?(options)
24
+ elsif honeypot_spam?(options) || spinner_spam?
23
25
  on_spam(options)
24
26
  end
25
27
  end
@@ -28,11 +30,7 @@ module InvisibleCaptcha
28
30
  if action = options[:on_timestamp_spam]
29
31
  send(action)
30
32
  else
31
- if respond_to?(:redirect_back)
32
- redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
33
- else
34
- redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
35
- end
33
+ redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
36
34
  end
37
35
  end
38
36
 
@@ -53,11 +51,11 @@ module InvisibleCaptcha
53
51
 
54
52
  return false unless enabled
55
53
 
56
- timestamp = session[:invisible_captcha_timestamp]
54
+ timestamp = session.delete(:invisible_captcha_timestamp)
57
55
 
58
56
  # Consider as spam if timestamp not in session, cause that means the form was not fetched at all
59
57
  unless timestamp
60
- logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp not found in session.")
58
+ warn_spam("Invisible Captcha timestamp not found in session.")
61
59
  return true
62
60
  end
63
61
 
@@ -66,7 +64,16 @@ module InvisibleCaptcha
66
64
 
67
65
  # Consider as spam if form submitted too quickly
68
66
  if time_to_submit < threshold
69
- logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
67
+ warn_spam("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
68
+ return true
69
+ end
70
+
71
+ false
72
+ end
73
+
74
+ def spinner_spam?
75
+ if InvisibleCaptcha.spinner_enabled && params[:spinner] != session[:invisible_captcha_spinner]
76
+ warn_spam("Invisible Captcha spinner value mismatch")
70
77
  return true
71
78
  end
72
79
 
@@ -81,7 +88,8 @@ module InvisibleCaptcha
81
88
  # If honeypot is defined for this controller-action, search for:
82
89
  # - honeypot: params[:subtitle]
83
90
  # - honeypot with scope: params[:topic][:subtitle]
84
- if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
91
+ if params[honeypot].present? || params.dig(scope, honeypot).present?
92
+ warn_spam("Invisible Captcha honeypot param '#{honeypot}' was present.")
85
93
  return true
86
94
  else
87
95
  # No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
@@ -90,11 +98,29 @@ module InvisibleCaptcha
90
98
  end
91
99
  else
92
100
  InvisibleCaptcha.honeypots.each do |default_honeypot|
93
- return true if params[default_honeypot].present?
101
+ if params[default_honeypot].present?
102
+ warn_spam("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
103
+ return true
104
+ end
94
105
  end
95
106
  end
96
107
 
97
108
  false
98
109
  end
110
+
111
+ def warn_spam(message)
112
+ logger.warn("Potential spam detected for IP #{request.remote_ip}. #{message}")
113
+
114
+ ActiveSupport::Notifications.instrument(
115
+ 'invisible_captcha.spam_detected',
116
+ message: message,
117
+ remote_ip: request.remote_ip,
118
+ user_agent: request.user_agent,
119
+ controller: params[:controller],
120
+ action: params[:action],
121
+ url: request.url,
122
+ params: request.filtered_parameters
123
+ )
124
+ end
99
125
  end
100
126
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module FormHelpers
3
5
  def invisible_captcha(honeypot, options = {})
4
6
  @template.invisible_captcha(honeypot, self.object_name, options)
5
7
  end
6
8
  end
7
- end
9
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  class Railtie < Rails::Railtie
3
5
  initializer 'invisible_captcha.rails_integration' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
- VERSION = "0.10.0"
4
+ VERSION = "2.0.0"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ViewHelpers
3
5
  # Builds the honeypot html
@@ -8,9 +10,17 @@ module InvisibleCaptcha
8
10
  #
9
11
  # @return [String] the generated html
10
12
  def invisible_captcha(honeypot = nil, scope = nil, options = {})
11
- if InvisibleCaptcha.timestamp_enabled
13
+ @captcha_ocurrences = 0 unless defined?(@captcha_ocurrences)
14
+ @captcha_ocurrences += 1
15
+
16
+ if InvisibleCaptcha.timestamp_enabled || InvisibleCaptcha.spinner_enabled
12
17
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
13
18
  end
19
+
20
+ if InvisibleCaptcha.spinner_enabled && @captcha_ocurrences == 1
21
+ session[:invisible_captcha_spinner] = InvisibleCaptcha.encode("#{session[:invisible_captcha_timestamp]}-#{current_request.remote_ip}")
22
+ end
23
+
14
24
  build_invisible_captcha(honeypot, scope, options)
15
25
  end
16
26
 
@@ -22,6 +32,10 @@ module InvisibleCaptcha
22
32
 
23
33
  private
24
34
 
35
+ def current_request
36
+ @request ||= request
37
+ end
38
+
25
39
  def build_invisible_captcha(honeypot = nil, scope = nil, options = {})
26
40
  if honeypot.is_a?(Hash)
27
41
  options = honeypot
@@ -41,7 +55,10 @@ module InvisibleCaptcha
41
55
  content_tag(:div, class: css_class) do
42
56
  concat styles unless InvisibleCaptcha.injectable_styles
43
57
  concat label_tag(build_label_name(honeypot, scope), label)
44
- concat text_field_tag(build_text_field_name(honeypot, scope), nil, options.merge(tabindex: -1))
58
+ concat text_field_tag(build_input_name(honeypot, scope), nil, default_honeypot_options.merge(options))
59
+ if InvisibleCaptcha.spinner_enabled
60
+ concat hidden_field_tag("spinner", session[:invisible_captcha_spinner])
61
+ end
45
62
  end
46
63
  end
47
64
 
@@ -54,7 +71,9 @@ module InvisibleCaptcha
54
71
 
55
72
  return if visible
56
73
 
57
- content_tag(:style, media: 'screen') do
74
+ nonce = content_security_policy_nonce if options[:nonce]
75
+
76
+ content_tag(:style, media: 'screen', nonce: nonce) do
58
77
  ".#{css_class} {#{InvisibleCaptcha.css_strategy}}"
59
78
  end
60
79
  end
@@ -67,12 +86,16 @@ module InvisibleCaptcha
67
86
  end
68
87
  end
69
88
 
70
- def build_text_field_name(honeypot, scope = nil)
89
+ def build_input_name(honeypot, scope = nil)
71
90
  if scope.present?
72
91
  "#{scope}[#{honeypot}]"
73
92
  else
74
93
  honeypot
75
94
  end
76
95
  end
96
+
97
+ def default_honeypot_options
98
+ { autocomplete: 'off', tabindex: -1 }
99
+ end
77
100
  end
78
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'invisible_captcha/version'
2
4
  require 'invisible_captcha/controller_ext'
3
5
  require 'invisible_captcha/view_helpers'
@@ -13,7 +15,9 @@ module InvisibleCaptcha
13
15
  :timestamp_threshold,
14
16
  :timestamp_enabled,
15
17
  :visual_honeypots,
16
- :injectable_styles
18
+ :injectable_styles,
19
+ :spinner_enabled,
20
+ :secret
17
21
 
18
22
  def init!
19
23
  # Default sentence for real users if text field was visible
@@ -31,9 +35,15 @@ module InvisibleCaptcha
31
35
  # Make honeypots visibles
32
36
  self.visual_honeypots = false
33
37
 
34
- # If enabled, you should call anywhere in of your layout the following helper, to inject the honeypot styles:
35
- # <%= invisible_captcha_styles %>
38
+ # If enabled, you should call anywhere in your layout the following helper, to inject the honeypot styles:
39
+ # <%= invisible_captcha_styles %>
36
40
  self.injectable_styles = false
41
+
42
+ # Spinner check enabled by default
43
+ self.spinner_enabled = true
44
+
45
+ # A secret key to encode some internal values
46
+ self.secret = ENV['INVISIBLE_CAPTCHA_SECRET'] || SecureRandom.hex(64)
37
47
  end
38
48
 
39
49
  def sentence_for_humans
@@ -68,6 +78,10 @@ module InvisibleCaptcha
68
78
  ].sample
69
79
  end
70
80
 
81
+ def encode(value)
82
+ Digest::MD5.hexdigest("#{self.secret}-#{value}")
83
+ end
84
+
71
85
  private
72
86
 
73
87
  def call_lambda_or_return(obj)
@@ -1,41 +1,27 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe InvisibleCaptcha::ControllerExt, type: :controller do
3
+ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
4
4
  render_views
5
5
 
6
- def switchable_post(action, params = {})
7
- if ::Rails::VERSION::STRING > '5'
8
- post action, params: params
9
- else
10
- post action, params
11
- end
12
- end
13
-
14
- def switchable_put(action, params = {})
15
- if ::Rails::VERSION::STRING > '5'
16
- put action, params: params
17
- else
18
- put action, params
19
- end
20
- end
21
-
22
6
  before(:each) do
23
7
  @controller = TopicsController.new
24
8
  request.env['HTTP_REFERER'] = 'http://test.host/topics'
9
+
25
10
  InvisibleCaptcha.init!
26
11
  InvisibleCaptcha.timestamp_threshold = 1
12
+ InvisibleCaptcha.spinner_enabled = false
27
13
  end
28
14
 
29
15
  context 'without invisible_captcha_timestamp in session' do
30
16
  it 'fails like if it was submitted too fast' do
31
- switchable_post :create, topic: { title: 'foo' }
17
+ post :create, params: { topic: { title: 'foo' } }
32
18
 
33
19
  expect(response).to redirect_to 'http://test.host/topics'
34
20
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
35
21
  end
36
22
 
37
23
  it 'passes if disabled at action level' do
38
- switchable_post :copy, topic: { title: 'foo' }
24
+ post :copy, params: { topic: { title: 'foo' } }
39
25
 
40
26
  expect(flash[:error]).not_to be_present
41
27
  expect(response.body).to be_present
@@ -43,7 +29,8 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
43
29
 
44
30
  it 'passes if disabled at app level' do
45
31
  InvisibleCaptcha.timestamp_enabled = false
46
- switchable_post :create, topic: { title: 'foo' }
32
+
33
+ post :create, params: { topic: { title: 'foo' } }
47
34
 
48
35
  expect(flash[:error]).not_to be_present
49
36
  expect(response.body).to be_present
@@ -56,32 +43,57 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
56
43
  end
57
44
 
58
45
  it 'fails if submission before timestamp_threshold' do
59
- switchable_post :create, topic: { title: 'foo' }
46
+ post :create, params: { topic: { title: 'foo' } }
60
47
 
61
48
  expect(response).to redirect_to 'http://test.host/topics'
62
49
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
50
+
51
+ # Make sure session is cleared
52
+ expect(session[:invisible_captcha_timestamp]).to be_nil
63
53
  end
64
54
 
65
- it 'allow a custom on_timestamp_spam callback' do
66
- switchable_put :update, id: 1, topic: { title: 'bar' }
55
+ it 'allows a custom on_timestamp_spam callback' do
56
+ put :update, params: { id: 1, topic: { title: 'bar' } }
67
57
 
68
58
  expect(response.status).to eq(204)
69
59
  end
70
60
 
61
+ it 'allows a new timestamp to be set in the on_timestamp_spam callback' do
62
+ @controller.singleton_class.class_eval do
63
+ def custom_timestamp_callback
64
+ session[:invisible_captcha_timestamp] = 2.seconds.from_now(Time.zone.now).iso8601
65
+ head(204)
66
+ end
67
+ end
68
+
69
+ expect { put :update, params: { id: 1, topic: { title: 'bar' } } }
70
+ .to change { session[:invisible_captcha_timestamp] }
71
+ .to be_present
72
+ end
73
+
71
74
  context 'successful submissions' do
72
75
  it 'passes if submission on or after timestamp_threshold' do
73
76
  sleep InvisibleCaptcha.timestamp_threshold
74
77
 
75
- switchable_post :create, topic: { title: 'foo' }
78
+ post :create, params: {
79
+ topic: {
80
+ title: 'foobar',
81
+ author: 'author',
82
+ body: 'body that passes validation'
83
+ }
84
+ }
76
85
 
77
86
  expect(flash[:error]).not_to be_present
78
87
  expect(response.body).to be_present
88
+
89
+ # Make sure session is cleared
90
+ expect(session[:invisible_captcha_timestamp]).to be_nil
79
91
  end
80
92
 
81
93
  it 'allow to set a custom timestamp_threshold per action' do
82
94
  sleep 2 # custom threshold
83
95
 
84
- switchable_post :publish, id: 1
96
+ post :publish, params: { id: 1 }
85
97
 
86
98
  expect(flash[:error]).not_to be_present
87
99
  expect(response.body).to be_present
@@ -92,33 +104,90 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
92
104
  context 'honeypot attribute' do
93
105
  before(:each) do
94
106
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
107
+
95
108
  # Wait for valid submission
96
109
  sleep InvisibleCaptcha.timestamp_threshold
97
110
  end
98
111
 
99
112
  it 'fails with spam' do
100
- switchable_post :create, topic: { subtitle: 'foo' }
113
+ post :create, params: { topic: { subtitle: 'foo' } }
101
114
 
102
115
  expect(response.body).to be_blank
103
116
  end
104
117
 
105
118
  it 'passes with no spam' do
106
- switchable_post :create, topic: { title: 'foo' }
119
+ post :create, params: { topic: { title: 'foo' } }
107
120
 
108
121
  expect(response.body).to be_present
109
122
  end
110
123
 
111
124
  it 'allow a custom on_spam callback' do
112
- switchable_put :update, id: 1, topic: { subtitle: 'foo' }
125
+ put :update, params: { id: 1, topic: { subtitle: 'foo' } }
113
126
 
114
127
  expect(response.body).to redirect_to(new_topic_path)
115
128
  end
116
129
 
117
130
  it 'honeypot is removed from params if you use a custom honeypot' do
118
- switchable_post :create, topic: { title: 'foo', subtitle: '' }
131
+ post :create, params: { topic: { title: 'foo', subtitle: '' } }
119
132
 
120
133
  expect(flash[:error]).not_to be_present
121
134
  expect(@controller.params[:topic].key?(:subtitle)).to eq(false)
122
135
  end
136
+
137
+ describe 'ActiveSupport::Notifications' do
138
+ let(:dummy_handler) { double(handle_event: nil) }
139
+
140
+ let!(:subscriber) do
141
+ subscriber = ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
142
+ dummy_handler.handle_event(data)
143
+ end
144
+
145
+ subscriber
146
+ end
147
+
148
+ after { ActiveSupport::Notifications.unsubscribe(subscriber) }
149
+
150
+ it 'dispatches an `invisible_captcha.spam_detected` event' do
151
+ expect(dummy_handler).to receive(:handle_event).once.with(
152
+ message: "Invisible Captcha honeypot param 'subtitle' was present.",
153
+ remote_ip: '0.0.0.0',
154
+ user_agent: 'Rails Testing',
155
+ controller: 'topics',
156
+ action: 'create',
157
+ url: 'http://test.host/topics',
158
+ params: {
159
+ topic: { subtitle: "foo"},
160
+ controller: 'topics',
161
+ action: 'create'
162
+ }
163
+ )
164
+
165
+ post :create, params: { topic: { subtitle: 'foo' } }
166
+ end
167
+ end
168
+ end
169
+
170
+ context 'spinner attribute' do
171
+ before(:each) do
172
+ InvisibleCaptcha.spinner_enabled = true
173
+ InvisibleCaptcha.secret = 'secret'
174
+ session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
175
+ session[:invisible_captcha_spinner] = '32ab649161f9f6faeeb323746de1a25d'
176
+
177
+ # Wait for valid submission
178
+ sleep InvisibleCaptcha.timestamp_threshold
179
+ end
180
+
181
+ it 'fails with no spam, but mismatch of spinner' do
182
+ post :create, params: { topic: { title: 'foo' }, spinner: 'mismatch' }
183
+
184
+ expect(response.body).to be_blank
185
+ end
186
+
187
+ it 'passes with no spam and spinner match' do
188
+ post :create, params: { topic: { title: 'foo' }, spinner: '32ab649161f9f6faeeb323746de1a25d' }
189
+
190
+ expect(response.body).to be_present
191
+ end
123
192
  end
124
193
  end
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
@@ -14,13 +14,8 @@ Dummy::Application.configure do
14
14
 
15
15
  # Disable serving static files from the `/public` folder by default since
16
16
  # Apache or NGINX already handles this.
17
- if Rails.version >= "5.0.0"
18
- config.public_file_server.enabled = true
19
- config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
20
- else
21
- config.serve_static_files = true
22
- config.static_cache_control = "public, max-age=3600"
23
- end
17
+ config.public_file_server.enabled = true
18
+ config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
24
19
 
25
20
  # Show full error reports and disable caching.
26
21
  config.consider_all_requests_local = true
@@ -39,4 +34,4 @@ Dummy::Application.configure do
39
34
 
40
35
  # Print deprecation notices to the stderr.
41
36
  config.active_support.deprecation = :stderr
42
- end
37
+ end
@@ -1,6 +1,6 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe InvisibleCaptcha do
3
+ RSpec.describe InvisibleCaptcha do
4
4
  it 'initialize with defaults' do
5
5
  InvisibleCaptcha.init!
6
6
 
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['RAILS_ENV'] = 'test'
4
+
2
5
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
3
6
  require 'rspec/rails'
4
7
  require 'invisible_captcha'
5
8
 
6
9
  RSpec.configure do |config|
7
- config.order = 'random'
10
+ config.include ActionDispatch::ContentSecurityPolicy::Request, type: :helper
11
+ config.disable_monkey_patching!
12
+ config.order = :random
8
13
  config.expect_with :rspec
9
14
  config.mock_with :rspec do |mocks|
10
15
  mocks.verify_partial_doubles = true
@@ -1,9 +1,9 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe InvisibleCaptcha::ViewHelpers, type: :helper do
3
+ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
4
4
  before(:each) do
5
- allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
6
5
  allow(InvisibleCaptcha).to receive(:css_strategy).and_return("display:none;")
6
+ allow_any_instance_of(ActionDispatch::ContentSecurityPolicy::Request).to receive(:content_security_policy_nonce).and_return('123')
7
7
 
8
8
  # to test content_for and provide
9
9
  @view_flow = ActionView::OutputFlow.new
@@ -28,10 +28,14 @@ describe InvisibleCaptcha::ViewHelpers, type: :helper do
28
28
  expect(invisible_captcha(:subtitle, :topic, { class: 'foo_class' })).to match(/class="foo_class"/)
29
29
  end
30
30
 
31
+ it 'with CSP nonce' do
32
+ expect(invisible_captcha(:subtitle, :topic, { nonce: true })).to match(/nonce="123"/)
33
+ end
34
+
31
35
  it 'generated html + styles' do
32
36
  InvisibleCaptcha.honeypots = [:foo_id]
33
37
  output = invisible_captcha.gsub("\"", "'")
34
- regexp = %r{<div class='foo_id_\w*'><style.*>.foo_id_\w* {display:none;}</style><label.*>#{InvisibleCaptcha.sentence_for_humans}.*<input.*name='foo_id'.*tabindex='-1'.*</div>}
38
+ regexp = %r{<div class='foo_id_\w*'><style.*>.foo_id_\w* {display:none;}</style><label.*>#{InvisibleCaptcha.sentence_for_humans}</label><input (?=.*name='foo_id'.*)(?=.*autocomplete='off'.*)(?=.*tabindex='-1'.*).*/></div>}
35
39
 
36
40
  expect(output).to match(regexp)
37
41
  end
@@ -54,6 +58,18 @@ describe InvisibleCaptcha::ViewHelpers, type: :helper do
54
58
  end
55
59
  end
56
60
 
61
+ context "should have spinner field" do
62
+ it 'that exists by default, spinner_enabled is true' do
63
+ InvisibleCaptcha.spinner_enabled = true
64
+ expect(invisible_captcha).to match(/spinner/)
65
+ end
66
+
67
+ it 'that does not exist if spinner_enabled is false' do
68
+ InvisibleCaptcha.spinner_enabled = false
69
+ expect(invisible_captcha).not_to match(/spinner/)
70
+ end
71
+ end
72
+
57
73
  it 'should set spam timestamp' do
58
74
  invisible_captcha
59
75
  expect(session[:invisible_captcha_timestamp]).to eq(Time.zone.now.iso8601)
@@ -62,14 +78,14 @@ describe InvisibleCaptcha::ViewHelpers, type: :helper do
62
78
  context 'injectable_styles option' do
63
79
  it 'by default, render styles along with the honeypot' do
64
80
  expect(invisible_captcha).to match(/display:none/)
65
- expect(helper.content_for(:invisible_captcha_styles)).to be_blank
81
+ expect(@view_flow.content[:invisible_captcha_styles]).to be_blank
66
82
  end
67
83
 
68
84
  it 'if injectable_styles is set, do not append styles inline' do
69
85
  InvisibleCaptcha.injectable_styles = true
70
86
 
71
87
  expect(invisible_captcha).not_to match(/display:none;/)
72
- expect(helper.content_for(:invisible_captcha_styles)).to match(/display:none;/)
88
+ expect(@view_flow.content[:invisible_captcha_styles]).to match(/display:none;/)
73
89
  end
74
90
  end
75
91
  end