Module: Hivehook::Webhook

Defined in:
lib/hivehook/webhook.rb

Constant Summary collapse

HEADER_SIGNATURE =
"X-Hivehook-Signature"
HEADER_TIMESTAMP =
"X-Hivehook-Timestamp"
HEADER_MESSAGE_ID =
"X-Hivehook-Message-ID"

Class Method Summary collapse

Class Method Details

.extract_v1(signature) ⇒ Object

Parse a multi-scheme signature header value and return the full “v1=…” element, or nil if absent. Supports comma-separated lists like “v1=abc,v2=xyz” or “t=123,v1=abc”.



51
52
53
54
# File 'lib/hivehook/webhook.rb', line 51

def self.extract_v1(signature)
  return nil if signature.nil?
  signature.split(",").map(&:strip).find { |part| part.start_with?("v1=") }
end

.sign(payload, secret, timestamp) ⇒ Object



11
12
13
14
15
# File 'lib/hivehook/webhook.rb', line 11

def self.sign(payload, secret, timestamp)
  message = "#{timestamp}.#{payload}"
  digest = OpenSSL::HMAC.hexdigest("SHA256", secret, message)
  "v1=#{digest}"
end

.verify(payload, secret, signature, timestamp, tolerance_seconds = nil) ⇒ Object

Verify a webhook signature.

tolerance_seconds semantics:

nil      -> skip timestamp check
0        -> strict, any drift fails
positive -> allow past drift up to N seconds, reject future timestamps beyond N seconds


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/hivehook/webhook.rb', line 23

def self.verify(payload, secret, signature, timestamp, tolerance_seconds = nil)
  unless tolerance_seconds.nil?
    delta = Time.now.to_i - timestamp
    # delta > 0  -> timestamp is in the past
    # delta < 0  -> timestamp is in the future
    if delta > tolerance_seconds || -delta > tolerance_seconds
      return false
    end
  end

  v1 = extract_v1(signature)
  return false unless v1

  expected = sign(payload, secret, timestamp)
  secure_compare(expected, v1)
end

.verify_with_rotation(payload, primary, secondary, signature, timestamp, tolerance_seconds = nil) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/hivehook/webhook.rb', line 40

def self.verify_with_rotation(payload, primary, secondary, signature, timestamp, tolerance_seconds = nil)
  # Compute both verifications without short-circuiting to keep
  # timing characteristics uniform regardless of which secret matched.
  primary_ok = verify(payload, primary, signature, timestamp, tolerance_seconds)
  secondary_ok = secondary ? verify(payload, secondary, signature, timestamp, tolerance_seconds) : false
  primary_ok | secondary_ok
end