Module: Broadcast::Webhook

Defined in:
lib/broadcast/webhook.rb

Constant Summary collapse

TIMESTAMP_TOLERANCE =

5 minutes

300

Class Method Summary collapse

Class Method Details

.compute_signature(payload, timestamp, secret) ⇒ Object



26
27
28
29
30
# File 'lib/broadcast/webhook.rb', line 26

def compute_signature(payload, timestamp, secret)
  signed_content = "#{timestamp}.#{payload}"
  hmac = OpenSSL::HMAC.digest('SHA256', secret, signed_content)
  Base64.strict_encode64(hmac)
end

.extract_signature(header) ⇒ Object



36
37
38
39
40
41
42
43
# File 'lib/broadcast/webhook.rb', line 36

def extract_signature(header)
  return nil unless header&.start_with?('v1,')

  sig = header.delete_prefix('v1,')
  return nil if sig.empty?

  sig
end

.secure_compare(a, b) ⇒ Object



45
46
47
48
49
# File 'lib/broadcast/webhook.rb', line 45

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  OpenSSL.fixed_length_secure_compare(a, b)
end

.timestamp_valid?(timestamp, current_time = Time.now.to_i) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/broadcast/webhook.rb', line 32

def timestamp_valid?(timestamp, current_time = Time.now.to_i)
  (current_time - timestamp).abs <= TIMESTAMP_TOLERANCE
end

.verify(payload, signature_header, timestamp_header, secret:, now: nil) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/broadcast/webhook.rb', line 12

def verify(payload, signature_header, timestamp_header, secret:, now: nil)
  return false if payload.nil? || signature_header.nil? || timestamp_header.nil? || secret.nil?

  timestamp = timestamp_header.to_i
  current_time = (now || Time.now).to_i
  return false unless timestamp_valid?(timestamp, current_time)

  expected = compute_signature(payload, timestamp, secret)
  actual = extract_signature(signature_header)
  return false if actual.nil?

  secure_compare(expected, actual)
end