Closed ndbroadbent closed 5 years ago
The library doesn't have much for slack events support, but this was discussed in #131. Currently there's no code here that verifies events are coming from slack. Any such code is welcome, please PR!
As @dblock pointed out, it's probably more appropriate to add a request validation helper in the repo that actually receives requests - slack-ruby-bot or slack-ruby-bot-server - but since you're asking here...
If your registered endpoint is something like /events
and you're running a Sinatra server, you could do something like:
# Events API endpoint
post '/events' do
body = ActiveSupport::JSON.decode(request.body.read)&.deep_symbolize_keys!
halt 404 unless valid_token?(body[:token])
...
end
def valid_token?(token)
token == Rails.configuration.x.slack.verification_token
end
You may notice that I'm referencing Rails.configuration
which is possible by init'ing the Rails app via,
require ::File.expand_path('../config/environment', __FILE__)
from your rackup .ru file. You could also just use basic Sinatra configuration for your verification token - I happen to run slack-bot servers in tandem with a Rails app and wanted to share the same config.
Note that the token will be in params
for slash command and action callback endpoints, the events API endpoint is yet another endpoint.
It could actually be useful to have a simple method as part of slack-ruby-client that implements Slack's signature verification procedure. (Slack rolled out request signing a while back; the old token verification system is deprecated.)
Even just a method to take care of the hashing perhaps, so that you could do something like this:
Slack::Events.valid_signature?(signature, timestamp, body, secret)
For reference, my implementation as a controller filter in Rails looks like this
def verify_signature
signing_secret = ENV['SLACK_SIGNING_SECRET']
version_number = 'v0' # always v0 for now
timestamp = request.headers['X-Slack-Request-Timestamp']
raw_body = request.body.read # raw body JSON string
if Time.at(timestamp.to_i) < 5.minutes.ago
# could be a replay attack
render nothing: true, status: :bad_request
return
end
sig_basestring = [version_number, timestamp, raw_body].join(':')
digest = OpenSSL::Digest::SHA256.new
hex_hash = OpenSSL::HMAC.hexdigest(digest, signing_secret, sig_basestring)
computed_signature = [version_number, hex_hash].join('=')
slack_signature = request.headers['X-Slack-Signature']
if computed_signature != slack_signature
render nothing: true, status: :unauthorized
end
end
Edit: If this gets added, I imagine it should have an option for changing the 5-minute cutoff to any duration, or no cutoff at all. There should also be an option to provide a different version string.
Again, I would take a PR along these lines.
Needed this in slack-strava, so took a stab at it in https://github.com/slack-ruby/slack-ruby-client/pull/245.
I've got the Events API working, and Slack is posting events to my server. I'm looking at the Verifying Requests From Slack documentation, and it doesn't mention Ruby under the "SDK support" section. Is that an oversight, or is there still no official way to verify Slack requests if you're using Ruby? I'm happy to follow the step-by-step instructions, but would prefer to use a library. Is there another Ruby gem that implements the same HMAC verification?
Thanks!