class HTTPSignature::Rack

Rack middleware using http-signature gem to validate signature on every incoming request

Attributes

exclude_paths[RW]

Public Class Methods

new(app) click to toggle source
# File lib/http_signature/rack.rb, line 11
def initialize(app)
  @app = app
  self.class.exclude_paths ||= []
end

Public Instance Methods

call(env) click to toggle source
# File lib/http_signature/rack.rb, line 16
def call(env)
  request = Rack::Request.new(env)

  return @app.call(env) if path_excluded?(request.path)

  return [401, {}, ['No signature header']] unless request.get_header("HTTP_SIGNATURE")

  begin
    request_body = request.body.read
    request_headers = parse_request_headers(request)
    parsed_signature = parse_signature(request_headers)
    key = HTTPSignature.key(parsed_signature['keyId'])
  rescue
    return [401, {}, ['Invalid signature :(']]
  end

  headers_to_sign = request_headers.select { |k, v| parsed_signature['headers'].include?(k) }

  params = {
    url: request.path,
    method: request.request_method,
    headers: headers_to_sign,
    key: key,
    key_id: parsed_signature['keyId'],
    algorithm: parsed_signature['algorithm'],
    body: request_body ? request_body : '',
    query_string_params: Rack::Utils.parse_nested_query(request.query_string)
  }

  valid_signature =
    if parsed_signature['algorithm'].include?('rsa')
      HTTPSignature.valid?(**params)
    else
      HTTPSignature.create(**params) == request_headers['signature']
    end

  if valid_signature
    @app.call(env)
  else
    [401, {}, ['Invalid signature :(']]
  end
end

Private Instance Methods

parse_request_headers(request) click to toggle source
# File lib/http_signature/rack.rb, line 61
def parse_request_headers(request)
  request_headers = {}

  request.each_header do |header|
    if header[0].include?('HTTP_') && header[0] != 'HTTP_VERSION'
      request_headers[header[0].gsub('HTTP_', '').gsub("_", "-").downcase] = header[1]
    end
  end

  request_headers
end
parse_signature(request_headers) click to toggle source
# File lib/http_signature/rack.rb, line 73
def parse_signature(request_headers)
  Rack::Utils.parse_nested_query(
    request_headers['signature'].gsub(',', '&')
  ).map do |k, v|
    [k, v.tr('"', '')]
  end.to_h
end
path_excluded?(path) click to toggle source
# File lib/http_signature/rack.rb, line 81
def path_excluded?(path)
  matches = self.class.exclude_paths.map do |exclude_path|
    path.match(exclude_path).present?
  end

  matches.select { |v| v == true }.length.positive?
end