* 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:
parent
64c15a121b
commit
7b303129b9
6
cmd/bot/config.json.example
Normal file
6
cmd/bot/config.json.example
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"verify-token": "",
|
||||
"should-verify": false,
|
||||
"page-token": "",
|
||||
"app-secret": ""
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
64
messenger.go
64
messenger.go
@ -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.
|
||||
@ -73,8 +81,10 @@ func New(mo Options) *Messenger {
|
||||
}
|
||||
|
||||
m := &Messenger{
|
||||
mux: mo.Mux,
|
||||
token: mo.Token,
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user