class Rack::SlackRequestVerification::Middleware
Public Class Methods
new(app, path_pattern:, signing_key: nil, signing_key_env_var: 'SLACK_SIGNING_KEY', max_staleness_in_secs: 60 * 5, logger: Logger.new($stdout), signing_version: 'v0', timestamp_header: 'X-Slack-Request-Timestamp', signature_header: 'X-Slack-Signature' )
click to toggle source
# File lib/rack/slack_request_verification/middleware.rb, line 16 def initialize(app, # A regular expression used to determine which requests to verify path_pattern:, # You can provide a signing key directly, set a SLACK_SIGNING_KEY env var # or customise the env var to something else signing_key: nil, signing_key_env_var: 'SLACK_SIGNING_KEY', # Mitigates replay attacks by verifying the request was sent recently – # a better strategy is to record the signature header to ensure you only # process each request once max_staleness_in_secs: 60 * 5, # Where to log error messages logger: Logger.new($stdout), signing_version: 'v0', timestamp_header: 'X-Slack-Request-Timestamp', signature_header: 'X-Slack-Signature' ) @app = app @path_pattern = path_pattern @signing_version = signing_version @timestamp_header = timestamp_header @signature_header = signature_header @logger = logger @max_staleness_in_secs = max_staleness_in_secs @signing_key = signing_key || ENV.fetch(signing_key_env_var) do fail Error, "#{signing_key_env_var} env var not set, please configure a signing key" end end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack/slack_request_verification/middleware.rb, line 50 def call(env) if !path_pattern.match?(env['PATH_INFO']) @app.call(env) else headers = { signature_header => env["HTTP_" + signature_header.gsub('-', '_').upcase], timestamp_header => env["HTTP_" + timestamp_header.gsub('-', '_').upcase]&.to_i } missing_headers = headers.select { |_, value| value.nil? }.keys if !missing_headers.empty? logger.error "Slack verification failed: missing #{missing_headers.join(', ')}" return [401, {}, "Not authorized"] end timestamp = headers[timestamp_header] signature = headers[signature_header] minimum_timestamp = Time.now.to_i - max_staleness_in_secs if timestamp < minimum_timestamp logger.error "Slack verification failed: #{timestamp_header} is #{timestamp}, only #{minimum_timestamp} or later is allowed" return [401, {}, "Not authorized"] end body = env['rack.input'] signature_base_string = [ signing_version, timestamp, body.read ].join(':') body.rewind digest = OpenSSL::HMAC.hexdigest("SHA256", signing_key, signature_base_string) computed_signature = [signing_version, digest].join('=') if computed_signature != signature logger.error "Slack verification failed: #{signature_header} does not match the signature" return [401, {}, "Not authorized"] end @app.call(env) end end