http_signature 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'http_signature'
4
- require 'faraday'
3
+ require "http_signature"
4
+ require "faraday"
5
5
 
6
6
  class HTTPSignature::Faraday < Faraday::Middleware
7
7
  class << self
@@ -9,10 +9,10 @@ class HTTPSignature::Faraday < Faraday::Middleware
9
9
  end
10
10
 
11
11
  def call(env)
12
- raise 'key and key_id needs to be set' if self.class.key.nil? || self.class.key_id.nil?
12
+ raise "key and key_id needs to be set" if self.class.key.nil? || self.class.key_id.nil?
13
13
 
14
14
  body =
15
- if env[:body] && env[:body].respond_to?(:read)
15
+ if env[:body]&.respond_to?(:read)
16
16
  string = env[:body].read
17
17
  env[:body].rewind
18
18
  string
@@ -21,20 +21,22 @@ class HTTPSignature::Faraday < Faraday::Middleware
21
21
  end
22
22
 
23
23
  # Choose which headers to sign
24
- filtered_headers = %w[Host Date Digest]
24
+ filtered_headers = %w[Host Date Content-Digest]
25
25
  headers_to_sign = env[:request_headers].select { |k, _v| filtered_headers.include?(k.to_s) }
26
26
 
27
- signature = HTTPSignature.create(
27
+ signature_headers = HTTPSignature.create(
28
28
  url: env[:url],
29
29
  method: env[:method],
30
30
  headers: headers_to_sign,
31
31
  key: self.class.key,
32
32
  key_id: self.class.key_id,
33
- algorithm: 'hmac-sha256',
33
+ algorithm: "hmac-sha256",
34
34
  body: body
35
35
  )
36
36
 
37
- env[:request_headers]['Signature'] = signature
37
+ signature_headers.each do |header, value|
38
+ env[:request_headers][header] = value
39
+ end
38
40
 
39
41
  @app.call(env)
40
42
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'http_signature'
3
+ require "http_signature"
4
+ require "rack"
4
5
 
5
6
  # Rack middleware using http-signature gem to validate signature on every incoming request
6
7
  class HTTPSignature::Rack
@@ -14,46 +15,38 @@ class HTTPSignature::Rack
14
15
  end
15
16
 
16
17
  def call(env)
17
- request = Rack::Request.new(env)
18
+ request = ::Rack::Request.new(env)
18
19
 
19
20
  return @app.call(env) if path_excluded?(request.path)
20
21
 
21
- return [401, {}, ['No signature header']] unless request.get_header("HTTP_SIGNATURE")
22
+ request_headers = parse_request_headers(request)
23
+ signature_input_header = request_headers["signature-input"]
24
+ signature_header = request_headers["signature"]
25
+ return [401, {}, ["No signature header"]] unless signature_input_header && signature_header
22
26
 
23
27
  begin
24
- request_body = request.body.read
25
- request_headers = parse_request_headers(request)
26
- parsed_signature = parse_signature(request_headers)
27
- key = HTTPSignature.key(parsed_signature['keyId'])
28
- rescue
29
- return [401, {}, ['Invalid signature :(']]
28
+ request_body =
29
+ if request.body
30
+ body_content = request.body.read
31
+ request.body.rewind if request.body.respond_to?(:rewind)
32
+ body_content
33
+ else
34
+ ""
35
+ end
36
+ valid_signature = HTTPSignature.valid?(
37
+ url: request.url,
38
+ method: request.request_method,
39
+ headers: request_headers,
40
+ body: request_body || "",
41
+ key_resolver: ->(key_id) { HTTPSignature.key(key_id) }
42
+ )
43
+ rescue HTTPSignature::SignatureError
44
+ return [401, {}, ["Invalid signature"]]
30
45
  end
31
46
 
32
- headers_to_sign = request_headers.select { |k, v| parsed_signature['headers'].include?(k) }
47
+ return [401, {}, ["Invalid signature"]] unless valid_signature
33
48
 
34
- params = {
35
- url: request.path,
36
- method: request.request_method,
37
- headers: headers_to_sign,
38
- key: key,
39
- key_id: parsed_signature['keyId'],
40
- algorithm: parsed_signature['algorithm'],
41
- body: request_body ? request_body : '',
42
- query_string_params: Rack::Utils.parse_nested_query(request.query_string)
43
- }
44
-
45
- valid_signature =
46
- if parsed_signature['algorithm'].include?('rsa')
47
- HTTPSignature.valid?(**params)
48
- else
49
- HTTPSignature.create(**params) == request_headers['signature']
50
- end
51
-
52
- if valid_signature
53
- @app.call(env)
54
- else
55
- [401, {}, ['Invalid signature :(']]
56
- end
49
+ @app.call(env)
57
50
  end
58
51
 
59
52
  private
@@ -62,27 +55,17 @@ class HTTPSignature::Rack
62
55
  request_headers = {}
63
56
 
64
57
  request.each_header do |header|
65
- if header[0].include?('HTTP_') && header[0] != 'HTTP_VERSION'
66
- request_headers[header[0].gsub('HTTP_', '').gsub("_", "-").downcase] = header[1]
58
+ if header[0].include?("HTTP_") && header[0] != "HTTP_VERSION"
59
+ request_headers[header[0].gsub("HTTP_", "").tr("_", "-").downcase] = header[1]
67
60
  end
68
61
  end
69
62
 
70
63
  request_headers
71
64
  end
72
65
 
73
- def parse_signature(request_headers)
74
- Rack::Utils.parse_nested_query(
75
- request_headers['signature'].gsub(',', '&')
76
- ).map do |k, v|
77
- [k, v.tr('"', '')]
78
- end.to_h
79
- end
80
-
81
66
  def path_excluded?(path)
82
- matches = self.class.exclude_paths.map do |exclude_path|
83
- path.match(exclude_path).present?
67
+ self.class.exclude_paths.any? do |exclude_path|
68
+ !!path.match(exclude_path)
84
69
  end
85
-
86
- matches.select { |v| v == true }.length.positive?
87
70
  end
88
71
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http_signature"
4
+ require "action_controller"
5
+
6
+ module HTTPSignature
7
+ module Rails
8
+ module Controller
9
+ extend ActiveSupport::Concern
10
+
11
+ private
12
+
13
+ # Use as a Rails before_action to enforce HTTP Message Signatures on an action.
14
+ def verify_http_signature!
15
+ request_headers = normalized_request_headers
16
+ signature_input_header = request_headers["signature-input"]
17
+ signature_header = request_headers["signature"]
18
+
19
+ return render status: :unauthorized, plain: "No signature header" unless signature_input_header && signature_header
20
+
21
+ request_body = read_request_body
22
+
23
+ valid_signature = HTTPSignature.valid?(
24
+ url: request.url,
25
+ method: request.request_method,
26
+ headers: request_headers,
27
+ body: request_body || "",
28
+ key_resolver: ->(key_id) { HTTPSignature.key(key_id) }
29
+ )
30
+
31
+ return if valid_signature
32
+
33
+ render status: :unauthorized, plain: "Invalid signature"
34
+ rescue HTTPSignature::SignatureError, ArgumentError
35
+ render status: :unauthorized, plain: "Invalid signature"
36
+ end
37
+
38
+ def normalized_request_headers
39
+ headers = {}
40
+
41
+ request.each_header do |key, value|
42
+ next unless key.start_with?("HTTP_")
43
+ next if key == "HTTP_VERSION"
44
+
45
+ canonical_key = key.sub("HTTP_", "").tr("_", "-").downcase
46
+ headers[canonical_key] = value
47
+ end
48
+
49
+ %w[CONTENT_TYPE CONTENT_LENGTH].each do |env_key|
50
+ next unless (value = request.get_header(env_key))
51
+
52
+ headers[env_key.downcase.tr("_", "-")] = value
53
+ end
54
+
55
+ headers
56
+ end
57
+
58
+ def read_request_body
59
+ return "" unless request.body
60
+
61
+ body_content = request.body.read
62
+ request.body.rewind if request.body.respond_to?(:rewind)
63
+ body_content
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPSignature
4
- VERSION = '0.1.0'
4
+ VERSION = "1.0.1"
5
5
  end