Module: Hook0
- Defined in:
- lib/hook0.rb,
lib/hook0/client.rb,
lib/hook0/errors.rb,
lib/hook0/runtime.rb,
lib/hook0/version.rb,
lib/hook0/signature.rb,
lib/hook0/transport.rb,
lib/hook0/generated/api.rb,
lib/hook0/generated/errors.rb,
lib/hook0/generated/models.rb
Overview
Verifying that a webhook came from Hook0, and that nothing in it changed on the way.
Defined Under Namespace
Modules: Generated, Runtime Classes: Client, ClientError, Event, EventType, Options, RetryPolicy, Signature, Transport, TransportError
Constant Summary collapse
- VERSION =
What this gem is released as, which the gemspec reads rather than repeats.
"2.0.2"
Class Method Summary collapse
-
.generate_event_id ⇒ String
A UUIDv7, the shape of identifier Hook0 mints when it is the one choosing.
-
.verify_webhook_signature(signature, payload, headers, subscription_secret, tolerance) ⇒ void
Verifies a webhook against the current moment.
-
.verify_webhook_signature_with_current_time(signature, payload, headers, subscription_secret, tolerance, current_time) ⇒ void
Verifies a webhook against a moment the caller names.
Class Method Details
.generate_event_id ⇒ String
A UUIDv7, the shape of identifier Hook0 mints when it is the one choosing.
Its leading 48 bits are the current time in milliseconds, so identifiers generated in sequence
are ordered, which is what keeps the index they end up in from being written all over. Written
here rather than taken from the standard library, which has had SecureRandom.uuid_v7 only
since Ruby 3.3 and this gem supports older.
346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/hook0/client.rb', line 346 def self.generate_event_id drawn = SecureRandom.random_bytes(16).unpack("C*") milliseconds = (Time.now.to_f * 1000).floor 6.times { |index| drawn[index] = (milliseconds >> (8 * (5 - index))) & 0xFF } drawn[6] = (drawn[6] & 0x0F) | 0x70 drawn[8] = (drawn[8] & 0x3F) | 0x80 written = drawn.pack("C*").unpack1("H*") [written[0, 8], written[8, 4], written[12, 4], written[16, 4], written[20, 12]].join("-") end |
.verify_webhook_signature(signature, payload, headers, subscription_secret, tolerance) ⇒ void
This method returns an undefined value.
Verifies a webhook against the current moment.
See verify_webhook_signature_with_current_time for what each argument is.
269 270 271 272 273 |
# File 'lib/hook0/signature.rb', line 269 def self.verify_webhook_signature(signature, payload, headers, subscription_secret, tolerance) verify_webhook_signature_with_current_time( signature, payload, headers, subscription_secret, tolerance, Time.now ) end |
.verify_webhook_signature_with_current_time(signature, payload, headers, subscription_secret, tolerance, current_time) ⇒ void
This method returns an undefined value.
Verifies a webhook against a moment the caller names.
The clock window is bilateral. A moment too far in the future is refused exactly like one too far in the past, so the window a given delivery is accepted in stays the width the caller asked for, whichever way a clock drifted.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/hook0/signature.rb', line 238 def self.verify_webhook_signature_with_current_time( signature, payload, headers, subscription_secret, tolerance, current_time ) parsed = Signature.parse(signature) delivered = delivered_headers(headers) covered_values = parsed.covered_headers.map do |name| raise ClientError, "the `#{name}` header the signature covers was not delivered" unless delivered.key?(name) delivered[name] end unless parsed.matches?(payload.to_s, covered_values, subscription_secret) raise ClientError, "the signature does not match what the subscription secret produces" end drift = current_time.to_f - parsed. if drift.abs > tolerance raise ClientError, "the signature was made #{format("%.0f", drift)} seconds from now, outside the #{tolerance} accepted" end nil end |