81 lines
1.9 KiB
Go
81 lines
1.9 KiB
Go
package forgejo
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
// WebhookEvent is the parsed payload from a Forgejo webhook.
|
|
type WebhookEvent struct {
|
|
Action string `json:"action"`
|
|
Issue *WebhookIssue `json:"issue,omitempty"`
|
|
Repo *WebhookRepo `json:"repository,omitempty"`
|
|
Sender *WebhookUser `json:"sender,omitempty"`
|
|
Ref string `json:"ref,omitempty"`
|
|
After string `json:"after,omitempty"`
|
|
Before string `json:"before,omitempty"`
|
|
}
|
|
|
|
type WebhookIssue struct {
|
|
ID int64 `json:"id"`
|
|
Number int `json:"number"`
|
|
Title string `json:"title"`
|
|
State string `json:"state"`
|
|
HTMLURL string `json:"html_url"`
|
|
}
|
|
|
|
type WebhookRepo struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
FullName string `json:"full_name"`
|
|
}
|
|
|
|
type WebhookUser struct {
|
|
Login string `json:"login"`
|
|
}
|
|
|
|
// VerifyAndParse reads the webhook body, verifies the HMAC signature, and parses the event.
|
|
func VerifyAndParse(r *http.Request, secret string) (*WebhookEvent, string, error) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("reading body: %w", err)
|
|
}
|
|
|
|
if secret != "" {
|
|
sig := r.Header.Get("X-Forgejo-Signature")
|
|
if sig == "" {
|
|
sig = r.Header.Get("X-Gitea-Signature")
|
|
}
|
|
if !verifyHMAC(body, sig, secret) {
|
|
return nil, "", fmt.Errorf("HMAC verification failed")
|
|
}
|
|
}
|
|
|
|
eventType := r.Header.Get("X-Forgejo-Event")
|
|
if eventType == "" {
|
|
eventType = r.Header.Get("X-Gitea-Event")
|
|
}
|
|
|
|
var event WebhookEvent
|
|
if err := json.Unmarshal(body, &event); err != nil {
|
|
return nil, "", fmt.Errorf("parsing webhook: %w", err)
|
|
}
|
|
|
|
return &event, eventType, nil
|
|
}
|
|
|
|
func verifyHMAC(body []byte, signature, secret string) bool {
|
|
if signature == "" {
|
|
return false
|
|
}
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write(body)
|
|
expected := hex.EncodeToString(mac.Sum(nil))
|
|
return hmac.Equal([]byte(expected), []byte(signature))
|
|
}
|