Re-vendor the canonical files from sneak/prompts at dd4027b (closes #112)
check / check (push) Failing after 4s
check / check (push) Failing after 4s
The shared files are fetched from sneak/prompts dd4027b, with this repository's own entries after the shared content in .gitignore, .editorconfig and .dockerignore. Lint and tests are Dockerfile phases (golangci-lint v2.14.0, Debian Go 1.24.13) that the build stage depends on, and the Makefile targets call the script/ entrypoints. make fmt also formats Markdown with prettier. Fixes for the new lint findings: the auth cookie is always Secure, an IRC connection's relay goroutine stops when the connection closes, and repeated strings are constants. neoirc-cli treats a plain-HTTP server on localhost or a loopback address as secure, so local use keeps its session. Whether the 60-second test cap covers building the test phase is open on sneak/prompts issue 113. Model: opus-5-5
This commit was merged in pull request #115.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user