memnarch/webhook/webhook.go

66 lines
1.3 KiB
Go

package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strings"
)
type Hook struct {
Signature string
Payload []byte
}
const signaturePrefix = ""
const signatureLength = len(signaturePrefix) + 64
func signBody(secret, body []byte) []byte {
computed := hmac.New(sha256.New, secret)
computed.Write(body)
return []byte(computed.Sum(nil))
}
func (h *Hook) SignedBy(secret []byte) bool {
if len(h.Signature) != signatureLength || !strings.HasPrefix(h.Signature, signaturePrefix) {
return false
}
actual := make([]byte, 20)
hex.Decode(actual, []byte(h.Signature))
return hmac.Equal(signBody(secret, h.Payload), actual)
}
func (h *Hook) Extract(dst interface{}) error {
return json.Unmarshal(h.Payload, dst)
}
func HookFrom(req *http.Request) (hook *Hook, err error) {
hook = new(Hook)
if !strings.EqualFold(req.Method, "POST") {
return nil, errors.New("Unknown method!")
}
if hook.Signature = req.Header.Get("X-Forgejo-Signature"); len(hook.Signature) == 0 {
return nil, errors.New("No signature!")
}
hook.Payload, err = ioutil.ReadAll(req.Body)
return
}
func Verify(secret []byte, req *http.Request) (hook *Hook, err error) {
hook, err = HookFrom(req)
//Compare HMACs
if err == nil && !hook.SignedBy(secret) {
err = errors.New("Invalid signature")
}
return
}