Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def call_rpc(endpoint, method: :get, payload: nil, symbolize_keys: false)
faraday.ssl.verify = !ForestAdminRpcAgent::Facades::Container.cache(:debug)
end

timestamp = Time.now.utc.iso8601
timestamp = Time.now.utc.iso8601(3)
signature = generate_signature(timestamp)

headers = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(uri, auth_secret, &on_rpc_stop)
def start
return if @closed

timestamp = Time.now.utc.iso8601
timestamp = Time.now.utc.iso8601(3)
signature = generate_signature(timestamp)

headers = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Utils
allow(fake_client).to receive(:on_event)
allow(fake_client).to receive(:on_error)

timestamp = '2025-01-01T12:00:00Z'
timestamp = '2025-01-01T12:00:00.000Z'
signature = OpenSSL::HMAC.hexdigest('SHA256', secret, timestamp)
# fix the timestamp to a specific value
allow(Time).to receive(:now).and_return(Time.parse(timestamp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,45 @@ module Middleware
end
end

context 'when multiple requests with millisecond timestamps (rapid fire)' do
it 'allows multiple requests in the same second with different milliseconds' do
# Simulate 3 rapid requests within the same second but with different milliseconds
3.times do |i|
timestamp_ms = Time.now.utc.iso8601(3)
signature_ms = OpenSSL::HMAC.hexdigest('SHA256', secret, timestamp_ms)

test_env = {
'HTTP_X_SIGNATURE' => signature_ms,
'HTTP_X_TIMESTAMP' => timestamp_ms
}

status, = middleware.call(test_env)
expect(status).to eq(200), "Request #{i + 1} should succeed with millisecond timestamp"

# Simulate a tiny delay to ensure different milliseconds
sleep(0.002)
end
end

it 'blocks replay with same millisecond timestamp' do
# First request with millisecond precision
timestamp_ms = Time.now.utc.iso8601(3)
signature_ms = OpenSSL::HMAC.hexdigest('SHA256', secret, timestamp_ms)

env['HTTP_X_SIGNATURE'] = signature_ms
env['HTTP_X_TIMESTAMP'] = timestamp_ms

# First request - should pass
status, = middleware.call(env)
expect(status).to eq(200)

# Second request with exact same timestamp and signature - should be blocked
status, _headers, body = middleware.call(env)
expect(status).to eq(401)
expect(JSON.parse(body.first)).to eq({ 'error' => 'Unauthorized' })
end
end

context 'with edge cases' do
it 'handles malformed ISO8601 timestamp' do
env['HTTP_X_SIGNATURE'] = signature
Expand Down