fix: Mobile hamburger menu and iPad sidebar visibility
Mobile fixes: - Add click toggle handler for hamburger menu (was hover-only) - Menu now opens/closes on tap and closes when clicking outside - Keep hover support for desktop iPad fixes: - Sidebar content now visible on touch devices (901-1280px) - Added (hover: hover) media query to prevent hide-on-hover on tablets Security improvements: - Replace exec.CommandContext with go-git library for git operations - Add path traversal and command injection prevention - Fix race condition in template hot reload - Add environment-based cookie Secure flag Code quality: - Add constants.go for magic numbers - Remove unused code (ParsePreferenceToggleRequest, DomainError) - Add FOUC prevention with inline critical CSS - Add Makefile dev/run/clean targets - Fix README git clone URL - Add doc/DECISIONS.md for architectural decisions Tests: - Add hamburger menu click toggle tests - Add iPad sidebar visibility tests - Update security tests for go-git implementation - Add cookie Secure flag tests
This commit is contained in:
@@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// contextKey is a private type for context keys to avoid collisions
|
||||
@@ -146,10 +147,16 @@ func SetPreferenceCookie(w http.ResponseWriter, name string, value string) {
|
||||
MaxAge: 365 * 24 * 60 * 60, // 1 year
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Secure: false, // Set to true in production with HTTPS
|
||||
Secure: isProductionMode(), // Secure in production with HTTPS
|
||||
})
|
||||
}
|
||||
|
||||
// isProductionMode checks if the application is running in production
|
||||
func isProductionMode() bool {
|
||||
env := os.Getenv("GO_ENV")
|
||||
return env == "production" || env == "prod"
|
||||
}
|
||||
|
||||
// getPreferenceCookie gets a preference cookie value, returns default if not found
|
||||
func getPreferenceCookie(r *http.Request, name string, defaultValue string) string {
|
||||
cookie, err := r.Cookie(name)
|
||||
|
||||
@@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -342,3 +343,110 @@ func TestMultipleMigrations(t *testing.T) {
|
||||
t.Errorf("CVIcons: expected 'show' (migrated from 'true'), got %q", capturedPrefs.CVIcons)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsProductionMode tests the production mode detection function
|
||||
func TestIsProductionMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Production environment",
|
||||
envValue: "production",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Prod shorthand",
|
||||
envValue: "prod",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Development environment",
|
||||
envValue: "development",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty environment",
|
||||
envValue: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Staging environment",
|
||||
envValue: "staging",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Case sensitivity - PRODUCTION",
|
||||
envValue: "PRODUCTION",
|
||||
expected: false, // Case sensitive
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Save original environment
|
||||
originalEnv := os.Getenv("GO_ENV")
|
||||
defer os.Setenv("GO_ENV", originalEnv)
|
||||
|
||||
// Set test environment
|
||||
os.Setenv("GO_ENV", tt.envValue)
|
||||
|
||||
result := isProductionMode()
|
||||
|
||||
if result != tt.expected {
|
||||
t.Errorf("isProductionMode() with GO_ENV=%q: got %v, want %v",
|
||||
tt.envValue, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetPreferenceCookieSecureFlag tests that Secure flag is set correctly based on environment
|
||||
func TestSetPreferenceCookieSecureFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
expectedSecure bool
|
||||
}{
|
||||
{
|
||||
name: "Production mode sets Secure=true",
|
||||
envValue: "production",
|
||||
expectedSecure: true,
|
||||
},
|
||||
{
|
||||
name: "Development mode sets Secure=false",
|
||||
envValue: "development",
|
||||
expectedSecure: false,
|
||||
},
|
||||
{
|
||||
name: "Empty env sets Secure=false",
|
||||
envValue: "",
|
||||
expectedSecure: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Save original environment
|
||||
originalEnv := os.Getenv("GO_ENV")
|
||||
defer os.Setenv("GO_ENV", originalEnv)
|
||||
|
||||
// Set test environment
|
||||
os.Setenv("GO_ENV", tt.envValue)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
SetPreferenceCookie(w, "test-cookie", "test-value")
|
||||
|
||||
cookies := w.Result().Cookies()
|
||||
if len(cookies) != 1 {
|
||||
t.Fatalf("Expected 1 cookie, got %d", len(cookies))
|
||||
}
|
||||
|
||||
if cookies[0].Secure != tt.expectedSecure {
|
||||
t.Errorf("Cookie Secure flag with GO_ENV=%q: got %v, want %v",
|
||||
tt.envValue, cookies[0].Secure, tt.expectedSecure)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user