B: Dynamically get Auth Secret

This commit is contained in:
Preston Baxter 2023-11-18 18:17:14 -06:00
parent ebd193ab38
commit d78ca20541
3 changed files with 64 additions and 26 deletions

View File

@ -35,6 +35,5 @@ func BuildRouter(r *gin.Engine) {
pcoClientMap = make(map[primitive.ObjectID]*pco.PcoApiClient) pcoClientMap = make(map[primitive.ObjectID]*pco.PcoApiClient)
pco := r.Group("/pco") pco := r.Group("/pco")
pco.Use(ValidatePcoWebhook) pco.POST("/:userid", ValidatePcoWebhook, ConsumePcoWebhook)
pco.POST("/:userid", ConsumePcoWebhook)
} }

View File

@ -1,22 +1,21 @@
package controllers package controllers
import ( import (
"bytes"
"context" "context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"io" "io"
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config" "git.preston-baxter.com/Preston_PLB/capstone/webhook-service/vendors/pco/webhooks"
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/jsonapi"
) )
const PCO_VALIDATE_HEADER = "X-PCO-Webhooks-Authenticity" const PCO_VALIDATE_HEADER = "X-PCO-Webhooks-Authenticity"
func ValidatePcoWebhook(c *gin.Context) { func ValidatePcoWebhook(c *gin.Context) {
conf := config.Config()
//get remote version from header //get remote version from header
remoteDigestStr := c.GetHeader(PCO_VALIDATE_HEADER) remoteDigestStr := c.GetHeader(PCO_VALIDATE_HEADER)
if remoteDigestStr == "" { if remoteDigestStr == "" {
@ -26,18 +25,28 @@ func ValidatePcoWebhook(c *gin.Context) {
} }
pcoSig := make([]byte, len(remoteDigestStr)/2) pcoSig := make([]byte, len(remoteDigestStr)/2)
_, err := hex.Decode(pcoSig, []byte(remoteDigestStr)) _, err := hex.Decode(pcoSig, []byte(remoteDigestStr))
if err != nil {
log.WithError(err).Error("Failed to decode byte digest")
_ = c.AbortWithError(501, err)
return
}
//clone request to harmlessly inspect the body //clone request to harmlessly inspect the body
bodyReader := c.Request.Clone(context.Background()).Body bodyReader := c.Request.Clone(context.Background()).Body
body, err := io.ReadAll(bodyReader) body, err := io.ReadAll(bodyReader)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to read body while validating PCO webhook") log.WithError(err).Error("Failed to read body while validating PCO webhook")
c.AbortWithError(501, err) _ = c.AbortWithError(501, err)
return return
} }
//Get secret //Get secret
key := conf.Vendors[models.PCO_VENDOR_NAME].WebhookSecret key, err := getAuthSecret(c, body)
if err != nil {
log.WithError(err).Error("Failed to find auth secret for event. It may not be setup")
_ = c.AbortWithError(501, err)
return
}
//Get HMAC //Get HMAC
hmacSig := hmac.New(sha256.New, []byte(key)) hmacSig := hmac.New(sha256.New, []byte(key))
@ -48,3 +57,20 @@ func ValidatePcoWebhook(c *gin.Context) {
c.AbortWithStatus(401) c.AbortWithStatus(401)
} }
} }
func getAuthSecret(c *gin.Context, body []byte) (string, error) {
userObjectId := userIdFromContext(c)
event := &webhooks.EventDelivery{}
err := jsonapi.UnmarshalPayload(bytes.NewBuffer(body), event)
if err != nil {
return "", err
}
webhook, err := mongo.FindPcoSubscriptionForUser(*userObjectId, event.Name)
if err != nil {
return "", err
}
return webhook.Details.AuthenticitySecret, nil
}

View File

@ -28,34 +28,47 @@ var (
type actionFunc func(*gin.Context, *webhooks.EventDelivery) error type actionFunc func(*gin.Context, *webhooks.EventDelivery) error
func userIdFromContext(c *gin.Context) (*primitive.ObjectID) {
if id, ok := c.Get("user_bson_id"); !ok {
userId := c.Param("userid")
if userId == "" {
log.Warn("Webhook did not contain user id. Rejecting")
c.AbortWithStatus(404)
return nil
}
userObjectId, err := primitive.ObjectIDFromHex(userId)
if err != nil {
log.WithError(err).Warn("User Id was malformed")
c.AbortWithStatus(400)
return nil
}
c.Set("user_bson_id", userObjectId)
return &userObjectId
} else {
if objId, ok := id.(primitive.ObjectID); ok {
return &objId
} else {
return nil
}
}
}
func ConsumePcoWebhook(c *gin.Context) { func ConsumePcoWebhook(c *gin.Context) {
userId := c.Param("userid") userObjectId := userIdFromContext(c)
if userId == "" {
log.Warn("Webhook did not contain user id. Rejecting")
c.AbortWithStatus(404)
return
}
//get actions for user
userObjectId, err := primitive.ObjectIDFromHex(userId)
if err != nil {
log.WithError(err).Warn("User Id was malformed")
c.AbortWithStatus(400)
return
}
c.Set("user_bson_id", userObjectId)
//read body and handle io in parallel because IO shenanigains //read body and handle io in parallel because IO shenanigains
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
wg.Add(2) wg.Add(2)
//get actions for user
var actionMappings []models.ActionMapping var actionMappings []models.ActionMapping
var webhookBody *webhooks.EventDelivery var webhookBody *webhooks.EventDelivery
errs := make([]error, 2) errs := make([]error, 2)
go func(wg *sync.WaitGroup) { go func(wg *sync.WaitGroup) {
actionMappings, errs[0] = mongo.FindActionMappingsByUser(userObjectId) actionMappings, errs[0] = mongo.FindActionMappingsByUser(*userObjectId)
wg.Done() wg.Done()
}(wg) }(wg)
@ -81,7 +94,7 @@ func ConsumePcoWebhook(c *gin.Context) {
actionKey := fmt.Sprintf("%s:%s", mapping.Action.VendorName, mapping.Action.Type) actionKey := fmt.Sprintf("%s:%s", mapping.Action.VendorName, mapping.Action.Type)
//if function exists run the function //if function exists run the function
if action, ok := actionFuncMap[actionKey]; ok { if action, ok := actionFuncMap[actionKey]; ok {
err = action(c, webhookBody) err := action(c, webhookBody)
//handle error //handle error
if err != nil { if err != nil {
log.WithError(err).Errorf("Failed to execute action: %s. From event source: %s:%s", actionKey, mapping.SourceEvent.VendorName, mapping.SourceEvent.Key) log.WithError(err).Errorf("Failed to execute action: %s. From event source: %s:%s", actionKey, mapping.SourceEvent.VendorName, mapping.SourceEvent.Key)