standardwebhooks 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 49abbd5549503d34882432103f14a3b8e730ef8677c1e06314ceac7bb471a7ca
4
+ data.tar.gz: f4b8df90ea0878fa129a1bd4484efb4338b869a3e843bd6cef8d0357ab722c11
5
+ SHA512:
6
+ metadata.gz: 39cb65e84871f70082fa9275298c130455cb7e75c9229f56d2b6bd941c06f6e8d13195ea027435fd744eafbfe07c68a3768890c83e6eacf6a63a6d25301efabe
7
+ data.tar.gz: bdb9a899342976fa57d33024ed11616e4e895ac4f4e0835be0a7209d1c7af35bd7f85e40c671193979e969a042ab76915674ed2b5be6f518846e09d2f1d67d01
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ standardwebhooks (1.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.5.1)
10
+ rake (13.2.1)
11
+ rspec (3.13.0)
12
+ rspec-core (~> 3.13.0)
13
+ rspec-expectations (~> 3.13.0)
14
+ rspec-mocks (~> 3.13.0)
15
+ rspec-core (3.13.0)
16
+ rspec-support (~> 3.13.0)
17
+ rspec-expectations (3.13.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.13.0)
20
+ rspec-mocks (3.13.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.13.0)
23
+ rspec-support (3.13.0)
24
+
25
+ PLATFORMS
26
+ x86_64-linux
27
+
28
+ DEPENDENCIES
29
+ bundler (>= 2.2.10)
30
+ rake (~> 13.0)
31
+ rspec (~> 3.2)
32
+ standardwebhooks!
33
+
34
+ BUNDLED WITH
35
+ 2.4.22
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ Ruby library for Standard Webhooks
2
+
3
+ # Example
4
+
5
+ Verifying a webhook payload:
6
+
7
+ ```ruby
8
+ require "standardwebhooks"
9
+
10
+ wh = StandardWebhooks::Webhook.new(base64_secret)
11
+ wh.verify(webhook_payload, webhook_headers)
12
+ ```
13
+
14
+ # Development
15
+
16
+ ## Building
17
+
18
+ ```sh
19
+ bundler exec rake build
20
+ ```
21
+
22
+ ## Contributing
23
+
24
+ Before opening a PR be sure to format your code!
25
+
26
+ ```sh
27
+ bundle exec rspec spec
28
+ ```
29
+
30
+ ## Running Tests
31
+
32
+ Simply run:
33
+
34
+ ```sh
35
+ bundle exec rspec spec
36
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardWebhooks
4
+ class StandardWebhooksError < StandardError
5
+ attr_reader :message
6
+
7
+ def initialize(message = nil)
8
+ @message = message
9
+ end
10
+ end
11
+
12
+ class WebhookVerificationError < StandardWebhooksError
13
+ end
14
+
15
+ class WebhookSigningError < StandardWebhooksError
16
+ end
17
+ end
@@ -0,0 +1,39 @@
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 StandardWebhooks
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
+
26
+ module_function :fixed_length_secure_compare
27
+
28
+ # Secure string comparison for strings of variable length.
29
+ #
30
+ # While a timing attack would not be able to discern the content of
31
+ # a secret compared via secure_compare, it is possible to determine
32
+ # the secret length. This should be considered when using secure_compare
33
+ # to compare weak, short secrets to user input.
34
+ def secure_compare(a, b)
35
+ a.length == b.length && fixed_length_secure_compare(a, b)
36
+ end
37
+
38
+ module_function :secure_compare
39
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+ require "base64"
6
+ require "uri"
7
+
8
+ module StandardWebhooks
9
+ class Webhook
10
+
11
+ def self.new_using_raw_bytes(secret)
12
+ self.new(secret.pack("C*").force_encoding("UTF-8"))
13
+ end
14
+
15
+ def initialize(secret)
16
+ if secret.start_with?(SECRET_PREFIX)
17
+ secret = secret[SECRET_PREFIX.length..-1]
18
+ end
19
+
20
+ @secret = Base64.decode64(secret)
21
+ end
22
+
23
+ def verify(payload, headers)
24
+ msg_id = headers["webhook-id"]
25
+ msg_signature = headers["webhook-signature"]
26
+ msg_timestamp = headers["webhook-timestamp"]
27
+
28
+ if !msg_signature || !msg_id || !msg_timestamp
29
+ raise WebhookVerificationError, "Missing required headers"
30
+ end
31
+
32
+ verify_timestamp(msg_timestamp)
33
+
34
+ _, signature = sign(msg_id, msg_timestamp, payload).split(",", 2)
35
+
36
+ passed_signatures = msg_signature.split(" ")
37
+
38
+ passed_signatures.each do |versioned_signature|
39
+ version, expected_signature = versioned_signature.split(",", 2)
40
+
41
+ if version != "v1"
42
+ next
43
+ end
44
+
45
+ if ::StandardWebhooks::secure_compare(signature, expected_signature)
46
+ return JSON.parse(payload, symbolize_names: true)
47
+ end
48
+ end
49
+
50
+ raise WebhookVerificationError, "No matching signature found"
51
+ end
52
+
53
+ def sign(msg_id, timestamp, payload)
54
+ begin
55
+ now = Integer(timestamp)
56
+ rescue
57
+ raise WebhookSigningError, "Invalid timestamp"
58
+ end
59
+
60
+ to_sign = "#{msg_id}.#{timestamp}.#{payload}"
61
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), @secret, to_sign)).strip
62
+
63
+ return "v1,#{signature}"
64
+ end
65
+
66
+ private
67
+
68
+ SECRET_PREFIX = "whsec_"
69
+ TOLERANCE = 5 * 60
70
+
71
+ def verify_timestamp(timestamp_header)
72
+ begin
73
+ now = Integer(Time.now)
74
+ timestamp = Integer(timestamp_header)
75
+ rescue
76
+ raise WebhookVerificationError, "Invalid Signature Headers"
77
+ end
78
+
79
+ if timestamp < (now - TOLERANCE)
80
+ raise WebhookVerificationError, "Message timestamp too old"
81
+ end
82
+
83
+ if timestamp > (now + TOLERANCE)
84
+ raise WebhookVerificationError, "Message timestamp too new"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ require "standardwebhooks/errors.rb"
2
+ require "standardwebhooks/util.rb"
3
+ require "standardwebhooks/webhooks.rb"
@@ -0,0 +1,38 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "standardwebhooks"
7
+ spec.version = "1.0.1"
8
+ spec.authors = ["Standard Webhooks"]
9
+ spec.license = "MIT"
10
+
11
+ spec.summary = "Ruby library for creating and verifying webhook signatures."
12
+
13
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
14
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
15
+ if spec.respond_to?(:metadata)
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+ spec.metadata["source_code_uri"] = "https://github.com/standard-webhooks/standard-webhooks"
18
+ else
19
+ raise "RubyGems 2.0 or newer is required to protect against " \
20
+ "public gem pushes."
21
+ end
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ ignored = Regexp.union(
25
+ /\Aspec/,
26
+ /\Apkg/,
27
+ /\Atemplates/,
28
+ /\A.gitignore/,
29
+ /.gem\z/
30
+ )
31
+
32
+ spec.files = Dir['**/*'].reject {|f| !File.file?(f) || ignored.match(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_development_dependency "bundler", ">= 2.2.10"
36
+ spec.add_development_dependency "rake", "~> 13.0"
37
+ spec.add_development_dependency "rspec", "~> 3.2"
38
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: standardwebhooks
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Standard Webhooks
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.2.10
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.2.10
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ description:
56
+ email:
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - README.md
64
+ - Rakefile
65
+ - lib/standardwebhooks.rb
66
+ - lib/standardwebhooks/errors.rb
67
+ - lib/standardwebhooks/util.rb
68
+ - lib/standardwebhooks/webhooks.rb
69
+ - standardwebhooks.gemspec
70
+ homepage:
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ allowed_push_host: https://rubygems.org
75
+ source_code_uri: https://github.com/standard-webhooks/standard-webhooks
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.1.6
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Ruby library for creating and verifying webhook signatures.
95
+ test_files: []