107 lines
3.9 KiB
Go
107 lines
3.9 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/example/notify-gateway/internal/auth"
|
|
"github.com/example/notify-gateway/internal/config"
|
|
"github.com/example/notify-gateway/internal/divera"
|
|
"github.com/example/notify-gateway/internal/gateway"
|
|
)
|
|
|
|
func TestOutboundAdminRoundTripPreviewAndIngress(t *testing.T) {
|
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
|
store, err := config.Open(configPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client := divera.New(func() config.DiveraConfig { return store.Get().Divera })
|
|
h := New(store, gateway.New(store, client), client, log.New(io.Discard, "", 0)).Handler()
|
|
c := store.Get()
|
|
cookie := &http.Cookie{Name: "ng_session", Value: auth.SignSession(c.Server.SessionSecret, c.Server.AdminUsername, time.Now().Add(time.Hour))}
|
|
edit := makeEditable(c)
|
|
edit.Outbounds = []config.OutboundConfig{{ID: "discord-ops", Name: "Ops", Provider: "discord", URL: "https://discord.com/api/webhooks/123/secret"}}
|
|
edit.Ingress.NtfyTokens = map[string]string{"ops": "ingress-token"}
|
|
edit.Mappings = []config.Mapping{{ID: "route", Name: "Route", Enabled: true, Source: "ntfy", Target: "discord", OutboundID: "discord-ops", TitleTemplate: "{{.Title}}", TextTemplate: "{{.Message}}"}}
|
|
body, _ := json.Marshal(edit)
|
|
request := func(method, path string, body []byte, admin bool) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest(method, path, bytes.NewReader(body))
|
|
r.Header.Set("Content-Type", "application/json")
|
|
if admin {
|
|
r.AddCookie(cookie)
|
|
r.Header.Set("X-CSRF-Token", csrfToken(c.Server.SessionSecret, cookie.Value))
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
if w := request("PUT", "/ui/api/config", body, false); w.Code != 303 {
|
|
t.Fatalf("unprotected admin API: %d", w.Code)
|
|
}
|
|
if w := request("PUT", "/ui/api/config", body, true); w.Code != 200 {
|
|
t.Fatalf("save: %d %s", w.Code, w.Body)
|
|
}
|
|
if store.Get().Server.SessionSecret != c.Server.SessionSecret {
|
|
t.Fatal("admin secret changed")
|
|
}
|
|
reopened, err := config.Open(configPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(reopened.Get().Outbounds) != 1 || reopened.Get().Outbounds[0].ID != "discord-ops" {
|
|
t.Fatal("outbound was not persisted")
|
|
}
|
|
w := request("GET", "/ui/api/config", nil, true)
|
|
var loaded editableConfig
|
|
if err := json.Unmarshal(w.Body.Bytes(), &loaded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(loaded.Outbounds) != 1 || loaded.Outbounds[0].Live || loaded.Mappings[0].OutboundID != "discord-ops" {
|
|
t.Fatalf("round trip: %+v", loaded)
|
|
}
|
|
preview, _ := json.Marshal(map[string]any{"mapping": edit.Mappings[0], "message": map[string]any{"source": "ntfy", "title": "Hello", "message": "World"}})
|
|
w = request("POST", "/ui/api/preview", preview, true)
|
|
if w.Code != 200 || !strings.Contains(w.Body.String(), `"content":"Hello\nWorld"`) {
|
|
t.Fatalf("preview: %d %s", w.Code, w.Body)
|
|
}
|
|
for _, authorized := range []bool{false, true} {
|
|
r := httptest.NewRequest("POST", "/ntfy/ops", strings.NewReader("World"))
|
|
r.Header.Set("Title", "Hello")
|
|
if authorized {
|
|
r.Header.Set("Authorization", "Bearer ingress-token")
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if !authorized {
|
|
if w.Code != 401 {
|
|
t.Fatalf("ingress auth: %d", w.Code)
|
|
}
|
|
continue
|
|
}
|
|
if w.Code != 200 || !strings.Contains(w.Body.String(), `"outbound_id":"discord-ops"`) || !strings.Contains(w.Body.String(), `dry_run`) {
|
|
t.Fatalf("delivery: %d %s", w.Code, w.Body)
|
|
}
|
|
if strings.Contains(w.Body.String(), "secret") {
|
|
t.Fatal("destination token leaked")
|
|
}
|
|
}
|
|
// A destination still referenced by a mapping cannot be removed.
|
|
edit.Outbounds = nil
|
|
body, _ = json.Marshal(edit)
|
|
if w := request("PUT", "/ui/api/config", body, true); w.Code != 400 {
|
|
t.Fatalf("invalid reference: %d %s", w.Code, w.Body)
|
|
}
|
|
if len(store.Get().Outbounds) != 1 {
|
|
t.Fatal("invalid config was persisted")
|
|
}
|
|
}
|