Skip to content

Instantly share code, notes, and snippets.

@jamescmartinez
Last active June 12, 2024 20:11
Show Gist options
  • Save jamescmartinez/5509d5c04f3dc206223374ef99de659d to your computer and use it in GitHub Desktop.
Save jamescmartinez/5509d5c04f3dc206223374ef99de659d to your computer and use it in GitHub Desktop.
Validate a Mergent Task in Go
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"io"
"net/http"
"net/http/httptest"
"testing"
)
// buildSignature builds a Mergent signature for HTTP request validation. You
// probably don't want to use this directly, and instead want to use
// validateSignature.
func buildSignature(body []byte, apiKey string) string {
h := hmac.New(sha1.New, []byte(apiKey))
h.Write(body)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
// validateSignature validates the HTTP request came from Mergent. When you
// receive a request, call `validateSignature` with the http request and your
// Mergent API key.
func validateSignature(r *http.Request, apiKey string) bool {
signature := r.Header.Get("X-Mergent-Signature")
if signature == "" {
return false
}
body, err := io.ReadAll(r.Body)
if err != nil {
return false
}
return buildSignature(body, apiKey) == signature
}
// --- Tests ---
//
// Typically this would be in a separate file, but for the sake of the GitHub
// Gist, I'm keeping it in the same file.
func TestBuildSignature(t *testing.T) {
apiKey := "12345"
t.Run("builds a HMAC-SHA1 signature for the provided body", func(t *testing.T) {
body := "foo"
expectedSignature := "QjZpKdTK/iwkUWiz3seoPbJHA0I="
signature := buildSignature([]byte(body), apiKey)
if signature != expectedSignature {
t.Errorf("Expected %s, got %s", expectedSignature, signature)
}
})
t.Run("is valid with empty params", func(t *testing.T) {
expectedSignature := "KT7FsM8VSFUliCTsf6xdxj0XaRU="
signature := buildSignature([]byte(""), apiKey)
if signature != expectedSignature {
t.Errorf("Expected %s, got %s", expectedSignature, signature)
}
})
}
func TestValidateSignature(t *testing.T) {
apiKey := "12345"
t.Run("returns true when the signature is valid", func(t *testing.T) {
expectedSignature := buildSignature([]byte(""), apiKey)
req := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("")))
req.Header.Set("X-Mergent-Signature", expectedSignature)
if !validateSignature(req, apiKey) {
t.Error("Expected true, got false")
}
})
t.Run("returns false when the signature is invalid", func(t *testing.T) {
req := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("")))
req.Header.Set("X-Mergent-Signature", "invalidsignature")
if validateSignature(req, apiKey) {
t.Error("Expected false, got true")
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment