83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
|
|
package handler
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/hmac"
|
||
|
|
"crypto/sha256"
|
||
|
|
"crypto/subtle"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
fwlogger "github.com/yourorg/go-dw-platform/framework/logger"
|
||
|
|
|
||
|
|
"github.com/yourorg/go-dw-platform/services/omnix-broadcast/dto"
|
||
|
|
"github.com/yourorg/go-dw-platform/services/omnix-broadcast/service"
|
||
|
|
)
|
||
|
|
|
||
|
|
const signatureHeader = "X-Sopiga-Signature"
|
||
|
|
|
||
|
|
// WebhookHandler receives delivery-status callbacks pushed by Omnix,
|
||
|
|
// as an alternative to polling. Payload shape confirmed by the Omnix team
|
||
|
|
// (see dto.DeliveryStatusWebhook); the signature scheme is still our own
|
||
|
|
// proposal pending their confirmation.
|
||
|
|
type WebhookHandler struct {
|
||
|
|
service *service.Service
|
||
|
|
logger *fwlogger.Logger
|
||
|
|
secret string
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewWebhookHandler(svc *service.Service, log *fwlogger.Logger, secret string) *WebhookHandler {
|
||
|
|
return &WebhookHandler{service: svc, logger: log, secret: secret}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *WebhookHandler) HandleDeliveryStatus(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.Method != http.MethodPost {
|
||
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
body, err := io.ReadAll(r.Body)
|
||
|
|
if err != nil {
|
||
|
|
h.logger.Error("webhook: read body failed", "error", err)
|
||
|
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer r.Body.Close()
|
||
|
|
|
||
|
|
if h.secret != "" && !h.verifySignature(body, r.Header.Get(signatureHeader)) {
|
||
|
|
h.logger.Error("webhook: signature verification failed")
|
||
|
|
w.WriteHeader(http.StatusUnauthorized)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var payload dto.DeliveryStatusWebhook
|
||
|
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||
|
|
h.logger.Error("webhook: invalid payload", "error", err)
|
||
|
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := h.service.SyncDeliveryFromWebhook(r.Context(), payload); err != nil {
|
||
|
|
h.logger.Error("webhook: sync delivery failed",
|
||
|
|
"recipient_detail_id", payload.RecipientDetailID,
|
||
|
|
"error", err)
|
||
|
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
h.logger.Info("webhook: delivery status applied",
|
||
|
|
"recipient_detail_id", payload.RecipientDetailID,
|
||
|
|
"status", payload.Status,
|
||
|
|
"gateway", payload.Gateway)
|
||
|
|
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *WebhookHandler) verifySignature(body []byte, signature string) bool {
|
||
|
|
mac := hmac.New(sha256.New, []byte(h.secret))
|
||
|
|
mac.Write(body)
|
||
|
|
expected := hex.EncodeToString(mac.Sum(nil))
|
||
|
|
return subtle.ConstantTimeCompare([]byte(expected), []byte(signature)) == 1
|
||
|
|
}
|