Class: Philiprehberger::WebhookSignature::Verifier
- Inherits:
-
Object
- Object
- Philiprehberger::WebhookSignature::Verifier
- Defined in:
- lib/philiprehberger/webhook_signature/verifier.rb
Overview
Verifies HMAC webhook signatures with replay prevention.
Constant Summary collapse
- DEFAULT_TOLERANCE =
5 minutes
300- SUPPORTED_ALGORITHMS =
{ sha256: 'SHA256', sha512: 'SHA512' }.freeze
Instance Method Summary collapse
-
#initialize(secret = nil, algorithm: :sha256, secrets: nil) ⇒ Verifier
constructor
A new instance of Verifier.
-
#valid?(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Boolean wrapper around verify!.
-
#valid_header?(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Boolean wrapper around verify_header!.
-
#verify(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Verify a payload against a signature.
-
#verify!(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ true
Verify and raise on failure.
-
#verify_header(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Verify a signature header string.
-
#verify_header!(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ true
Verify a header string or raise on failure.
Constructor Details
#initialize(secret = nil, algorithm: :sha256, secrets: nil) ⇒ Verifier
Returns a new instance of Verifier.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 19 def initialize(secret = nil, algorithm: :sha256, secrets: nil) if !secret.nil? && !secrets.nil? raise ArgumentError, 'Provide either secret: or secrets:, not both' end resolved = if secrets.nil? raise ArgumentError, 'Secret must be a non-empty string' if secret.nil? || secret.empty? [secret] else unless secrets.is_a?(Array) && !secrets.empty? raise ArgumentError, 'secrets must be a non-empty Array of strings' end if secrets.any? { |s| s.nil? || s.empty? } raise ArgumentError, 'Each secret must be a non-empty string' end secrets end unless SUPPORTED_ALGORITHMS.key?(algorithm) raise ArgumentError, "Unsupported algorithm: #{algorithm.inspect}. Supported algorithms: #{SUPPORTED_ALGORITHMS.keys.map(&:inspect).join(', ')}" end @secrets = resolved @algorithm = algorithm @digest_name = SUPPORTED_ALGORITHMS.fetch(algorithm) end |
Instance Method Details
#valid?(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Boolean wrapper around verify!.
110 111 112 113 114 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 110 def valid?(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) verify!(payload, timestamp: , signature: signature, tolerance: tolerance) rescue VerificationError false end |
#valid_header?(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Boolean wrapper around verify_header!.
122 123 124 125 126 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 122 def valid_header?(payload, header:, tolerance: DEFAULT_TOLERANCE) verify_header!(payload, header: header, tolerance: tolerance) rescue VerificationError false end |
#verify(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Verify a payload against a signature.
56 57 58 59 60 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 56 def verify(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) return false if tolerance && stale?(, tolerance) @secrets.any? { |s| signature_matches?(payload, , signature, s) } end |
#verify!(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) ⇒ true
Verify and raise on failure.
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 80 def verify!(payload, timestamp:, signature:, tolerance: DEFAULT_TOLERANCE) if tolerance && stale?(, tolerance) raise VerificationError, "Signature timestamp is too old (tolerance: #{tolerance}s)" end unless @secrets.any? { |s| signature_matches?(payload, , signature, s) } raise VerificationError, 'Signature mismatch' end true end |
#verify_header(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ Boolean
Verify a signature header string.
68 69 70 71 72 73 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 68 def verify_header(payload, header:, tolerance: DEFAULT_TOLERANCE) parsed = parse_header(header) return false unless parsed verify(payload, timestamp: parsed[:timestamp], signature: parsed[:signature], tolerance: tolerance) end |
#verify_header!(payload, header:, tolerance: DEFAULT_TOLERANCE) ⇒ true
Verify a header string or raise on failure.
99 100 101 102 103 104 |
# File 'lib/philiprehberger/webhook_signature/verifier.rb', line 99 def verify_header!(payload, header:, tolerance: DEFAULT_TOLERANCE) parsed = parse_header(header) raise VerificationError, 'Invalid header format' unless parsed verify!(payload, timestamp: parsed[:timestamp], signature: parsed[:signature], tolerance: tolerance) end |