Skip to content

Commit

Permalink
Changes underlying code to seperate some concerns
Browse files Browse the repository at this point in the history
  • Loading branch information
gnikyt committed Dec 6, 2022
1 parent 1544375 commit df8c687
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 35 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 109,11 @@ func main() {
FUNCTIONS

func WebhookVerify(key string, fn http.HandlerFunc) http.HandlerFunc
Public webhook verify function wrapper. Can be used with any framework
tapping into net/http. Simply pass in the secret key for the Shopify app.
Example: `WebhookVerify("abc123", anotherHandler)`.
Public webhook verify wrapper. Can be used with any framework tapping into
net/http. Simply pass in the secret key for the Shopify app. Example:
`WebhookVerify("abc123", anotherHandler)`.

func WebhookVerifyRequest(key string, w http.ResponseWriter, r *http.Request) (ok bool)
func WebhookVerifyRequest(key string, w http.ResponseWriter, r *http.Request) bool
Webhook verify request from HTTP. Returns a usable handler. Pass in the
secret key for the Shopify app and the next handler.`

Expand Down
51 changes: 30 additions & 21 deletions http_shopify_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 9,14 @@ import (
"net/http"
)

// Public webhook verify function wrapper.
// Public webhook verify wrapper.
// Can be used with any framework tapping into net/http.
// Simply pass in the secret key for the Shopify app.
// Example: `WebhookVerify("abc123", anotherHandler)`.
func WebhookVerify(key string, fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Verify and if all is well, run the next handler.
ok := WebhookVerifyRequest(key, w, r)
if ok {
if ok := WebhookVerifyRequest(key, w, r); ok {
fn(w, r)
}
}
Expand All @@ -26,39 25,49 @@ func WebhookVerify(key string, fn http.HandlerFunc) http.HandlerFunc {
// Webhook verify request from HTTP.
// Returns a usable handler.
// Pass in the secret key for the Shopify app and the next handler.`
func WebhookVerifyRequest(key string, w http.ResponseWriter, r *http.Request) (ok bool) {
func WebhookVerifyRequest(key string, w http.ResponseWriter, r *http.Request) bool {
// HMAC from request headers and the shop.
shmac := r.Header.Get("X-Shopify-Hmac-Sha256")
shop := r.Header.Get("X-Shopify-Shop-Domain")

if shop == "" {
// No shop provided.
http.Error(w, "Missing shop domain", http.StatusBadRequest)
return false
}

if shmac == "" {
// No HMAC provided.
http.Error(w, "Missing signature", http.StatusBadRequest)
return false
}

// Read the body and put it back.
bb, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
r.Body = ioutil.NopCloser(bytes.NewBuffer(bb))

// Create a signature to compare.
lhmac := newSignature(key, bb)

// Verify all is ok.
ok = verifyRequest(key, shop, shmac, bb)
if !ok {
if ok := isValidSignature(lhmac, shmac); !ok {
http.Error(w, "Invalid webhook signature", http.StatusBadRequest)
return
}

return
}

// Do the actual work.
// Take the request body, the secret key,
// Attempt to reproduce the same HMAC from the request.
func verifyRequest(key string, shop string, shmac string, bb []byte) bool {
if shop == "" {
// No shop provided.
return false
}
return true
}

// Create an hmac of the body with the secret key to compare.
// Create an HMAC of the body (bb) with the secret key (key).
// Returns a string.
func newSignature(key string, bb []byte) string {
h := hmac.New(sha256.New, []byte(key))
h.Write(bb)
enc := base64.StdEncoding.EncodeToString(h.Sum(nil))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

return enc == shmac
// Compares the created HMAC signature with the request's HMAC signature.
// Returns bool of comparison result.
func isValidSignature(lhmac string, shmac string) bool {
return lhmac == shmac
}
20 changes: 10 additions & 10 deletions http_shopify_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 13,10 @@ func TestVerifyRequest(t *testing.T) {
// Setup a simple body with a matching HMAC.
body := []byte(`{"key":"value"}`)
hmac := "7iASoA8WSbw19M/h lgrLr2ly/LvgnE9bcLsk9gflvs="
shop := "example.myshopify.com"

if ok := verifyRequest("secret", shop, hmac, body); !ok {
// Create a signature
lhmac := newSignature("secret", body)
if ok := isValidSignature(lhmac, hmac); !ok {
t.Errorf("expected request data to verify")
}
}
Expand All @@ -24,17 25,19 @@ func TestVerifyRequestError(t *testing.T) {
// Setup a simple body with a matching HMAC, but missing shop.
body := []byte(`{"key":"value"}`)
hmac := "ee2012a00f1649bc35f4cfe1fa582b2ebda5cbf2ef82713d6dc2ec93d81f96fb"
shop := ""

if ok := verifyRequest("secret", shop, hmac, body); ok {
// Create a signature
lhmac := newSignature("secret", body)
if ok := isValidSignature(lhmac, hmac); ok {
t.Errorf("expected request data to not verify, but it did")
}

// Now add the shop, but make the HMAC not match.
shop = "example.myshopify.com"
// HMAC which does not match body content.
hmac = "7iASoA8WSbw19M/h "

if ok := verifyRequest("secret", shop, hmac, body); ok {
// Create a signature
lhmac = newSignature("secret", body)
if ok := isValidSignature(lhmac, hmac); ok {
t.Errorf("expected request data to not verify, but it did")
}
}
Expand All @@ -49,7 52,6 @@ func TestNetHttpSuccess(t *testing.T) {

// Setup the server with our data.
rec, ran := setupServer(key, shop, hmac, body)

if c := rec.Code; c != http.StatusOK {
t.Errorf("expected status code %v got %v", http.StatusOK, c)
}
Expand All @@ -69,7 71,6 @@ func TestNetHttpFailure(t *testing.T) {

// Setup the server with our data.
rec, ran := setupServer(key, shop, hmac, body)

if c := rec.Code; c != http.StatusBadRequest {
t.Errorf("expected status code %v got %v", http.StatusBadRequest, c)
}
Expand Down Expand Up @@ -99,6 100,5 @@ func setupServer(key string, shop string, hmac string, body string) (*httptest.R
// Create the handler and serve with our recorder and request.
h := WebhookVerify(key, nh)
h.ServeHTTP(rec, req)

return rec, ran
}

0 comments on commit df8c687

Please sign in to comment.