check / check (push) Waiting to run
seal.DeriveKeys derives the session hash and block keys, the encrypted URL key and the CSRF key from the signing key, with their salts declared beside it, and returns them as one seal.Keys. internal/providers calls it once at startup and provides the value to the fx graph. The session manager, the encrypted URL generator and the CSRF protection each take their own key. session.NewManager, encurl.NewGenerator, newCSRFProtect and handlers.New return no error, as nothing in them can fail now. Each key is derived as before, with seal.DeriveKey and the same salt. The signing key itself stays the HMAC key of signed URLs and the login password. README.md says under signing_key what it protects. Model: opus-5-5
95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
package session_test
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/pixa/internal/seal"
|
|
"sneak.berlin/go/pixa/internal/session"
|
|
)
|
|
|
|
// TestSessionCookieAttributesAlwaysSecure verifies that every cookie
|
|
// emitted by the session manager carries HttpOnly, Secure, and a
|
|
// SameSite mode of Lax or stricter. 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). Nothing 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) {
|
|
t.Parallel()
|
|
|
|
keys, err := seal.DeriveKeys("test-signing-key-12345")
|
|
if err != nil {
|
|
t.Fatalf("seal.DeriveKeys() error = %v", err)
|
|
}
|
|
|
|
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
|
|
|
|
writePaths := []struct {
|
|
name string
|
|
setCookie func(t *testing.T, w http.ResponseWriter)
|
|
}{
|
|
{
|
|
name: "CreateSession",
|
|
setCookie: func(t *testing.T, w http.ResponseWriter) {
|
|
t.Helper()
|
|
|
|
err := mgr.CreateSession(w)
|
|
if 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 {
|
|
t.Run(writePath.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
w := httptest.NewRecorder()
|
|
writePath.setCookie(t, w)
|
|
|
|
var sessionCookie *http.Cookie
|
|
|
|
for _, c := range w.Result().Cookies() {
|
|
if c.Name == session.CookieName {
|
|
sessionCookie = c
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if sessionCookie == nil {
|
|
t.Fatalf("no cookie named %q was set", session.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)
|
|
}
|
|
})
|
|
}
|
|
}
|