Skip to content

Commit b84c0cf

Browse files
committed
Fix Slack auth filter not halting request on invalid signature
`valid_slack_request?` returned a boolean but never called `head` or `render`, so unauthenticated Slack requests were silently allowed through. Added `head :unauthorized and return` to halt the filter chain, consistent with the fix applied in #66.
1 parent 24872fc commit b84c0cf

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

app/controllers/slack/application_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Slack::ApplicationController < ApplicationController
77
private
88

99
def valid_slack_request?
10-
@verified ||= verify_slack_signature
10+
head :unauthorized and return unless verify_slack_signature
1111
end
1212

1313
def verify_slack_signature
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
require "test_helper"
2+
3+
class Slack::PuzzlesControllerTest < ActionDispatch::IntegrationTest
4+
setup do
5+
ENV["SLACK_SIGNING_SECRET"] = "test_signing_secret"
6+
end
7+
8+
test "rejects request with invalid Slack signature" do
9+
post slack_puzzle_path, params: { payload: puzzle_payload },
10+
headers: slack_headers(secret: "wrong_secret")
11+
12+
assert_response :unauthorized
13+
end
14+
15+
test "rejects request with expired Slack timestamp" do
16+
post slack_puzzle_path, params: { payload: puzzle_payload },
17+
headers: slack_headers(timestamp: Time.now.to_i - 400)
18+
19+
assert_response :unauthorized
20+
end
21+
22+
test "allows request with valid Slack signature" do
23+
# Use a payload that fails model validation so no Slack API call is made,
24+
# but the controller still renders 200 (its behavior on both save success/failure).
25+
params = { payload: puzzle_payload(question: "") }
26+
27+
post slack_puzzle_path, params: params,
28+
headers: slack_headers(body: params.to_query)
29+
30+
assert_response :ok
31+
end
32+
33+
private
34+
35+
def puzzle_payload(question: "What is Ruby?")
36+
{
37+
user: { id: "U123" },
38+
view: {
39+
state: {
40+
values: {
41+
question: { question: { value: question } },
42+
answer: { answer: { selected_option: { value: "ruby" } } },
43+
explanation: { explanation: { value: "It is a programming language." } },
44+
link: { link: { value: nil } }
45+
}
46+
}
47+
}
48+
}.to_json
49+
end
50+
51+
def slack_headers(secret: ENV["SLACK_SIGNING_SECRET"], timestamp: Time.now.to_i, body: "")
52+
ts = timestamp.to_s
53+
sig = "v0=" + OpenSSL::HMAC.hexdigest("SHA256", secret, "v0:#{ts}:#{body}")
54+
{ "X-Slack-Request-Timestamp" => ts, "X-Slack-Signature" => sig }
55+
end
56+
end

0 commit comments

Comments
 (0)