check / check (push) Waiting to run
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
132 lines
2.9 KiB
Go
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)
|
|
}
|
|
}
|