Add a failing test asserting that every cookie written by the session manager (both the CreateSession set-cookie path and the ClearSession delete-cookie path) carries HttpOnly, Secure, and a SameSite mode of Lax or stricter, regardless of constructor arguments. Session cookies carry authentication state and must never be sent over plaintext HTTP. Currently fails for the constructor secure=false case, which produces cookies without the Secure attribute (gosec G124 at internal/session/session.go:84 and :128).
93 lines
2.5 KiB
Go
93 lines
2.5 KiB
Go
package session
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// TestSessionCookieAttributesAlwaysSecure verifies that every cookie
|
|
// emitted by the session manager carries HttpOnly, Secure, and a
|
|
// SameSite mode of Lax or stricter, regardless of how the manager was
|
|
// constructed. Session cookies contain the authentication state and
|
|
// must never be exposed to script (HttpOnly), sent over plaintext HTTP
|
|
// (Secure), or attached to cross-site requests (SameSite). No
|
|
// constructor argument may weaken these attributes.
|
|
//
|
|
// This covers both cookie-writing paths: CreateSession (the login
|
|
// set-cookie path) and ClearSession (the logout delete-cookie path).
|
|
func TestSessionCookieAttributesAlwaysSecure(t *testing.T) {
|
|
for _, constructorBoolArg := range []bool{false, true} {
|
|
mgr, err := NewManager("test-signing-key-12345", constructorBoolArg)
|
|
if err != nil {
|
|
t.Fatalf("NewManager() error = %v", err)
|
|
}
|
|
|
|
writePaths := []struct {
|
|
name string
|
|
setCookie func(t *testing.T, w http.ResponseWriter)
|
|
}{
|
|
{
|
|
name: "CreateSession",
|
|
setCookie: func(t *testing.T, w http.ResponseWriter) {
|
|
t.Helper()
|
|
if err := mgr.CreateSession(w); err != nil {
|
|
t.Fatalf("CreateSession() error = %v", err)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "ClearSession",
|
|
setCookie: func(t *testing.T, w http.ResponseWriter) {
|
|
t.Helper()
|
|
mgr.ClearSession(w)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, writePath := range writePaths {
|
|
testName := writePath.name
|
|
if constructorBoolArg {
|
|
testName += "/constructorBoolArg=true"
|
|
} else {
|
|
testName += "/constructorBoolArg=false"
|
|
}
|
|
|
|
t.Run(testName, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writePath.setCookie(t, w)
|
|
|
|
var sessionCookie *http.Cookie
|
|
for _, c := range w.Result().Cookies() {
|
|
if c.Name == CookieName {
|
|
sessionCookie = c
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if sessionCookie == nil {
|
|
t.Fatalf("no cookie named %q was set", CookieName)
|
|
}
|
|
|
|
t.Logf("cookie attributes: HttpOnly=%v Secure=%v SameSite=%v",
|
|
sessionCookie.HttpOnly, sessionCookie.Secure, sessionCookie.SameSite)
|
|
|
|
if !sessionCookie.HttpOnly {
|
|
t.Error("session cookie must have HttpOnly set")
|
|
}
|
|
|
|
if !sessionCookie.Secure {
|
|
t.Error("session cookie must have Secure set")
|
|
}
|
|
|
|
if sessionCookie.SameSite != http.SameSiteLaxMode &&
|
|
sessionCookie.SameSite != http.SameSiteStrictMode {
|
|
t.Errorf("session cookie SameSite = %v, want Lax (%v) or Strict (%v)",
|
|
sessionCookie.SameSite, http.SameSiteLaxMode, http.SameSiteStrictMode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|