tercul-backend/internal/platform/http/middleware.go
google-labs-jules[bot] 53aa4d0344
Security Hardening and GraphQL Caching (#69)
* feat: add security middleware, graphql apq, and improved linting

- Add RateLimit, RequestValidation, and CORS middleware.
- Configure middleware chain in API server.
- Implement Redis cache for GraphQL Automatic Persisted Queries.
- Add .golangci.yml and fix linting issues (shadowing, timeouts).

* feat: security, caching and linting config

- Fix .golangci.yml config for govet shadow check
- (Previous changes: Security middleware, GraphQL APQ, Linting fixes)

* fix: resolve remaining lint errors

- Fix unhandled errors in tests (errcheck)
- Define constants for repeated strings (goconst)
- Suppress high complexity warnings with nolint:gocyclo
- Fix integer overflow warnings (gosec)
- Add package comments
- Split long lines (lll)
- Rename Analyse -> Analyze (misspell)
- Fix naked returns and unused params

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-12-01 00:14:22 +01:00

87 lines
2.8 KiB
Go

// Package http provides HTTP middleware and utilities.
package http
import (
"net/http"
"strings"
)
// CORSMiddleware handles Cross-Origin Resource Sharing
func CORSMiddleware(allowedOrigins []string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
allowed := false
// If no allowed origins configured, allow all (development mode usually)
if len(allowedOrigins) == 0 {
allowed = true
} else {
for _, o := range allowedOrigins {
if o == "*" || o == origin {
allowed = true
break
}
}
}
// Safe default if we want to allow everything
if allowed {
// If origin is present, use it, otherwise *
if origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Client-ID, X-API-Key")
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
}
// RequestValidationMiddleware performs basic request validation
func RequestValidationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check Content-Type for POST requests to /query
if r.Method == "POST" && r.URL.Path == "/query" {
ct := r.Header.Get("Content-Type")
// GraphQL clients might send application/json; charset=utf-8
if !strings.Contains(ct, "application/json") {
// Some clients might send no content type or something else?
// Strictly enforcing application/json is good for security.
// But we should be careful not to break existing clients if they are sloppy.
// For now, let's enforce it as requested.
http.Error(w, "Content-Type must be application/json", http.StatusUnsupportedMediaType)
return
}
}
next.ServeHTTP(w, r)
})
}
// APIKeyMiddleware checks for X-API-Key header
// This is a placeholder for future external integrations.
// It allows requests with a valid API key to bypass other auth or strictly enforce it.
// Currently it is a pass-through as we don't have defined API keys in config yet.
func APIKeyMiddleware(validAPIKeys []string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// If we had keys, we would check them here.
// apiKey := r.Header.Get("X-API-Key")
// validate(apiKey)
next.ServeHTTP(w, r)
})
}
}