mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-09 09:04:15 +00:00
Merge branch 'main' into breaking/v2
This commit is contained in:
@@ -54,6 +54,8 @@ func initRouter(db *gorm.DB, svc *services) (utils.Service, error) {
|
||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware().Add(rate.Every(time.Second), 60)
|
||||
|
||||
// Setup global middleware
|
||||
r.Use(middleware.HeadMiddleware())
|
||||
r.Use(middleware.NewCacheControlMiddleware().Add())
|
||||
r.Use(middleware.NewCorsMiddleware().Add())
|
||||
r.Use(middleware.NewCspMiddleware().Add())
|
||||
r.Use(middleware.NewErrorHandlerMiddleware().Add())
|
||||
@@ -101,7 +103,17 @@ func initRouter(db *gorm.DB, svc *services) (utils.Service, error) {
|
||||
srv := &http.Server{
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
Handler: r,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// HEAD requests don't get matched by Gin routes, so we convert them to GET
|
||||
// middleware.HeadMiddleware will convert them back to HEAD later
|
||||
if req.Method == http.MethodHead {
|
||||
req.Method = http.MethodGet
|
||||
ctx := context.WithValue(req.Context(), middleware.IsHeadRequestCtxKey{}, true)
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
}),
|
||||
}
|
||||
|
||||
// Set up the listener
|
||||
|
||||
26
backend/internal/middleware/cache_control.go
Normal file
26
backend/internal/middleware/cache_control.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// CacheControlMiddleware sets a safe default Cache-Control header on responses
|
||||
// that do not already specify one. This prevents proxies from caching
|
||||
// authenticated responses that might contain private data.
|
||||
type CacheControlMiddleware struct {
|
||||
headerValue string
|
||||
}
|
||||
|
||||
func NewCacheControlMiddleware() *CacheControlMiddleware {
|
||||
return &CacheControlMiddleware{
|
||||
headerValue: "private, no-store",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CacheControlMiddleware) Add() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Writer.Header().Get("Cache-Control") == "" {
|
||||
c.Header("Cache-Control", m.headerValue)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
45
backend/internal/middleware/cache_control_test.go
Normal file
45
backend/internal/middleware/cache_control_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCacheControlMiddlewareSetsDefault(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.Use(NewCacheControlMiddleware().Add())
|
||||
|
||||
router.GET("/test", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, "private, no-store", w.Header().Get("Cache-Control"))
|
||||
}
|
||||
|
||||
func TestCacheControlMiddlewarePreservesExistingHeader(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.Use(NewCacheControlMiddleware().Add())
|
||||
|
||||
router.GET("/custom", func(c *gin.Context) {
|
||||
c.Header("Cache-Control", "public, max-age=60")
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/custom", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, "public, max-age=60", w.Header().Get("Cache-Control"))
|
||||
}
|
||||
40
backend/internal/middleware/head_middleware.go
Normal file
40
backend/internal/middleware/head_middleware.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IsHeadRequestCtxKey struct{}
|
||||
|
||||
type headWriter struct {
|
||||
gin.ResponseWriter
|
||||
size int
|
||||
}
|
||||
|
||||
func (w *headWriter) Write(b []byte) (int, error) {
|
||||
w.size += len(b)
|
||||
return w.size, nil
|
||||
}
|
||||
|
||||
func HeadMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Only process if it's a HEAD request
|
||||
if c.Request.Context().Value(IsHeadRequestCtxKey{}) != true {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Replace the ResponseWriter with our headWriter to swallow the body
|
||||
hw := &headWriter{ResponseWriter: c.Writer}
|
||||
c.Writer = hw
|
||||
|
||||
c.Next()
|
||||
|
||||
c.Writer.Header().Set("Content-Length", strconv.Itoa(hw.size))
|
||||
c.Request.Method = http.MethodHead
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user