init
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/example/notify-gateway/internal/model"
|
||||
)
|
||||
|
||||
func (s *Server) discord(w http.ResponseWriter, r *http.Request) {
|
||||
c := s.store.Get().Ingress.Discord
|
||||
if !c.Enabled {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, (1<<20)+1))
|
||||
if err != nil || len(body) > 1<<20 {
|
||||
http.Error(w, "invalid request", 400)
|
||||
return
|
||||
}
|
||||
stamp := r.Header.Get("X-Signature-Timestamp")
|
||||
ts, err := strconv.ParseInt(stamp, 10, 64)
|
||||
key, e1 := hex.DecodeString(c.PublicKey)
|
||||
sig, e2 := hex.DecodeString(r.Header.Get("X-Signature-Ed25519"))
|
||||
if err != nil || e1 != nil || e2 != nil || len(key) != ed25519.PublicKeySize || ts < time.Now().Add(-5*time.Minute).Unix() || ts > time.Now().Add(5*time.Minute).Unix() || !ed25519.Verify(key, append([]byte(stamp), body...), sig) {
|
||||
http.Error(w, "invalid signature", 401)
|
||||
return
|
||||
}
|
||||
var in struct {
|
||||
ID string `json:"id"`
|
||||
ApplicationID string `json:"application_id"`
|
||||
Type int `json:"type"`
|
||||
GuildID string `json:"guild_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
Data struct {
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
Options []struct {
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
} `json:"options"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if json.Unmarshal(body, &in) != nil || in.ApplicationID != c.ApplicationID {
|
||||
http.Error(w, "invalid interaction", 400)
|
||||
return
|
||||
}
|
||||
if in.Type == 1 {
|
||||
writeJSON(w, 200, map[string]int{"type": 1})
|
||||
return
|
||||
}
|
||||
if in.Type != 2 || in.Data.Type != 1 || in.Data.Name != c.Command || in.ID == "" || !slices.Contains(c.GuildIDs, in.GuildID) || !slices.Contains(c.ChannelIDs, in.ChannelID) {
|
||||
http.Error(w, "interaction not allowed", 403)
|
||||
return
|
||||
}
|
||||
msg := model.InboundMessage{Source: "discord", Channel: in.ChannelID, ReceivedAt: time.Now().UTC()}
|
||||
for _, o := range in.Data.Options {
|
||||
switch o.Name {
|
||||
case "title":
|
||||
err = json.Unmarshal(o.Value, &msg.Title)
|
||||
case "message":
|
||||
err = json.Unmarshal(o.Value, &msg.Message)
|
||||
case "priority":
|
||||
err = json.Unmarshal(o.Value, &msg.Priority)
|
||||
default:
|
||||
http.Error(w, "unsupported option", 400)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "invalid option", 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
if msg.Message == "" {
|
||||
http.Error(w, "message required", 400)
|
||||
return
|
||||
}
|
||||
if s.queue == nil {
|
||||
http.Error(w, "outbox unavailable", 503)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
receipt, err := s.queue.Accept(ctx, msg, "discord:"+in.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "interaction could not be queued; check routing", 503)
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"type": 4, "data": map[string]any{"content": "Meldung angenommen. Referenz: " + receipt.ID, "flags": 64, "allowed_mentions": map[string]any{"parse": []string{}}}})
|
||||
}
|
||||
Reference in New Issue
Block a user