101 lines
2.4 KiB
Go
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)
|
|
}
|
|
}
|