Files
Pete/internal/web/csrf_test.go
T
prosolis 4eaf8ae366 web: refuse writes that come from somebody else's page
The session cookie is scoped to the parent domain and is SameSite=Lax, and
every *.parodia.dev host is the same site as Pete as far as Lax is concerned.
So a form on any sibling subdomain could post a bet, a mischief order or an
equip change and the browser would attach whoever's signed-in cookie to it.
Nothing on the server looked at where a request came from.

The whole handler now sits behind http.NewCrossOriginProtection. It reads
Sec-Fetch-Site (falling back to Origin against Host) and refuses a
state-changing request from any other origin with a 403. GETs are untouched,
and so is anything that carries neither header, which is exactly what
gogobee's bearer-token calls from Go look like.
2026-09-22 22:54:38 -07:00

52 lines
1.6 KiB
Go

package web
import (
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"pete/internal/config"
"pete/internal/storage"
)
// A sibling parodia.dev host is the same *site* as Pete, so SameSite=Lax lets
// the session cookie ride along on its forms. The server has to refuse those
// itself, and must still let a same-origin page and a headerless server-to-server
// call (gogobee, with its bearer token) through.
func TestCrossOriginWritesAreRefused(t *testing.T) {
storage.Close()
if err := storage.Init(filepath.Join(t.TempDir(), "csrf.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { storage.Close() })
s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil, true, config.AdventureConfig{}, nil)
if err != nil {
t.Fatal(err)
}
post := func(hdr map[string]string) int {
req := httptest.NewRequest("POST", "https://news.parodia.dev/api/state/read", nil)
for k, v := range hdr {
req.Header.Set(k, v)
}
rec := httptest.NewRecorder()
s.srv.Handler.ServeHTTP(rec, req)
return rec.Code
}
if code := post(map[string]string{"Sec-Fetch-Site": "cross-site"}); code != http.StatusForbidden {
t.Errorf("cross-site POST = %d, want 403", code)
}
if code := post(map[string]string{"Sec-Fetch-Site": "same-site", "Origin": "https://evil.parodia.dev"}); code != http.StatusForbidden {
t.Errorf("sibling-subdomain POST = %d, want 403", code)
}
if code := post(map[string]string{"Sec-Fetch-Site": "same-origin"}); code == http.StatusForbidden {
t.Error("same-origin POST was refused")
}
if code := post(nil); code == http.StatusForbidden {
t.Error("headerless server-to-server POST was refused")
}
}