1
0
mirror of synced 2024-11-22 21:06:06 +03:00

Adds integrity check (#19) (#30)

* Adds integrity check (#19)

* Replace usage of `flag' with `configure' for `port' arg.

	Usage:
		./main               # Default port 8080
		./main  --port=1234  # Custom port 1234
This commit is contained in:
Ramy Aboul Naga 2017-06-28 04:10:34 +02:00 committed by Harrison Shoebridge
parent 64c15a121b
commit 7b303129b9
3 changed files with 72 additions and 5 deletions

View File

@ -0,0 +1,6 @@
{
"verify-token": "",
"should-verify": false,
"page-token": "",
"app-secret": ""
}

View File

@ -14,6 +14,8 @@ var (
verifyToken = conf.String("verify-token", "mad-skrilla", "The token used to verify facebook") verifyToken = conf.String("verify-token", "mad-skrilla", "The token used to verify facebook")
verify = conf.Bool("should-verify", false, "Whether or not the app should verify itself") verify = conf.Bool("should-verify", false, "Whether or not the app should verify itself")
pageToken = conf.String("page-token", "not skrilla", "The token that is used to verify the page on facebook") pageToken = conf.String("page-token", "not skrilla", "The token that is used to verify the page on facebook")
appSecret = conf.String("app-secret", "", "The app secret from the facebook developer portal")
port = conf.Int("port", 8080, "The port used to serve the messenger bot")
) )
func main() { func main() {
@ -26,6 +28,7 @@ func main() {
// Create a new messenger client // Create a new messenger client
client := messenger.New(messenger.Options{ client := messenger.New(messenger.Options{
Verify: *verify, Verify: *verify,
AppSecret: *appSecret,
VerifyToken: *verifyToken, VerifyToken: *verifyToken,
Token: *pageToken, Token: *pageToken,
}) })
@ -52,7 +55,7 @@ func main() {
fmt.Println("Read at:", m.Watermark().Format(time.UnixDate)) fmt.Println("Read at:", m.Watermark().Format(time.UnixDate))
}) })
fmt.Println("Serving messenger bot on localhost:8080") fmt.Printf("Serving messenger bot on localhost:%d\n", *port)
http.ListenAndServe("localhost:8080", client.Handler()) http.ListenAndServe(fmt.Sprintf("localhost:%d", *port), client.Handler())
} }

View File

@ -2,10 +2,13 @@ package messenger
import ( import (
"bytes" "bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings"
"time" "time"
) )
@ -24,6 +27,9 @@ type Options struct {
// Verify sets whether or not to be in the "verify" mode. Used for // Verify sets whether or not to be in the "verify" mode. Used for
// verifying webhooks on the Facebook Developer Portal. // verifying webhooks on the Facebook Developer Portal.
Verify bool Verify bool
// AppSecret is the app secret from the Facebook Developer Portal. Used when
// in the "verify" mode.
AppSecret string
// VerifyToken is the token to be used when verifying the webhook. Is set // VerifyToken is the token to be used when verifying the webhook. Is set
// when the webhook is created. // when the webhook is created.
VerifyToken string VerifyToken string
@ -64,6 +70,8 @@ type Messenger struct {
referralHandlers []ReferralHandler referralHandlers []ReferralHandler
token string token string
verifyHandler func(http.ResponseWriter, *http.Request) verifyHandler func(http.ResponseWriter, *http.Request)
verify bool
appSecret string
} }
// New creates a new Messenger. You pass in Options in order to affect settings. // New creates a new Messenger. You pass in Options in order to affect settings.
@ -75,6 +83,8 @@ func New(mo Options) *Messenger {
m := &Messenger{ m := &Messenger{
mux: mo.Mux, mux: mo.Mux,
token: mo.Token, token: mo.Token,
verify: mo.Verify,
appSecret: mo.AppSecret,
} }
if mo.WebhookURL == "" { if mo.WebhookURL == "" {
@ -240,7 +250,11 @@ func (m *Messenger) handle(w http.ResponseWriter, r *http.Request) {
var rec Receive var rec Receive
err := json.NewDecoder(r.Body).Decode(&rec) // consume a *copy* of the request body
body, _ := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
err := json.Unmarshal(body, &rec)
if err != nil { if err != nil {
fmt.Println("could not decode response:", err) fmt.Println("could not decode response:", err)
fmt.Fprintln(w, `{status: 'not ok'}`) fmt.Fprintln(w, `{status: 'not ok'}`)
@ -251,11 +265,55 @@ func (m *Messenger) handle(w http.ResponseWriter, r *http.Request) {
fmt.Println("Object is not page, undefined behaviour. Got", rec.Object) fmt.Println("Object is not page, undefined behaviour. Got", rec.Object)
} }
if m.verify {
if err := m.checkIntegrity(r); err != nil {
fmt.Println("could not verify request:", err)
fmt.Fprintln(w, `{status: 'not ok'}`)
return
}
}
m.dispatch(rec) m.dispatch(rec)
fmt.Fprintln(w, `{status: 'ok'}`) fmt.Fprintln(w, `{status: 'ok'}`)
} }
// checkIntegrity checks the integrity of the requests received
func (m *Messenger) checkIntegrity(r *http.Request) error {
if m.appSecret == "" {
return fmt.Errorf("missing app secret")
}
sigHeader := "X-Hub-Signature"
sig := strings.SplitN(r.Header.Get(sigHeader), "=", 2)
if len(sig) == 1 {
if sig[0] == "" {
return fmt.Errorf("missing %s header", sigHeader)
}
return fmt.Errorf("malformed %s header: %v", sigHeader, strings.Join(sig, "="))
}
checkSHA1 := func(body []byte, hash string) error {
mac := hmac.New(sha1.New, []byte(m.appSecret))
if mac.Write(body); fmt.Sprintf("%x", mac.Sum(nil)) != hash {
return fmt.Errorf("invalid signature: %s", hash)
}
return nil
}
body, _ := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
sigEnc := strings.ToLower(sig[0])
sigHash := strings.ToLower(sig[1])
switch sigEnc {
case "sha1":
return checkSHA1(body, sigHash)
default:
return fmt.Errorf("unknown %s header encoding, expected sha1: %s", sigHeader, sig[0])
}
}
// dispatch triggers all of the relevant handlers when a webhook event is received. // dispatch triggers all of the relevant handlers when a webhook event is received.
func (m *Messenger) dispatch(r Receive) { func (m *Messenger) dispatch(r Receive) {
for _, entry := range r.Entry { for _, entry := range r.Entry {