aboutsummaryrefslogtreecommitdiff
path: root/pkg/twilio/filter.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/twilio/filter.go')
-rw-r--r--pkg/twilio/filter.go74
1 files changed, 74 insertions, 0 deletions
diff --git a/pkg/twilio/filter.go b/pkg/twilio/filter.go
new file mode 100644
index 0000000..7d5f6b5
--- /dev/null
+++ b/pkg/twilio/filter.go
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: © 2021 Grégoire Duchêne <gduchene@awhk.org>
+// SPDX-License-Identifier: ISC
+
+package twilio
+
+import (
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/base64"
+ "errors"
+ "log"
+ "net/http"
+ "sort"
+ "strings"
+)
+
+var (
+ ErrBase64 = errors.New("failed to decode X-Twilio-Signature header")
+ ErrMissingHeader = errors.New("missing X-Twilio-Signature header")
+ ErrSignatureMismatch = errors.New("signature mismatch")
+)
+
+type Filter struct {
+ AuthToken []byte
+ Handler http.Handler
+}
+
+var _ http.Handler = &Filter{}
+
+func (th *Filter) CheckRequestSignature(r *http.Request) error {
+ hdr := r.Header.Get("X-Twilio-Signature")
+ if len(hdr) == 0 {
+ return ErrMissingHeader
+ }
+ reqSig, err := base64.StdEncoding.DecodeString(hdr)
+ if err != nil {
+ return ErrBase64
+ }
+
+ // See https://www.twilio.com/docs/usage/security#validating-requests
+ // for more details.
+
+ parts := []string{}
+ if r.Method == http.MethodPost {
+ if err := r.ParseForm(); err != nil {
+ return err
+ }
+ for k := range r.PostForm {
+ parts = append(parts, k)
+ }
+ sort.Strings(parts)
+ for i, k := range parts {
+ parts[i] += r.PostForm[k][0]
+ }
+ }
+ s := r.URL.String() + strings.Join(parts, "")
+ h := hmac.New(sha1.New, th.AuthToken)
+ h.Write([]byte(s))
+ ourSig := h.Sum(nil)
+
+ if !hmac.Equal(reqSig, ourSig) {
+ return ErrSignatureMismatch
+ }
+ return nil
+}
+
+func (th *Filter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if err := th.CheckRequestSignature(r); err != nil {
+ log.Println("Failed to check Twilio signature:", err)
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ th.Handler.ServeHTTP(w, r)
+}