all repos — memnarch @ 734eab3cfdf4291d064c479653fb07cc194305bf

featherweight orchestrator

webhook/webhook.go (raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
}