Files
pixa/internal/session/session_test.go
T
clawbot 77d55abb7e
check / check (push) Canceled after 0s
Derive every key from the signing key in one place (closes #315)
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
2026-10-26 14:10:20 +01:00

251 lines
5.7 KiB
Go

package session_test
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"sneak.berlin/go/pixa/internal/seal"
"sneak.berlin/go/pixa/internal/session"
)
func TestManager_CreateAndValidate(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)
// Create a session
w := httptest.NewRecorder()
err = mgr.CreateSession(w)
if err != nil {
t.Fatalf("CreateSession() error = %v", err)
}
// Extract the cookie from response
resp := w.Result()
cookies := resp.Cookies()
if len(cookies) == 0 {
t.Fatal("CreateSession() did not set a cookie")
}
var sessionCookie *http.Cookie
for _, c := range cookies {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if sessionCookie == nil {
t.Fatalf("CreateSession() did not set cookie named %q", session.CookieName)
}
// Validate the session
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
data, err := mgr.ValidateSession(req)
if err != nil {
t.Fatalf("ValidateSession() error = %v", err)
}
if !data.Authenticated {
t.Error("ValidateSession() returned unauthenticated session")
}
if data.ExpiresAt.Before(time.Now()) {
t.Error("ValidateSession() returned already-expired session")
}
}
func TestManager_ValidateSession_NoCookie(t *testing.T) {
t.Parallel()
keys, _ := seal.DeriveKeys("test-signing-key-12345")
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
_, err := mgr.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with no cookie")
}
if !errors.Is(err, session.ErrNoSession) {
t.Errorf("ValidateSession() error = %v, want %v", err, session.ErrNoSession)
}
}
func TestManager_ValidateSession_TamperedCookie(t *testing.T) {
t.Parallel()
keys, _ := seal.DeriveKeys("test-signing-key-12345")
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: session.CookieName,
Value: "tampered-invalid-cookie-value",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
_, err := mgr.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with tampered cookie")
}
if !errors.Is(err, session.ErrInvalidSession) {
t.Errorf("ValidateSession() error = %v, want %v", err, session.ErrInvalidSession)
}
}
func TestManager_ValidateSession_WrongKey(t *testing.T) {
t.Parallel()
keys1, _ := seal.DeriveKeys("signing-key-1")
keys2, _ := seal.DeriveKeys("signing-key-2")
mgr1 := session.NewManager(keys1.SessionHashKey, keys1.SessionBlockKey)
mgr2 := session.NewManager(keys2.SessionHashKey, keys2.SessionBlockKey)
// Create session with mgr1
w := httptest.NewRecorder()
_ = mgr1.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
// Try to validate with mgr2 (different key)
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
_, err := mgr2.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with different signing key")
}
}
func TestManager_ClearSession(t *testing.T) {
t.Parallel()
keys, _ := seal.DeriveKeys("test-signing-key-12345")
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
w := httptest.NewRecorder()
mgr.ClearSession(w)
resp := w.Result()
cookies := resp.Cookies()
var sessionCookie *http.Cookie
for _, c := range cookies {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if sessionCookie == nil {
t.Fatal("ClearSession() did not set a cookie")
}
if sessionCookie.MaxAge != -1 {
t.Errorf("ClearSession() cookie MaxAge = %d, want -1", sessionCookie.MaxAge)
}
}
func TestManager_IsAuthenticated(t *testing.T) {
t.Parallel()
keys, _ := seal.DeriveKeys("test-signing-key-12345")
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
// No session - should return false
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
if mgr.IsAuthenticated(req) {
t.Error("IsAuthenticated() should return false with no session")
}
// Create session
w := httptest.NewRecorder()
_ = mgr.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
// With valid session - should return true
req = httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
if !mgr.IsAuthenticated(req) {
t.Error("IsAuthenticated() should return true with valid session")
}
}
func TestManager_CookieAttributes(t *testing.T) {
t.Parallel()
keys, _ := seal.DeriveKeys("test-key")
mgr := session.NewManager(keys.SessionHashKey, keys.SessionBlockKey)
w := httptest.NewRecorder()
_ = mgr.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if !sessionCookie.HttpOnly {
t.Error("Cookie should have HttpOnly flag")
}
if !sessionCookie.Secure {
t.Error("Cookie should have Secure flag when manager created with secure=true")
}
if sessionCookie.SameSite != http.SameSiteStrictMode {
t.Errorf("Cookie SameSite = %v, want %v",
sessionCookie.SameSite, http.SameSiteStrictMode)
}
}