mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
133 lines
3.3 KiB
Go
133 lines
3.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// RequestSizeLimit limits the size of request bodies
|
|
func RequestSizeLimit(maxSize int64) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Skip for upload endpoints (they have their own limits)
|
|
if strings.Contains(c.Request.URL.Path, "/upload") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxSize)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// SanitizeHeaders removes potentially dangerous headers
|
|
func SanitizeHeaders() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Remove server information leakage
|
|
c.Writer.Header().Del("Server")
|
|
c.Writer.Header().Del("X-Powered-By")
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// ValidateContentType ensures proper content type for POST/PUT/PATCH
|
|
func ValidateContentType() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "PATCH" {
|
|
contentType := c.GetHeader("Content-Type")
|
|
path := c.Request.URL.Path
|
|
|
|
// Allow multipart for uploads and image processing crop upload
|
|
if strings.Contains(path, "/upload") || strings.Contains(path, "/image-processing/crop-upload") || strings.Contains(path, "/admin/scoreboard/qr") || strings.Contains(path, "/admin/scoreboard/load") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Allow scoreboard timer control endpoints without requiring JSON body
|
|
// These actions do not read request body and are triggered via simple POSTs from remote UI
|
|
if strings.Contains(path, "/admin/scoreboard/timer/") {
|
|
c.Next()
|
|
return
|
|
}
|
|
if strings.Contains(path, "/admin/scoreboard/swap-sides") || strings.Contains(path, "/admin/scoreboard/second-half") {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Require JSON for other API endpoints
|
|
if !strings.Contains(contentType, "application/json") {
|
|
c.JSON(http.StatusUnsupportedMediaType, gin.H{
|
|
"error": "Content-Type must be application/json",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RequestID adds a unique request ID for tracing
|
|
func RequestID() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
requestID := c.GetHeader("X-Request-ID")
|
|
if requestID == "" {
|
|
requestID = generateRequestID()
|
|
}
|
|
|
|
c.Set("request_id", requestID)
|
|
c.Header("X-Request-ID", requestID)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func generateRequestID() string {
|
|
return uuid.New().String()
|
|
}
|
|
|
|
// GetRequestID retrieves the request ID from context
|
|
func GetRequestID(c *gin.Context) string {
|
|
if id, exists := c.Get("request_id"); exists {
|
|
if requestID, ok := id.(string); ok {
|
|
return requestID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SecurityAuditLog logs security-relevant events
|
|
type SecurityEvent struct {
|
|
Type string
|
|
UserID uint
|
|
IP string
|
|
Path string
|
|
Method string
|
|
RequestID string
|
|
Details map[string]interface{}
|
|
}
|
|
|
|
func LogSecurityEvent(c *gin.Context, eventType string, details map[string]interface{}) {
|
|
event := SecurityEvent{
|
|
Type: eventType,
|
|
IP: c.ClientIP(),
|
|
Path: c.Request.URL.Path,
|
|
Method: c.Request.Method,
|
|
RequestID: c.GetString("request_id"),
|
|
Details: details,
|
|
}
|
|
|
|
if userID, exists := c.Get("user_id"); exists {
|
|
if uid, ok := userID.(uint); ok {
|
|
event.UserID = uid
|
|
}
|
|
}
|
|
|
|
// Log to your logger
|
|
// logger.Warn("SECURITY_EVENT: type=%s user_id=%d ip=%s path=%s",
|
|
// event.Type, event.UserID, event.IP, event.Path)
|
|
}
|