reattract 0.4.1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d788029cfabbe3aa6a894388ea0dac86ccba0891deb9b5eecd8366d3e931c412
4
- data.tar.gz: b83d5843485a28165e9e1c5d5cb1eb3d97018861ab8dd8463854229d6962060e
3
+ metadata.gz: bf5cfc801f5c995cff77779a49585f2777dec7034be5cb25d42cc972fa42ac9e
4
+ data.tar.gz: e5aa74ecdeeda05a7d2c98b0f6d83497efcb6b37f16f52736ea18db4ce849dcf
5
5
  SHA512:
6
- metadata.gz: f4f36d5e427823a146c68f43b9f5b7c0b356f142ae4b9ce08c293138a0acc6654d1c7cc92805773e075426e845850d958ca971608a6c6c1cf2bfb96b02d01637
7
- data.tar.gz: 5298603faf71b6c7dd81fe9d347dfc06cedcdf56d7c4108e8e3dd17e0eda69b9cc5faced3ebceb0df255c9bbce885d1534f960c8ca19edb64a53521a030ff724
6
+ metadata.gz: aec4a441a83025aad75c7b94d12b2e56dc75b8b552fb65661eb4be7d8b0591524269f37440110626f7e40b64c083e64c25e6f9ca8218bef187a51ea8342ac046
7
+ data.tar.gz: 326847fc5d71033e425802520d9a81e23bfa5dcf154af97b9bddc42bcd86cc710dd8b00225808500e64286c6adee2bbdc204e408a2ca61fa9f706efeb31b3a45
data/.rubocop.yml CHANGED
@@ -10,6 +10,9 @@ AllCops:
10
10
  - 'Guardfile'
11
11
  - 'reattract.gemspec'
12
12
  - 'bin/**/*'
13
+ - 'spec/reattract/webhoook_spec.rb' # rebranded
14
+ - 'lib/reattract/webhook.rb' # rebranded
15
+ - 'lib/reattract/active_support_include.rb' # rebranded
13
16
 
14
17
  Layout/LineLength:
