refactor: use 'c' alias for constants package

- Update all imports from 'constants' to 'c' for brevity
- Replace all 'constants.' references with 'c.'
- Fix remaining hardcoded content-type headers in httputil
- Fix remaining hardcoded User-Agent and Accept headers
- Rename CSRF receiver from 'c' to 'csrf' to avoid conflict
- Add ContentTypePlainSimple constant for Accept header matching
- Fix JSONCached to use proper integer formatting
This commit is contained in:
juanatsap
2025-12-06 16:31:42 +00:00
parent 2c7f8de242
commit 30ed21ff7a
21 changed files with 1335 additions and 167 deletions
+22 -22
View File
@@ -7,26 +7,26 @@ import (
"sync"
"time"
"github.com/juanatsap/cv-site/internal/constants"
c "github.com/juanatsap/cv-site/internal/constants"
)
// SecurityHeaders adds production-grade security headers to responses
func SecurityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Prevent clickjacking
w.Header().Set(constants.HeaderXFrameOptions, constants.FrameOptionsSameOrigin)
w.Header().Set(c.HeaderXFrameOptions, c.FrameOptionsSameOrigin)
// Prevent MIME type sniffing
w.Header().Set(constants.HeaderXContentTypeOpts, constants.NoSniff)
w.Header().Set(c.HeaderXContentTypeOpts, c.NoSniff)
// XSS Protection (legacy but still useful for older browsers)
w.Header().Set(constants.HeaderXXSSProtection, constants.XSSProtection)
w.Header().Set(c.HeaderXXSSProtection, c.XSSProtection)
// Referrer policy - strict privacy
w.Header().Set(constants.HeaderReferrerPolicy, constants.ReferrerPolicy)
w.Header().Set(c.HeaderReferrerPolicy, c.ReferrerPolicy)
// Permissions Policy - disable unnecessary features
w.Header().Set(constants.HeaderPermissionsPolicy,
w.Header().Set(c.HeaderPermissionsPolicy,
"geolocation=(), microphone=(), camera=(), payment=(), usb=(), "+
"magnetometer=(), gyroscope=(), accelerometer=()")
@@ -40,12 +40,12 @@ func SecurityHeaders(next http.Handler) http.Handler {
"frame-ancestors 'self'; " +
"base-uri 'self'; " +
"form-action 'self'"
w.Header().Set(constants.HeaderCSP, csp)
w.Header().Set(c.HeaderCSP, csp)
// HSTS - only in production with HTTPS
if os.Getenv(constants.EnvVarGOEnv) == constants.EnvProduction {
if os.Getenv(c.EnvVarGOEnv) == c.EnvProduction {
// 1 year max-age, include subdomains
w.Header().Set(constants.HeaderHSTS, constants.HSTSMaxAge)
w.Header().Set(c.HeaderHSTS, c.HSTSMaxAge)
}
next.ServeHTTP(w, r)
@@ -76,7 +76,7 @@ func OriginChecker(next http.Handler) http.Handler {
}
// Check Origin header (for CORS requests)
origin := r.Header.Get(constants.HeaderOrigin)
origin := r.Header.Get(c.HeaderOrigin)
if origin != "" {
if !isAllowedOrigin(origin, allowedOrigins) {
http.Error(w, "Forbidden: External access not allowed", http.StatusForbidden)
@@ -85,7 +85,7 @@ func OriginChecker(next http.Handler) http.Handler {
}
// Check Referer header (for direct requests)
referer := r.Header.Get(constants.HeaderReferer)
referer := r.Header.Get(c.HeaderReferer)
if referer != "" {
if !isAllowedOrigin(referer, allowedOrigins) {
http.Error(w, "Forbidden: External access not allowed", http.StatusForbidden)
@@ -98,7 +98,7 @@ func OriginChecker(next http.Handler) http.Handler {
if origin == "" && referer == "" {
// For production, you might want to be stricter here
// For now, allow it (users can bookmark /export/pdf directly)
if os.Getenv(constants.EnvVarGOEnv) == constants.EnvProduction && r.URL.Path == constants.RouteExportPDF {
if os.Getenv(c.EnvVarGOEnv) == c.EnvProduction && r.URL.Path == c.RouteExportPDF {
// In production, require at least a referer for PDF endpoint
http.Error(w, "Forbidden: Direct access not allowed", http.StatusForbidden)
return
@@ -163,16 +163,16 @@ func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get client IP (handle X-Forwarded-For for proxies)
ip := r.Header.Get(constants.HeaderXForwardedFor)
ip := r.Header.Get(c.HeaderXForwardedFor)
if ip == "" {
ip = r.Header.Get(constants.HeaderXRealIP)
ip = r.Header.Get(c.HeaderXRealIP)
}
if ip == "" {
ip = strings.Split(r.RemoteAddr, ":")[0]
}
if !rl.allow(ip) {
w.Header().Set(constants.HeaderRetryAfter, "60")
w.Header().Set(c.HeaderRetryAfter, "60")
http.Error(w, "Rate limit exceeded. Please try again later.", http.StatusTooManyRequests)
return
}
@@ -227,12 +227,12 @@ func (rl *RateLimiter) cleanup() {
// 1 hour in development, 1 day in production
func CacheControl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cacheValue := constants.CachePublic1Hour
if os.Getenv(constants.EnvVarGOEnv) == constants.EnvProduction {
cacheValue = constants.CachePublic1Day
cacheValue := c.CachePublic1Hour
if os.Getenv(c.EnvVarGOEnv) == c.EnvProduction {
cacheValue = c.CachePublic1Day
}
w.Header().Set(constants.HeaderCacheControl, cacheValue)
w.Header().Set(c.HeaderCacheControl, cacheValue)
next.ServeHTTP(w, r)
})
}
@@ -243,12 +243,12 @@ func DynamicCacheControl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// For dynamic HTML pages: short cache, must revalidate
// This improves performance while ensuring fresh content
if os.Getenv(constants.EnvVarGOEnv) == constants.EnvProduction {
if os.Getenv(c.EnvVarGOEnv) == c.EnvProduction {
// Production: 5 minutes cache, but must revalidate
w.Header().Set(constants.HeaderCacheControl, constants.CachePublic5Min)
w.Header().Set(c.HeaderCacheControl, c.CachePublic5Min)
} else {
// Development: no cache for easier testing
w.Header().Set(constants.HeaderCacheControl, constants.CacheNoStore)
w.Header().Set(c.HeaderCacheControl, c.CacheNoStore)
}
next.ServeHTTP(w, r)
})