Class: Flipper::Cloud::MessageVerifier

Inherits:
Object
  • Object
show all
Defined in:
lib/flipper/cloud/message_verifier.rb

Defined Under Namespace

Classes: InvalidSignature

Constant Summary collapse

DEFAULT_VERSION =
"v1"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(secret:, version: DEFAULT_VERSION) ⇒ MessageVerifier

Returns a new instance of MessageVerifier.

Raises:

  • (ArgumentError)


17
18
19
20
21
22
23
# File 'lib/flipper/cloud/message_verifier.rb', line 17

def initialize(secret:, version: DEFAULT_VERSION)
  @secret = secret
  @version = version || DEFAULT_VERSION

  raise ArgumentError, "secret should be a string" unless @secret.is_a?(String)
  raise ArgumentError, "version should be a string" unless @version.is_a?(String)
end

Class Method Details

.header(signature, timestamp, version = DEFAULT_VERSION) ⇒ Object

Raises:

  • (ArgumentError)


11
12
13
14
15
# File 'lib/flipper/cloud/message_verifier.rb', line 11

def self.header(signature, timestamp, version = DEFAULT_VERSION)
  raise ArgumentError, "timestamp should be an instance of Time" unless timestamp.is_a?(Time)
  raise ArgumentError, "signature should be a string" unless signature.is_a?(String)
  "t=#{timestamp.to_i},#{version}=#{signature}"
end

Instance Method Details

#generate(payload, timestamp) ⇒ Object

Raises:

  • (ArgumentError)


25
26
27
28
29
30
# File 'lib/flipper/cloud/message_verifier.rb', line 25

def generate(payload, timestamp)
  raise ArgumentError, "timestamp should be an instance of Time" unless timestamp.is_a?(Time)
  raise ArgumentError, "payload should be a string" unless payload.is_a?(String)

  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), @secret, "#{timestamp.to_i}.#{payload}")
end

#header(signature, timestamp) ⇒ Object



32
33
34
# File 'lib/flipper/cloud/message_verifier.rb', line 32

def header(signature, timestamp)
  self.class.header(signature, timestamp, @version)
end

#verify(payload, header, tolerance: nil) ⇒ Object

Public: Verifies the signature header for a given payload.

Raises a InvalidSignature in the following cases:

  • the header does not match the expected format

  • no signatures found with the expected scheme

  • no signatures matching the expected signature

  • a tolerance is provided and the timestamp is not within the tolerance

Returns true otherwise.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/flipper/cloud/message_verifier.rb', line 46

def verify(payload, header, tolerance: nil)
  begin
    timestamp, signatures = get_timestamp_and_signatures(header)
  rescue StandardError
    raise InvalidSignature, "Unable to extract timestamp and signatures from header"
  end

  if signatures.empty?
    raise InvalidSignature, "No signatures found with expected version #{@version}"
  end

  expected_sig = generate(payload, timestamp)
  unless signatures.any? { |s| secure_compare(expected_sig, s) }
    raise InvalidSignature, "No signatures found matching the expected signature for payload"
  end

  if tolerance && timestamp < Time.now - tolerance
    raise InvalidSignature, "Timestamp outside the tolerance zone (#{Time.at(timestamp)})"
  end

  true
end