15
18
  Max: 120
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reattract (0.4.1)
4
+ reattract (0.4.3)
5
5
  jwt (~> 2.7.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,18 +1,16 @@
1
- # Reattract
1
+ # Reattract Ruby SDK
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/reattract/api`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Reattract's ruby API
6
4
 
7
5
  ## Installation
8
6
 
9
7
  Install the gem and add to the application's Gemfile by executing:
10
8
 
11
- $ bundle add reattract_api
9
+ $ bundle add reattract
12
10
 
13
11
  If bundler is not being used to manage dependencies, install the gem by executing:
14
12
 
15
- $ gem install reattract_api
13
+ $ gem install reattract
16
14
 
17
15
  ## Usage
18
16
 
@@ -26,7 +24,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
26
24
 
27
25
  ## Contributing
28
26
 
29
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/reattract.
27
+ Bug reports and pull requests are welcome on GitHub at https://github.com/reattract/sdk-platform.
30
28
 
31
29
  ## License
32
30
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Constant time string comparison, for fixed length strings.
4
+ # Code borrowed from ActiveSupport
5
+ # https://github.com/rails/rails/blob/75ac626c4e21129d8296d4206a1960563cc3d4aa/activesupport/lib/active_support/security_utils.rb#L33
6
+ #
7
+ # The values compared should be of fixed length, such as strings
8
+ # that have already been processed by HMAC. Raises in case of length mismatch.
9
+ module Reattract
10
+ if defined?(OpenSSL.fixed_length_secure_compare)
11
+ def fixed_length_secure_compare(a, b)
12
+ OpenSSL.fixed_length_secure_compare(a, b)
13
+ end
14
+ else
15
+ def fixed_length_secure_compare(a, b)
16
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
17
+
18
+ l = a.unpack "C#{a.bytesize}"
19
+
20
+ res = 0
21
+ b.each_byte { |byte| res |= byte ^ l.shift }
22
+ res == 0
23
+ end
24
+ end
25
+ module_function :fixed_length_secure_compare
26
+
27
+ # Secure string comparison for strings of variable length.
28
+ #
29
+ # While a timing attack would not be able to discern the content of
30
+ # a secret compared via secure_compare, it is possible to determine
31
+ # the secret length. This should be considered when using secure_compare
32
+ # to compare weak, short secrets to user input.
33
+ def secure_compare(a, b)
34
+ a.length == b.length && fixed_length_secure_compare(a, b)
35
+ end
36
+ module_function :secure_compare
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reattract
4
+ # Standard error message
5
+ class ReattractError < StandardError
6
+ attr_reader :message
7
+
8
+ def initialize(message = nil)
9
+ @message = message
10
+ super
11
+ end
12
+ end
13
+
14
+ class WebhookVerificationError < ReattractError
15
+ end
16
+
17
+ class WebhookSigningError < ReattractError
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reattract
4
- VERSION = '0.4.1'
4
+ VERSION = '0.4.3'
5
5
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reattract
4
+ # Reattract's webhook verification class
5
+ class Webhook
6
+
7
+ def self.new_using_raw_bytes(secret)
8
+ self.new(secret.pack("C*").force_encoding("UTF-8"))
9
+ end
10
+
11
+ def initialize(secret)
12
+ if secret.start_with?(SECRET_PREFIX)
13
+ secret = secret[SECRET_PREFIX.length..-1]
14
+ end
15
+
16
+ @secret = Base64.decode64(secret)
17
+ end
18
+
19
+ def verify(payload, headers)
20
+ msgId = headers["svix-id"]
21
+ msgSignature = headers["svix-signature"]
22
+ msgTimestamp = headers["svix-timestamp"]
23
+ if !msgSignature || !msgId || !msgTimestamp
24
+ msgId = headers["webhook-id"]
25
+ msgSignature = headers["webhook-signature"]
26
+ msgTimestamp = headers["webhook-timestamp"]
27
+ if !msgSignature || !msgId || !msgTimestamp
28
+ raise WebhookVerificationError, "Missing required headers"
29
+ end
30
+ end
31
+
32
+ verify_timestamp(msgTimestamp)
33
+
34
+ _, signature = sign(msgId, msgTimestamp, payload).split(",", 2)
35
+
36
+ passedSignatures = msgSignature.split(" ")
37
+ passedSignatures.each do |versionedSignature|
38
+ version, expectedSignature = versionedSignature.split(",", 2)
39
+ if version != "v1"
40
+ next
41
+ end
42
+ if ::Reattract::secure_compare(signature, expectedSignature)
43
+ return JSON.parse(payload, symbolize_names: true)
44
+ end
45
+ end
46
+ raise WebhookVerificationError, "No matching signature found"
47
+ end
48
+
49
+ def sign(msgId, timestamp, payload)
50
+ begin
51
+ now = Integer(timestamp)
52
+ rescue
53
+ raise WebhookSigningError, "Invalid timestamp"
54
+ end
55
+ toSign = "#{msgId}.#{timestamp}.#{payload}"
56
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), @secret, toSign)).strip
57
+ return "v1,#{signature}"
58
+ end
59
+
60
+ private
61
+ SECRET_PREFIX = "whsec_"
62
+ TOLERANCE = 5 * 60
63
+
64
+ def verify_timestamp(timestampHeader)
65
+ begin
66
+ now = Integer(Time.now)
67
+ timestamp = Integer(timestampHeader)
68
+ rescue
69
+ raise WebhookVerificationError, "Invalid Signature Headers"
70
+ end
71
+
72
+ if timestamp < (now - TOLERANCE)
73
+ raise WebhookVerificationError, "Message timestamp too old"
74
+ end
75
+ if timestamp > (now + TOLERANCE)
76
+ raise WebhookVerificationError, "Message timestamp too new"
77
+ end
78
+ end
79
+ end
80
+ end
data/lib/reattract.rb CHANGED
@@ -2,11 +2,16 @@
2
2
 
3
3
  require 'forwardable'
4
4
 
5
+ require_relative 'reattract/active_support_include'
6
+ require_relative 'reattract/reattract_error'
5
7
  require_relative 'reattract/version'
6
8
  require_relative 'reattract/configuration'
7
9
  require_relative 'reattract/jwt_generator'
8
10
  require_relative 'reattract/connection'
9
11
  require_relative 'reattract/request'
12
+ require_relative 'reattract/webhook'
13
+
14
+ # resources
10
15
  require_relative 'reattract/resources/app_event'
11
16
  require_relative 'reattract/resources/campaign'
12
17
  require_relative 'reattract/resources/customer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reattract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Burris
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-15 00:00:00.000000000 Z
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -41,9 +41,11 @@ files:
41
41
  - README.md
42
42
  - Rakefile
43
43
  - lib/reattract.rb
44
+ - lib/reattract/active_support_include.rb
44
45
  - lib/reattract/configuration.rb
45
46
  - lib/reattract/connection.rb
46
47
  - lib/reattract/jwt_generator.rb
48
+ - lib/reattract/reattract_error.rb
47
49
  - lib/reattract/request.rb
48
50
  - lib/reattract/resources/app_event.rb
49
51
  - lib/reattract/resources/campaign.rb
@@ -52,6 +54,7 @@ files:
52
54
  - lib/reattract/resources/invite_conversion.rb
53
55
  - lib/reattract/resources/invite_session.rb
54
56
  - lib/reattract/version.rb
57
+ - lib/reattract/webhook.rb
55
58
  - sig/reattract/api.rbs
56
59
  homepage: https://docs.reattract.io
57
60
  licenses: