1
0
mirror of synced 2024-11-22 04:46:05 +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")
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")
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() {
@ -26,6 +28,7 @@ func main() {
// Create a new messenger client
client := messenger.New(messenger.Options{
Verify: *verify,
AppSecret: *appSecret,
VerifyToken: *verifyToken,
Token: *pageToken,
})
@ -52,7 +55,7 @@ func main() {
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 (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)
@ -24,6 +27,9 @@ type Options struct {
// Verify sets whether or not to be in the "verify" mode. Used for
// verifying webhooks on the Facebook Developer Portal.
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
// when the webhook is created.
VerifyToken string
@ -64,6 +70,8 @@ type Messenger struct {
referralHandlers []ReferralHandler
token string
verifyHandler func(http.ResponseWriter, *http.Request)
verify bool
appSecret string
}
// 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{
mux: mo.Mux,
token: mo.Token,
verify: mo.Verify,
appSecret: mo.AppSecret,
}
if mo.WebhookURL == "" {
@ -240,7 +250,11 @@ func (m *Messenger) handle(w http.ResponseWriter, r *http.Request) {
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 {
fmt.Println("could not decode response:", err)
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)
}
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)
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.
func (m *Messenger) dispatch(r Receive) {
for _, entry := range r.Entry {