package webhook import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "strconv" "strings" "gitea-codex-bot/internal/domain" ) func VerifySignature(body []byte, secret, supplied string) bool { supplied = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(supplied), "sha256=")) if supplied == "" { return false } mac := hmac.New(sha256.New, []byte(secret)) _, _ = mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(supplied))) } func Digest(body []byte) string { sum := sha256.Sum256(body); return hex.EncodeToString(sum[:]) } func ParseEvent(eventName, delivery string, body []byte) (domain.WebhookEvent, error) { if eventName != "issue_comment" && eventName != "pull_request_comment" { return domain.WebhookEvent{}, errors.New("event ignored") } var raw map[string]any if err := json.Unmarshal(body, &raw); err != nil { return domain.WebhookEvent{}, errors.New("invalid JSON payload") } repo := nestedString(raw, "repository", "full_name") commentID := nestedInt64(raw, "comment", "id") sender := nestedString(raw, "sender", "username") commentBody := nestedString(raw, "comment", "body") if repo == "" || commentID <= 0 { return domain.WebhookEvent{}, errors.New("not a pull request comment") } prNumber, headSHA := 0, "" if eventName == "issue_comment" { if _, ok := raw["pull_request"]; !ok || raw["pull_request"] == nil { return domain.WebhookEvent{}, errors.New("not a pull request comment") } issue, ok := raw["issue"].(map[string]any) if !ok || !truthy(issue["pull_request"]) { return domain.WebhookEvent{}, errors.New("not a pull request comment") } prNumber = number(issue["number"]) headSHA = nestedString(raw, "pull_request", "head", "sha") } else { pr, ok := raw["pull_request"].(map[string]any) if !ok || pr == nil { return domain.WebhookEvent{}, errors.New("not a pull request comment") } prNumber = number(pr["number"]) headSHA = nestedString(raw, "pull_request", "head", "sha") } if prNumber <= 0 { return domain.WebhookEvent{}, errors.New("not a pull request comment") } if headSHA == "" { headSHA = "unknown" } return domain.WebhookEvent{EventName: eventName, DeliveryID: delivery, Repo: repo, PRNumber: prNumber, HeadSHA: headSHA, CommentID: commentID, CommentBody: strings.TrimSpace(commentBody), Sender: sender, PayloadSHA256: Digest(body)}, nil } func nestedString(raw map[string]any, path ...string) string { var current any = raw for _, key := range path { obj, ok := current.(map[string]any) if !ok { return "" } current = obj[key] } if value, ok := current.(string); ok { return value } return "" } func nestedInt64(raw map[string]any, path ...string) int64 { var current any = raw for _, key := range path { obj, ok := current.(map[string]any) if !ok { return 0 } current = obj[key] } return int64(number(current)) } func number(value any) int { switch v := value.(type) { case float64: return int(v) case json.Number: n, _ := strconv.Atoi(string(v)) return n case int: return v case int64: return int(v) case string: n, _ := strconv.Atoi(v) return n } return 0 } func truthy(value any) bool { switch v := value.(type) { case bool: return v case map[string]any: return len(v) > 0 case string: return strings.TrimSpace(v) != "" default: return value != nil } } var _ = fmt.Sprintf