Files
protocol/webhook/notifier.go
David Zhao 782f517a6e Use custom mime-type to avoid overeager JSON parsing. (#210)
Middlewares/server frameworks have a tendency of replacing req.body with
parsed JSON object. We do not want this behavior as our Webhook receiver
needs to validate the raw string body against the `Authorization` header
2022-09-19 21:03:55 -07:00

89 lines
1.9 KiB
Go

package webhook
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"time"
"github.com/go-logr/logr"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"github.com/livekit/protocol/auth"
)
const defaultWebhookTimeout = 10 * time.Second
type Notifier interface {
Notify(ctx context.Context, payload interface{}) error
}
type notifier struct {
apiKey string
apiSecret string
urls []string
client *http.Client
logger logr.Logger
}
func NewNotifier(apiKey, apiSecret string, urls []string) Notifier {
return &notifier{
apiKey: apiKey,
apiSecret: apiSecret,
urls: urls,
logger: logr.Discard(),
client: &http.Client{
Timeout: defaultWebhookTimeout,
},
}
}
func (n *notifier) Notify(_ context.Context, payload interface{}) error {
var encoded []byte
var err error
if message, ok := payload.(proto.Message); ok {
// use proto marshaler to ensure lowerCaseCamel
encoded, err = protojson.Marshal(message)
} else {
// encode as JSON
encoded, err = json.Marshal(payload)
}
if err != nil {
return err
}
// sign payload
sum := sha256.Sum256(encoded)
b64 := base64.StdEncoding.EncodeToString(sum[:])
at := auth.NewAccessToken(n.apiKey, n.apiSecret).
SetValidFor(5 * time.Minute).
SetSha256(b64)
token, err := at.ToJWT()
if err != nil {
return err
}
for _, url := range n.urls {
r, err := http.NewRequest("POST", url, bytes.NewReader(encoded))
if err != nil {
// ignore and continue
n.logger.Error(err, "could not create request", "url", url)
continue
}
r.Header.Set(authHeader, token)
// use a custom mime type to ensure signature is checked prior to parsing
r.Header.Set("content-type", "application/webhook+json")
_, err = n.client.Do(r)
if err != nil {
n.logger.Error(err, "could not post to webhook", "url", url)
}
}
return nil
}