Skip to content

Commit e7a835f

Browse files
cocomarineCopilot
andcommitted
update errors and test
Co-authored-by: Copilot <copilot@github.com>
1 parent 04866c6 commit e7a835f

2 files changed

Lines changed: 82 additions & 30 deletions

File tree

app/controllers/api/subscriptions_controller.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,15 @@ def valid_turnstile_token?
6767
remoteip: request.remote_ip
6868
}
6969
)
70+
unless response.success?
71+
Rails.logger.warn("[subscriptions#create] turnstile verification skipped: HTTP #{response.status}")
72+
return true # fail open
73+
end
74+
7075
JSON.parse(response.body)['success'] == true
71-
rescue StandardError
76+
rescue Faraday::Error, JSON::ParserError => e
77+
Sentry.capture_exception(e)
78+
Rails.logger.warn("[subscriptions#create] turnstile verification error: #{e.message}")
7279
# Fail open to allow the request through if verification is unavailable
7380
# due to network issues, Cloudflare downtime or malformed responses etc.
7481
true

spec/requests/api/subscriptions_spec.rb

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
let(:submitter) { instance_double(Subscriptions::PardotFormHandlerSubmitter) }
3939

4040
before do
41+
allow(Rails.configuration.x.cloudflare_turnstile).to receive(:enabled).and_return(false)
4142
allow(Subscriptions::PardotFormHandlerSubmitter).to receive(:new).and_return(submitter)
4243
allow(submitter).to receive(:call).and_return(submitter_result_success)
44+
allow(Sentry).to receive(:capture_exception)
4345
end
4446

4547
it 'returns success for a valid payload' do
@@ -113,7 +115,9 @@
113115
end
114116

115117
describe 'Cloudflare Turnstile integration' do
116-
let(:request_url) { 'https://challenges.cloudflare.com/turnstile/v0/siteverify' }
118+
let(:request_url) { Api::SubscriptionsController::API_URL }
119+
let(:turnstile_request_body) { { 'secret' => 'test-secret', 'response' => 'test-token', 'remoteip' => '127.0.0.1' } }
120+
let(:post_params) { payload }
117121

118122
before do
119123
allow(Rails.configuration.x.cloudflare_turnstile).to receive_messages(
@@ -122,43 +126,84 @@
122126
)
123127
end
124128

125-
it 'returns 422 when turnstile token is missing' do
126-
post(path, params: payload.deep_merge(subscription: { turnstile_token: '' }), as: :json)
129+
shared_examples 'turnstile verification failure' do
130+
it 'returns 422 with turnstile_verification_failed error code' do
131+
post(path, params: post_params, as: :json)
127132

128-
expect(response).to have_http_status(:unprocessable_content)
129-
expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed')
133+
expect(response).to have_http_status(:unprocessable_content)
134+
expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed')
135+
end
130136
end
131137

132-
it 'returns 422 when turnstile verification fails' do
133-
stub_request(:post, request_url)
134-
.with(
135-
body: hash_including(
136-
secret: 'test-secret',
137-
response: 'test-token'
138-
)
139-
)
140-
.to_return(status: 200, body: { success: false }.to_json)
138+
shared_examples 'fail-open turnstile response' do
139+
it 'allows the request through' do
140+
post(path, params: payload, as: :json)
141141

142-
post(path, params: payload, as: :json)
142+
expect(response).to have_http_status(:ok)
143+
expect(response.parsed_body['ok']).to be(true)
144+
end
145+
end
146+
147+
context 'when turnstile token is missing' do
148+
let(:post_params) { payload.deep_merge(subscription: { turnstile_token: '' }) }
149+
150+
it_behaves_like 'turnstile verification failure'
151+
end
152+
153+
context 'when turnstile verification fails' do
154+
before do
155+
stub_request(:post, request_url)
156+
.with(body: turnstile_request_body)
157+
.to_return(status: 200, body: { success: false }.to_json)
158+
end
159+
160+
it_behaves_like 'turnstile verification failure'
161+
end
143162

144-
expect(response).to have_http_status(:unprocessable_content)
145-
expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed')
163+
context 'when turnstile verification times out' do
164+
before do
165+
stub_request(:post, request_url)
166+
.with(body: turnstile_request_body)
167+
.to_timeout
168+
end
169+
170+
it 'allows the request through and reports to Sentry' do
171+
post(path, params: payload, as: :json)
172+
173+
expect(response).to have_http_status(:ok)
174+
expect(response.parsed_body['ok']).to be(true)
175+
expect(Sentry).to have_received(:capture_exception).with(be_a(Faraday::Error))
176+
end
146177
end
147178

148-
it 'allows request through if turnstile verification is unavailable' do
149-
stub_request(:post, request_url)
150-
.with(
151-
body: hash_including(
152-
secret: 'test-secret',
153-
response: 'test-token'
154-
)
155-
)
156-
.to_timeout
179+
context 'when Cloudflare returns a server error' do
180+
before do
181+
stub_request(:post, request_url)
182+
.with(body: turnstile_request_body)
183+
.to_return(status: 500, body: 'Internal Server Error')
184+
end
185+
186+
it_behaves_like 'fail-open turnstile response'
187+
end
188+
189+
context 'when Cloudflare returns malformed JSON' do
190+
before do
191+
stub_request(:post, request_url)
192+
.with(body: turnstile_request_body)
193+
.to_return(status: 200, body: 'not-json')
194+
end
195+
196+
it_behaves_like 'fail-open turnstile response'
197+
end
157198

158-
post(path, params: payload, as: :json)
199+
context 'when turnstile token is valid' do
200+
before do
201+
stub_request(:post, request_url)
202+
.with(body: turnstile_request_body)
203+
.to_return(status: 200, body: { success: true }.to_json)
204+
end
159205

160-
expect(response).to have_http_status(:ok)
161-
expect(response.parsed_body['ok']).to be(true)
206+
it_behaves_like 'fail-open turnstile response'
162207
end
163208
end
164209
end

0 commit comments

Comments
 (0)