/
validation.go
80 lines (63 loc) · 1.64 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package twilio
import (
"context"
"crypto/hmac"
"net/http"
"regexp"
"github.com/target/goalert/config"
"github.com/target/goalert/util/log"
"github.com/pkg/errors"
)
type contextKey string
const twilioAlreadyValidated = contextKey("already-validated")
func validateRequest(req *http.Request) error {
if req.Method == "POST" {
req.ParseForm()
}
ctx := req.Context()
cfg := config.FromContext(ctx)
sig := req.Header.Get("X-Twilio-Signature")
if sig == "" {
return errors.New("missing X-Twilio-Signature")
}
calcSig := Signature(cfg.Twilio.AuthToken, cfg.CallbackURL(req.URL.String()), req.PostForm)
if !hmac.Equal([]byte(sig), calcSig) {
return errors.New("invalid X-Twilio-Signature")
}
return nil
}
// WrapValidation will wrap an http.Handler to do X-Twilio-Signature checking.
func WrapValidation(h http.Handler, c Config) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
if val, ok := ctx.Value(twilioAlreadyValidated).(bool); ok && val {
// only validate once
h.ServeHTTP(w, req)
return
}
err := validateRequest(req)
if err != nil {
log.Log(ctx, err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
h.ServeHTTP(w, req.WithContext(context.WithValue(ctx, twilioAlreadyValidated, true)))
})
}
var numRx = regexp.MustCompile(`^\+\d{1,15}$`)
var sidRx = regexp.MustCompile(`^(CA|SM)[\da-f]{32}$`)
func validPhone(n string) string {
if !numRx.MatchString(n) {
return ""
}
return n
}
func validSID(n string) string {
if len(n) != 34 {
return ""
}
if !sidRx.MatchString(n) {
return ""
}
return n
}