fix: forwardemail bounce processing invalid signature issue (#2250)

This commit is contained in:
Shaun Warman 2025-01-09 23:10:21 -06:00 committed by GitHub
parent 0878f3e6e5
commit a5e56c3446
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 709 additions and 621 deletions

View file

@ -210,7 +210,7 @@ func handleBounceWebhook(c echo.Context) error {
sig = c.Request().Header.Get("X-Webhook-Signature")
)
bs, err := app.bounce.Forwardemail.ProcessBounce([]byte(sig), rawReq)
bs, err := app.bounce.Forwardemail.ProcessBounce(sig, rawReq)
if err != nil {
app.log.Printf("error processing forwardemail notification: %v", err)
if _, ok := err.(*echo.HTTPError); ok {

View file

@ -42,5 +42,8 @@
"vite": "^5.1.8",
"vue-eslint-parser": "^9.3.2",
"vue-template-compiler": "^2.6.12"
},
"resolutions": {
"jackspeak": "2.1.1"
}
}

1265
frontend/yarn.lock vendored

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,9 @@ package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
@ -17,7 +18,7 @@ type BounceDetails struct {
Message string `json:"message"`
Category string `json:"category"`
Code int `json:"code"`
Status string `json:"status"`
Status interface{} `json:"status"`
Line int `json:"line"`
}
@ -30,7 +31,7 @@ type forwardemailNotif struct {
Message string `json:"message"`
Response string `json:"response"`
ResponseCode int `json:"response_code"`
TruthSource string `json:"truth_source"`
TruthSource bool `json:"truth_source"`
Headers map[string]string `json:"headers"`
Bounce BounceDetails `json:"bounce"`
BouncedAt time.Time `json:"bounced_at"`
@ -45,30 +46,37 @@ func NewForwardemail(key []byte) *Forwardemail {
return &Forwardemail{hmacKey: key}
}
// ProcessBounce processes Forward Email bounce notifications and returns one object.
func (p *Forwardemail) ProcessBounce(sig, b []byte) ([]models.Bounce, error) {
key := []byte(p.hmacKey)
mac := hmac.New(sha256.New, key)
mac.Write(b)
signature := mac.Sum(nil)
if subtle.ConstantTimeCompare(signature, []byte(sig)) != 1 {
return nil, fmt.Errorf("invalid signature")
func (p *Forwardemail) ProcessBounce(sigHex string, body []byte) ([]models.Bounce, error) {
// Decode the hex-encoded signature from the webhook
sig, err := hex.DecodeString(sigHex)
if err != nil {
return nil, fmt.Errorf("invalid signature encoding: %v", err)
}
// Generate HMAC using the request body and secret key
mac := hmac.New(sha256.New, p.hmacKey)
mac.Write(body)
expectedSignature := mac.Sum(nil)
// Compare the generated signature with the provided signature
if !hmac.Equal(expectedSignature, sig) {
return nil, errors.New("invalid signature")
}
// Parse the JSON payload
var n forwardemailNotif
if err := json.Unmarshal(b, &n); err != nil {
if err := json.Unmarshal(body, &n); err != nil {
return nil, fmt.Errorf("error unmarshalling Forwardemail notification: %v", err)
}
// Categorize the bounce type
typ := models.BounceTypeSoft
// TODO: support `typ = models.BounceTypeComplaint` in future
switch n.Bounce.Category {
case "block", "recipient", "virus", "spam":
hardBounceCategories := []string{"block", "recipient", "virus", "spam"}
for _, category := range hardBounceCategories {
if n.Bounce.Category == category {
typ = models.BounceTypeHard
break
}
}
campUUID := ""
@ -81,7 +89,7 @@ func (p *Forwardemail) ProcessBounce(sig, b []byte) ([]models.Bounce, error) {
CampaignUUID: campUUID,
Type: typ,
Source: "forwardemail",
Meta: json.RawMessage(b),
Meta: json.RawMessage(body),
CreatedAt: n.BouncedAt,
}}, nil
}