fix twilio authentication
This commit is contained in:
parent
5d6c8b8aa6
commit
808d25e901
11 changed files with 85 additions and 50 deletions
|
@ -36,6 +36,7 @@ type C struct {
|
|||
AccountSid string
|
||||
AuthToken string
|
||||
SkipVerify bool
|
||||
BaseURL string
|
||||
}
|
||||
Auth struct {
|
||||
ValidMinutes int
|
||||
|
|
|
@ -13,9 +13,22 @@ import (
|
|||
const AuthSessionName = "auth_session"
|
||||
|
||||
type RequestAuth struct {
|
||||
Valid bool
|
||||
Roles []string
|
||||
Expire time.Time
|
||||
Present bool
|
||||
Valid bool
|
||||
Roles []string
|
||||
Expire time.Time
|
||||
}
|
||||
|
||||
func (a RequestAuth) HasRole(role Role) bool {
|
||||
if !a.Valid {
|
||||
return false
|
||||
}
|
||||
for _, r := range a.Roles {
|
||||
if r == string(role) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Middleware(store sessions.Store) echo.MiddlewareFunc {
|
||||
|
@ -31,6 +44,7 @@ func Middleware(store sessions.Store) echo.MiddlewareFunc {
|
|||
|
||||
var auth RequestAuth
|
||||
if expireTs, ok := sess.Values["expire"].(string); ok {
|
||||
auth.Present = true
|
||||
if expireTime, err := time.Parse(time.RFC3339, expireTs); err != nil {
|
||||
log.Printf("invalid expireTime: %v", expireTs)
|
||||
} else {
|
||||
|
@ -116,5 +130,9 @@ func Login(c echo.Context) (err error) {
|
|||
|
||||
}
|
||||
func GetRequestAuth(c echo.Context) RequestAuth {
|
||||
return c.Get("auth_" + AuthSessionName).(RequestAuth)
|
||||
if a, ok := c.Get("auth_" + AuthSessionName).(RequestAuth); ok {
|
||||
return a
|
||||
} else {
|
||||
return RequestAuth{Present: false, Valid: false}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ func Invoke(name string, target reflect.Value, args ...reflect.Value) (any, erro
|
|||
}
|
||||
target = reflect.ValueOf(t)
|
||||
}
|
||||
for i, arg := range args {
|
||||
log.Printf("invoke %s arg[%d]=%v", name, i, arg)
|
||||
}
|
||||
ret := target.Call(args)
|
||||
if len(ret) == 0 {
|
||||
return nil, nil
|
||||
|
@ -68,6 +71,9 @@ func Invoke(name string, target reflect.Value, args ...reflect.Value) (any, erro
|
|||
if err, ok := ret[len(ret)-1].Interface().(error); ok && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, r := range ret {
|
||||
log.Printf("invoke %s ret[%d]=%v", name, i, r)
|
||||
}
|
||||
|
||||
switch len(ret) {
|
||||
case 0:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
@ -28,8 +30,10 @@ func ManagedSession(c echo.Context) (p Provider, close func()) {
|
|||
checkedOutSessions[name] = s
|
||||
return s.Values
|
||||
}, func() {
|
||||
for _, s := range checkedOutSessions {
|
||||
s.Save(c.Request(), c.Response())
|
||||
for name, s := range checkedOutSessions {
|
||||
if err := s.Save(c.Request(), c.Response()); err != nil {
|
||||
log.Printf("error saving session %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package twilio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTwilioValidate(t *testing.T) {
|
||||
params := map[string]string{
|
||||
"CallSid": "CA1234567890ABCDE",
|
||||
"Caller": "+12349013030",
|
||||
"Digits": "1234",
|
||||
"From": "+12349013030",
|
||||
"To": "+18005551212",
|
||||
}
|
||||
bodyForm := make(url.Values)
|
||||
for k, v := range params {
|
||||
bodyForm.Set(k, v)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "https://mycompany.com/myapp.php?foo=1&bar=2",
|
||||
bytes.NewBufferString(bodyForm.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
require.NoError(t, req.ParseForm())
|
||||
|
||||
assert.ErrorContains(t, TwilioValidate(req), "no twilio signature")
|
||||
req.Header.Set("X-Twilio-Signature", "garbage")
|
||||
assert.ErrorContains(t, TwilioValidate(req), "twilio signature verification failed")
|
||||
req.Header.Set("X-Twilio-Signature", "0/KCTR6DLpKmkAf8muzZqo1nDgQ=")
|
||||
assert.NoError(t, TwilioValidate(req))
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package twilio
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -9,8 +10,10 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
|
||||
"github.com/eternal-flame-AD/yoake/config"
|
||||
"github.com/eternal-flame-AD/yoake/internal/auth"
|
||||
"github.com/twilio/twilio-go/client"
|
||||
)
|
||||
|
||||
|
@ -22,14 +25,33 @@ func firstUrlValues(val url.Values) map[string]string {
|
|||
return res
|
||||
}
|
||||
|
||||
func VerifyMiddleware(prefix string) echo.MiddlewareFunc {
|
||||
func VerifyMiddleware(prefix string, baseurlS string) echo.MiddlewareFunc {
|
||||
baseURL, err := url.Parse(baseurlS)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid twilio baseurl: %v", baseurlS)
|
||||
}
|
||||
log.Printf("twilio baseurl is %v", baseURL)
|
||||
var basicAuth echo.MiddlewareFunc
|
||||
if userpass := baseURL.User.String(); userpass != "" {
|
||||
basicAuth = middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
|
||||
ui := url.UserPassword(username, password)
|
||||
return subtle.ConstantTimeCompare([]byte(ui.String()), []byte(userpass)) == 1, nil
|
||||
})
|
||||
}
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
verifySignature := func(c echo.Context) error {
|
||||
if reqAuth := auth.GetRequestAuth(c); reqAuth.Valid && reqAuth.HasRole(auth.RoleAdmin) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
cleanPath := path.Clean(c.Request().URL.Path)
|
||||
//log.Printf("cleanPath: %s", cleanPath)
|
||||
if cleanPath == prefix || strings.HasPrefix(cleanPath, prefix+"/") {
|
||||
if err := TwilioValidate(c.Request()); err != nil {
|
||||
fullReq := c.Request().Clone(c.Request().Context())
|
||||
log.Printf("original request URL: %v, scheme=%s, host=%s, user=%s", c.Request().URL, c.Request().URL.Scheme, c.Request().URL.Host, c.Request().URL.User)
|
||||
fullReq.URL = baseURL.ResolveReference(c.Request().URL)
|
||||
fullReq.URL.User = nil
|
||||
if err := TwilioValidate(c, fullReq); err != nil {
|
||||
c.String(http.StatusOK, "We are sorry. Request Validation Failed. This is not your fault.")
|
||||
log.Printf("twilio verify failed: %v", err)
|
||||
return err
|
||||
|
@ -37,10 +59,14 @@ func VerifyMiddleware(prefix string) echo.MiddlewareFunc {
|
|||
}
|
||||
return next(c)
|
||||
}
|
||||
if basicAuth != nil {
|
||||
return basicAuth(verifySignature)
|
||||
}
|
||||
return verifySignature
|
||||
}
|
||||
}
|
||||
|
||||
func TwilioValidate(req *http.Request) error {
|
||||
func TwilioValidate(c echo.Context, req *http.Request) error {
|
||||
conf := config.Config().Twilio
|
||||
signature := req.Header.Get("X-Twilio-Signature")
|
||||
if signature == "" {
|
||||
|
@ -51,7 +77,11 @@ func TwilioValidate(req *http.Request) error {
|
|||
}
|
||||
requestValidator := client.NewRequestValidator(conf.AuthToken)
|
||||
if req.Method == "POST" {
|
||||
if !requestValidator.Validate(req.URL.String(), firstUrlValues(req.PostForm), signature) {
|
||||
form, err := c.FormParams()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !requestValidator.Validate(req.URL.String(), firstUrlValues(form), signature) {
|
||||
return fmt.Errorf("twilio signature verification failed")
|
||||
}
|
||||
} else if req.Method == "GET" {
|
||||
|
|
|
@ -127,8 +127,8 @@ var (
|
|||
LogError: true,
|
||||
LogContentLength: true,
|
||||
LogResponseSize: true,
|
||||
LogHeaders: []string{},
|
||||
LogHeaders: []string{"Content-Type"},
|
||||
LogQueryParams: []string{},
|
||||
LogFormValues: []string{},
|
||||
LogFormValues: []string{"From", "To"},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -66,8 +66,8 @@ func Init(hostname string) {
|
|||
}
|
||||
},
|
||||
middleware.Gzip(),
|
||||
logMiddleware("twilio", twilio.VerifyMiddleware("/twilio")),
|
||||
auth.Middleware(sessionCookie),
|
||||
logMiddleware("twilio", twilio.VerifyMiddleware("/twilio", config.Config().Twilio.BaseURL)),
|
||||
middleware.Rewrite(map[string]string{"*/": "$1/index.html"}),
|
||||
logMiddleware("template", servetpl.ServeTemplateDir(webroot.Root)),
|
||||
logMiddleware("static", middleware.Static(webroot.Root)))
|
||||
|
|
5
webroot/twilio/sms/entrypoint.tpl.xml
Normal file
5
webroot/twilio/sms/entrypoint.tpl.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{ template "/twilio/head.tpl.xml" . }}
|
||||
{{ $session := (get .Global "twilio_session") }}
|
||||
<Response>
|
||||
<Redirect method="POST">/twilio/sms/menu.xml</Redirect>
|
||||
</Response>
|
6
webroot/twilio/sms/menu.tpl.xml
Normal file
6
webroot/twilio/sms/menu.tpl.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{ template "/twilio/head.tpl.xml" . }}
|
||||
{{ $session := (get .Global "twilio_session") }}
|
||||
|
||||
<Response>
|
||||
<Message>Hello! This is yumechi.</Message>
|
||||
</Response>
|
|
@ -2,6 +2,7 @@
|
|||
{{ $session := (get .Global "twilio_session") }}
|
||||
|
||||
{{ $num_visited := (get $session "num_visited") }}
|
||||
{{ if not $num_visited }}{{ $num_visited = 0 }} {{ end }}
|
||||
{{ set $session "num_visited" (math "argv(1) + 1" $num_visited) }}
|
||||
<Response>
|
||||
<Say voice="alice" language="en-US">This is Anne!</Say>
|
||||
|
|
Loading…
Reference in a new issue