Belgelere geç
Ruby

Bir teslimatı doğrulamak

`OpenEmail.verify_webhook_signature`: sabit zamanlı, yeniden oynatma penceresiyle ve ayrıştırılmış olayı geri döndürerek.

Bir istek işleyicisinde

Bir webhook URL'si herkese açıktır. İnternetteki her şey ona doğru biçimli JSON'u POST ile gönderebilir; bu yüzden imzayı denetlemeden olayın type değerini okuyan bir işleyici, herkese açık bir yazma API'sidir.

app/controllers/open_email_webhooks_controller.rb
class OpenEmailWebhooksController < ActionController::API  def create    event = OpenEmail.verify_webhook_signature(      payload: request.raw_post,      headers: request.headers,      secret: ENV.fetch("OPENEMAIL_WEBHOOK_SECRET"),      tolerance_seconds: 300    )     Rails.logger.info("#{event[:type]} #{event[:data]}")    head :no_content  rescue OpenEmail::WebhookSignatureError    head :bad_request  endend

HAM gövdeyi bir String olarak geçirin. Ayrıştırıp yeniden serileştirmek anahtar sırasını ve boşlukları değiştirir ve imza eşleşmez; Rails'in params gibi bir Hash'in denetlenmek yerine ArgumentError fırlatmasının nedeni budur. headers:, başlık adı herhangi bir büyük/küçük harf biçiminde olan bir Hash, başlığın HTTP_X_OPENEMAIL_SIGNATURE olarak geldiği bir Rack ortamı ya da Rails'in request.headers nesnesini alır. Array olan bir değer ilk öğesinden okunur.

Elle yazılmış bir denetimin genellikle ele almadığı iki şeyi ele alır: MAC'i sabit zamanda karşılaştırır, böylece doğru önek süre ölçülerek elde edilemez; ve her iki yönde de tolerance_seconds: değerinden (aksini belirtmedikçe beş dakika) daha eski bir teslimatı reddeder, böylece yakalanmış bir istek sonsuza dek yeniden oynatılamaz. tolerance_seconds: 0 yeniden oynatma denetimini kapatır. İki hata da sessizdir. Bunlardan birine sahip bir işleyici, yazmayı düşüneceğiniz her testi geçer.

Her başarısızlıkta OpenEmail::WebhookSignatureError fırlatır: eksik bir X-OpenEmail-Signature başlığı, t=<seconds>,v1=<hex> biçiminde olmayan bir başlık, pencerenin dışındaki bir zaman damgası ya da eşleşmeyen bir imza. Başarılı olduğunda gövdeyi id, type, createdAt ve data içeren, Symbol anahtarlı bir Hash'e ayrıştırılmış olarak döndürür; böylece yanlış yapılabilecek ikinci bir JSON.parse olmaz.

Eksik ya da boş bir secret: ise bunun yerine ArgumentError fırlatır, çünkü bu kötü bir teslimat değil, yapılandırmanızdaki bir hatadır. Yalnızca OpenEmail::WebhookSignatureError yakalayın ve 400 ile yanıt verin; böylece yanlış yapılandırılmış bir sunucu 500 ile yanıt verir ve siz sorunu düzelttiğinizde teslimat yeniden denenir.

Gem'in zaten yüklediği standart kütüphanedeki OpenSSL'i kullanır; bu yüzden başka hiçbir şeye ihtiyaç duymaz.

Bir Rack ya da Sinatra uygulamasında

Rails dışında Rack ortamının kendisini headers: olarak geçirin ve ham gövdeyi istekten okuyun.

require "openemail"require "rack" webhook = lambda do |env|  event = OpenEmail.verify_webhook_signature(    payload: Rack::Request.new(env).body.read,    headers: env,    secret: ENV.fetch("OPENEMAIL_WEBHOOK_SECRET")  )   warn "#{event[:type]} #{event[:id]}"  [204, {}, []]rescue OpenEmail::WebhookSignatureError  [400, {"content-type" => "text/plain"}, ["bad signature"]]end run webhook

Rack sürümü eksiksiz bir config.ru'dur. İkisinde de hızlı yanıt verin: 5 saniye içinde yanıt almayan bir teslimat başarısız sayılır ve daha sonra yeniden gönderilir; bu yüzden işi bir işe devredin ve 2xx ile yanıt verin.

OpenEmail::WEBHOOK_SIGNATURE_HEADERS bir teslimatın taşıdığı üç başlığı adlandırır: X-OpenEmail-Signature, olayın türüyle X-OpenEmail-Event ve kimliğiyle, yani gövdedeki aynı id ile X-OpenEmail-Delivery. Bu kimlik olayın her yeniden denemesinde ve yeniden oynatmasında aynı kalır; bu yüzden daha önce işlediğiniz olayları atlarken saklamanız gereken budur.

Gizli anahtar döndürülürken

webhooks.rotate_secret için bir çakışma penceresi yoktur: teslimatlar, çağrı döndüğü andan itibaren yeni gizli anahtarla imzalanır. Önce iki gizli anahtarı da kabul eden bir alıcı dağıtın, döndürün, yeni gizli anahtarı alıcının okuduğu yere kaydedin, ardından eskisini kaldırın.

two_secrets.rb
def verify_delivery(payload, headers)  secrets = [ENV.fetch("OPENEMAIL_WEBHOOK_SECRET"), ENV["OPENEMAIL_WEBHOOK_SECRET_NEXT"]].compact   secrets.each_with_index do |secret, index|    return OpenEmail.verify_webhook_signature(payload:, headers:, secret:)  rescue OpenEmail::WebhookSignatureError    raise if index == secrets.size - 1  endend

webhooks.test imzalı yapay bir olay gönderir; bu yüzden eskisini kaldırmadan önce yeni gizli anahtarın doğrulandığını kanıtlamak için onu döndürmeden sonra çağırın.