fix: set Secure/HttpOnly/SameSite on session cookies (closes #47) #48
92
internal/session/session_cookie_attributes_test.go
Normal file
92
internal/session/session_cookie_attributes_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user