65 lines
1.3 KiB
Go
65 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
|
|
}
|