Files
neoirc/internal/cli/api/client_test.go
T
sneak 778b26cc0d
check / check (push) Waiting to run
Test that neoirc-cli keeps its auth cookie off plain HTTP to other hosts
The cookie test showed the Secure auth cookie reaching localhost and
127.0.0.1 over plain HTTP, but not that it is still withheld over plain
HTTP from every other host. A new test points the client at
neoirc.example, sends its connections to the test server, and checks
that the cookie does not arrive.

Model: opus-5-5
2026-10-06 16:16:05 +00:00

132 lines
2.9 KiB
Go

package neoircapi_test
import (
"context"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
api "sneak.berlin/go/neoirc/internal/cli/api"
)
const cookieValue = "opaque-value"
// newSessionServer starts a plain-HTTP server that, like
// neoircd, sets a Secure auth cookie when a session is
// created and answers GET /api/v1/state only when that
// cookie comes back.
func newSessionServer(t *testing.T) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/server", func(
writer http.ResponseWriter, _ *http.Request,
) {
_, _ = io.WriteString(writer, `{}`)
})
mux.HandleFunc("POST /api/v1/session", func(
writer http.ResponseWriter, _ *http.Request,
) {
http.SetCookie(writer, &http.Cookie{
Name: "neoirc_auth",
Value: cookieValue,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
writer.WriteHeader(http.StatusCreated)
_, _ = io.WriteString(writer, `{"id":1,"nick":"alice"}`)
})
mux.HandleFunc("GET /api/v1/state", func(
writer http.ResponseWriter, request *http.Request,
) {
cookie, err := request.Cookie("neoirc_auth")
if err != nil || cookie.Value != cookieValue {
writer.WriteHeader(http.StatusUnauthorized)
return
}
_, _ = io.WriteString(
writer, `{"id":1,"nick":"alice","channels":[]}`,
)
})
server := httptest.NewServer(mux)
t.Cleanup(server.Close)
return server
}
func TestClientKeepsSessionOverPlainHTTPOnLocalhost(t *testing.T) {
t.Parallel()
server := newSessionServer(t)
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatalf("parse server URL: %v", err)
}
for _, host := range []string{"127.0.0.1", "localhost"} {
t.Run(host, func(t *testing.T) {
t.Parallel()
client := api.NewClient(
"http://" + net.JoinHostPort(host, serverURL.Port()),
)
_, err := client.CreateSession("alice")
if err != nil {
t.Fatalf("create session: %v", err)
}
_, err = client.GetState()
if err != nil {
t.Fatalf("state after creating the session: %v", err)
}
})
}
}
func TestClientWithholdsCookieOverPlainHTTPFromOtherHosts(t *testing.T) {
t.Parallel()
server := newSessionServer(t)
// neoirc.example is not loopback. Every connection the client
// opens to it goes to the test server instead.
client := api.NewClient("http://neoirc.example")
client.HTTPClient.Transport = &http.Transport{
DialContext: func(
ctx context.Context, network, _ string,
) (net.Conn, error) {
var dialer net.Dialer
return dialer.DialContext(
ctx, network, server.Listener.Addr().String(),
)
},
}
_, err := client.CreateSession("alice")
if err != nil {
t.Fatalf("create session: %v", err)
}
// The server answers 401 when the auth cookie does not arrive.
_, err = client.GetState()
if err == nil || !strings.Contains(err.Error(), "401") {
t.Fatalf("state: got %v, want 401: cookie sent over plain HTTP", err)
}
}