This repository has been archived on 2021-04-05. You can view files and clone it, but cannot push or open issues or pull requests.
go-slash/handler.go
jolheiser aaaf8149e5
Initial commit
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2021-03-31 23:40:16 -05:00

101 lines
2.4 KiB
Go

package slash
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
// Handler returns an http.Handler capable of responding to slash commands
func Handler(key string, commands []*Command) (http.Handler, error) {
mux := chi.NewMux()
mux.Use(middleware.Timeout(time.Minute))
mux.Use(middleware.Recoverer)
hexKey, err := hex.DecodeString(key)
if err != nil {
return nil, err
}
mux.Use(verifyMiddleware(hexKey))
mux.Post("/", handler(commands))
return mux, nil
}
func handler(commands []*Command) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var interaction *Interaction
if err := json.NewDecoder(r.Body).Decode(&interaction); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Pre-ACK
if interaction.Type == 0 {
http.Error(w, "invalid body", http.StatusBadRequest)
return
}
// ACK
if interaction.Type == 1 {
if err := json.NewEncoder(w).Encode(map[string]int{"type": 1}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// Interaction
for _, cmd := range commands {
if cmd.ID == interaction.Data.ID {
resp, err := cmd.Handle(interaction)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
break
}
}
}
}
func verifyMiddleware(key []byte) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Signature-Ed25519")
timestamp := r.Header.Get("X-Signature-Timestamp")
sig, err := hex.DecodeString(signature)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
body := new(bytes.Buffer)
if _, err := io.Copy(body, r.Body); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
buf := bytes.NewBufferString(timestamp + body.String())
if !ed25519.Verify(key, buf.Bytes(), sig) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
r.Body = io.NopCloser(body)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}