Module: Mailkite
- Defined in:
- lib/mailkite.rb
Defined Under Namespace
Constant Summary collapse
- VERSION =
"0.1.0"- DEFAULT_BASE_URL =
"https://api.mailkite.dev"- DEFAULT_TOLERANCE_MS =
Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
5 * 60 * 1000
Class Method Summary collapse
-
.secure_compare(a, b) ⇒ Object
Length-independent constant-time string compare (no openssl >= 2.2 needed).
-
.verify_webhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS) ⇒ Object
Verify an ‘x-mailkite-signature` header on an inbound webhook delivery.
Class Method Details
.secure_compare(a, b) ⇒ Object
Length-independent constant-time string compare (no openssl >= 2.2 needed).
48 49 50 51 52 53 54 |
# File 'lib/mailkite.rb', line 48 def self.secure_compare(a, b) return false unless a.bytesize == b.bytesize diff = 0 a.bytes.each_with_index { |byte, i| diff |= byte ^ b.getbyte(i) } diff.zero? end |
.verify_webhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS) ⇒ Object
Verify an ‘x-mailkite-signature` header on an inbound webhook delivery. Local HMAC-SHA256 check — no network call. Pass the raw, unparsed body.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/mailkite.rb', line 23 def self.verify_webhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS) return false unless signature.is_a?(String) && !signature.empty? parts = {} signature.split(",").each do |seg| i = seg.index("=") next unless i parts[seg[0...i].strip] = seg[(i + 1)..].strip end t = parts["t"] v1 = parts["v1"] return false if t.nil? || v1.nil? || t.empty? || v1.empty? || !t.match?(/\A-?\d+\z/) # The t in the header is milliseconds since the epoch. if tolerance_ms && tolerance_ms > 0 return false if ((Time.now.to_f * 1000) - t.to_i).abs > tolerance_ms end expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{payload}") secure_compare(expected, v1) rescue StandardError false end |