add dummy routes and start decoding webhook

This commit is contained in:
Iris Lightshard 2023-08-28 00:09:46 -06:00
parent 773810957f
commit 2a1845f04c
Signed by: nilix
GPG key ID: F54E0D40695271D4
2 changed files with 140 additions and 9 deletions

84
main.go
View file

@ -6,19 +6,23 @@ import (
"fmt"
"html/template"
"os"
"os/exec"
"net/http"
"path/filepath"
"hacklab.nilfm.cc/quartzgun/renderer"
"hacklab.nilfm.cc/quartzgun/router"
. "hacklab.nilfm.cc/quartzgun/util"
"forge.lightcrystal.systems/lightcrystal/memnarch/webhook"
)
func testPayload(next http.Handler) http.Handler {
handler := func(w http.ResponseWriter, req *http.Request) {
data := &map[string]interface{}{}
func decode(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
data := make(map[string]interface{})
err := json.NewDecoder(req.Body).Decode(data)
err := json.NewDecoder(req.Body).Decode(&data)
if err == nil {
AddContextValue(req, "data", data)
@ -27,18 +31,80 @@ func testPayload(next http.Handler) http.Handler {
}
next.ServeHTTP(w, req)
}
return http.HandlerFunc(handler)
})
}
func runJob(secret string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// validate signature
_, err := webhook.Verify([]byte(secret), req)
if err != nil {
w.WriteHeader(422)
return
}
// get repo from data
data := req.Context().Value("data").(map[string]interface{})
if data != nil {
w.WriteHeader(500)
return
}
repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string)
owner := data["owner"].(map[string]interface{})["login"].(string)
repo := data["repository"].(map[string]interface{})["name"].(string)
obj := data["head_commit"].(map[string]interface{})["id"].(string)
// create working dir
workingDir := filepath.Join("working", owner, repo, obj)
if (os.MkdirAll(workingDir, 0750) != nil) {
w.WriteHeader(500)
return
}
// from this point on we can tell the client they succeeded
// so we run the rest in a goroutine...
go func() {
// cd and checkout repo
cmd := exec.Command("git", "clone", repoUrl)
cmd.Dir = workingDir
err := cmd.Run()
if err != nil {
// clone error - log it and quit
return
}
// read memnarch action file
// decode and perform action
}()
AddContextValue(req, "data", "job submitted")
next.ServeHTTP(w, req)
})
}
func seeJobsForRepo(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
})
}
func seeJobsForObject(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
})
}
func run(args []string) error {
secret := args[1]
rtr := &router.Router{
Fallback: *template.Must(template.ParseFiles("templates/error.html")),
}
rtr.Post("/api/test", testPayload(renderer.JSON("data")))
rtr.Post("/echo", decode(renderer.JSON("data")))
rtr.Post(`/do/(?P<job>\S+)`, decode(runJob(secret, renderer.JSON("data"))))
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>\S+)`, seeJobsForRepo(renderer.JSON("data")))
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<object>\S+)`, seeJobsForObject(renderer.JSON("data")))
http.ListenAndServe(":9999", rtr);
return nil

65
webhook/webhook.go Normal file
View file

@ -0,0 +1,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[5:]))
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-Signature-SHA256"); 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
}