Skip to content

Instantly share code, notes, and snippets.

@prognostikos
Last active August 8, 2023 07:22
Show Gist options
  • Save prognostikos/aa6c861770a9adac0c2cb0165cd76a54 to your computer and use it in GitHub Desktop.
Save prognostikos/aa6c861770a9adac0c2cb0165cd76a54 to your computer and use it in GitHub Desktop.
Ruby validator for Vipps Webhook requests
require "active_support/security_utils"
require "digest/sha2"
require "openssl/hmac"
require "uri"
module Vipps
# Validates webhook requests, documented at:
# https://developer.vippsmobilepay.com/docs/APIs/webhooks-api/request-authentication/
#
class ValidatesWebhook
def initialize(
secret:,
url:,
request_body:,
request_checksum:,
date:,
authorization:
)
@secret = secret
@url = URI(url)
@request_body = request_body
@request_checksum = request_checksum
@date = date
@authorization = authorization
end
def valid?
body_checksum_matches? &&
authorization_matches?
end
def body_checksum_matches?
ActiveSupport::SecurityUtils.secure_compare(
request_checksum,
calculated_body_checksum
)
end
def authorization_matches?
ActiveSupport::SecurityUtils.secure_compare(
authorization,
calculated_authorization
)
end
private
def calculated_body_checksum
Digest::SHA256.base64digest(request_body)
end
def calculated_authorization
"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=#{calculated_signature}"
end
def calculated_signature
OpenSSL::HMAC.base64digest(
"SHA256",
secret,
string_to_sign
)
end
def string_to_sign
"POST\n#{url.path}\n#{date};#{url.authority};#{request_checksum}"
end
attr_reader :secret,
:url,
:request_body,
:request_checksum,
:date,
:authorization
end
end
require "spec_helper"
require_relative "../../../vipps_validates_webhook"
RSpec.describe "validating Vipps webhooks" do
let(:validator) {
Vipps::ValidatesWebhook.new(
secret: "A0+AeKBRG2KRGvnNwJpQlb6IJFk48CKXCIcrLoHncVJKDILsQSxS6NWCccwWm6r6FhGKhiHTBsG2wo/xU6FY/A==",
url: "https://webhook.site/e2cee29b-012e-4f1d-8ef4-e95fd74a7a63",
request_body: '{"some-unique-content":"ee6e441b-cc4a-46f8-895d-a5af79bcc233/hello-world"}',
request_checksum: "lNlsp1XA03N34HrQsVzPgJKtC+r7l/RBF4V3JQUWMj4=",
date: "Thu, 30 Mar 2023 08:38:32 GMT",
authorization: "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=agAiSyogQbDHpeucoNwYz+yAr5nJ+v+zasdkSbqzv+U="
)
}
it "can calculate and compare a checksum for the body" do
expect(validator.body_checksum_matches?).to be(true)
end
it "can verify the authorization for the request" do
expect(validator.authorization_matches?).to be(true)
end
it "can verify the request" do
expect(validator.valid?).to be(true)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment