* 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")
|
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())
|
||||||
}
|
}
|
||||||
|
64
messenger.go
64
messenger.go
@ -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.
|
||||||
@ -73,8 +81,10 @@ 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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user