diff --git a/jaws.go b/jaws.go index 694d6712..d685bea2 100644 --- a/jaws.go +++ b/jaws.go @@ -94,8 +94,13 @@ type Jaws struct { // It defaults to [assets.DefaultCookieName], which is derived from the // executable and falls back to "jaws". CookieName must be a valid, non-empty // HTTP cookie name; see [http.Cookie.Valid]. - CookieName string - AutoSession bool // Create a session during a successful WebSocket upgrade when a Request has none and the Session limits allow it. Defaults to false. + CookieName string + // AutoSession creates a [Session] on a successful WebSocket upgrade. + // + // It applies when the [Request] has no Session and Session limits allow one. + // The cookie's Secure flag follows the initial page request's scheme, honoring + // [Jaws.TrustForwardedHeaders]. AutoSession defaults to false. + AutoSession bool // TrustForwardedHeaders enables trusted proxy header processing. // // It governs the session cookie Secure flag and WebSocket Origin scheme diff --git a/request.go b/request.go index d950613c..fe17094b 100644 --- a/request.go +++ b/request.go @@ -372,7 +372,7 @@ func (rq *Request) ensureAutoSession(w http.ResponseWriter, r *http.Request) { // through Jaws session lookups. func (rq *Request) newAutoSession(r *http.Request) (sess *Session) { jw := rq.Jaws - secure := secureheaders.RequestIsSecure(r, jw.TrustForwardedHeaders) + secure := secureheaders.RequestIsSecure(rq.Initial(), jw.TrustForwardedHeaders) remoteIP := jw.clientIP(r) jw.mu.Lock() defer jw.mu.Unlock() diff --git a/request_test.go b/request_test.go index 616b0613..3eca0484 100644 --- a/request_test.go +++ b/request_test.go @@ -4151,6 +4151,64 @@ func TestWS_AutoSessionCreatesSession(t *testing.T) { } } +func TestWS_AutoSessionSecureFollowsPage(t *testing.T) { + for _, tc := range []struct { + name string + pageScheme string + upgradeScheme string + wantSecure bool + }{ + {"HTTP page", "http", "", false}, + {"HTTPS scheme on both requests", "https", "https", true}, + {"HTTPS scheme only on page", "https", "", true}, + {"HTTPS scheme only on upgrade", "http", "https", false}, + } { + t.Run(tc.name, func(t *testing.T) { + jw, err := New() + if err != nil { + t.Fatal(err) + } + jw.AutoSession = true + jw.TrustForwardedHeaders = true + go jw.Serve() + t.Cleanup(jw.Close) + server := httptest.NewServer(jw) + t.Cleanup(server.Close) + u, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + page := httptest.NewRequest(http.MethodGet, server.URL+"/", nil) + page.Header.Set("X-Forwarded-For", "203.0.113.9") + page.Header.Set("X-Forwarded-Proto", tc.pageScheme) + rq := jw.NewRequest(httptest.NewRecorder(), page) + header := http.Header{} + header.Set("Origin", tc.pageScheme+"://"+u.Host) + header.Set("X-Forwarded-For", "203.0.113.9") + if tc.upgradeScheme != "" { + header.Set("X-Forwarded-Proto", tc.upgradeScheme) + } + ctx, cancel := context.WithTimeout(t.Context(), testTimeout) + defer cancel() + conn, resp, err := websocket.Dial(ctx, server.URL+"/jaws/"+rq.JawsKeyString(), &websocket.DialOptions{HTTPHeader: header}) + if err != nil { + t.Fatal(err) + } + defer func() { _ = conn.CloseNow() }() + if resp.StatusCode != http.StatusSwitchingProtocols { + t.Fatalf("status = %d, want 101", resp.StatusCode) + } + cookies := resp.Cookies() + if len(cookies) != 1 { + t.Fatalf("AutoSession cookies = %v, want one", cookies) + } + if cookies[0].Secure != tc.wantSecure { + t.Fatalf("AutoSession cookie Secure = %v, want %v", cookies[0].Secure, tc.wantSecure) + } + }) + } +} + func TestWS_AutoSessionDoesNotCreateAfterJawsClose(t *testing.T) { jw, err := New() if err != nil {