package router import ( "github.com/atlasos/calypso/internal/common/config" "github.com/gin-gonic/gin" ) // securityHeadersMiddleware adds security headers to responses func securityHeadersMiddleware(cfg *config.Config) gin.HandlerFunc { if !cfg.Security.SecurityHeaders.Enabled { return func(c *gin.Context) { c.Next() } } return func(c *gin.Context) { // Prevent clickjacking c.Header("X-Frame-Options", "DENY") // Prevent MIME type sniffing c.Header("X-Content-Type-Options", "nosniff") // Enable XSS protection c.Header("X-XSS-Protection", "1; mode=block") // Strict Transport Security (HSTS) - only if using HTTPS // c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains") // Content Security Policy (basic) c.Header("Content-Security-Policy", "default-src 'self'") // Referrer Policy c.Header("Referrer-Policy", "strict-origin-when-cross-origin") // Permissions Policy c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()") c.Next() } } // corsMiddleware creates configurable CORS middleware func corsMiddleware(cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { origin := c.Request.Header.Get("Origin") // Check if origin is allowed allowed := false for _, allowedOrigin := range cfg.Security.CORS.AllowedOrigins { if allowedOrigin == "*" || allowedOrigin == origin { allowed = true break } } if allowed { c.Writer.Header().Set("Access-Control-Allow-Origin", origin) } if cfg.Security.CORS.AllowCredentials { c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") } // Set allowed methods methods := cfg.Security.CORS.AllowedMethods if len(methods) == 0 { methods = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"} } c.Writer.Header().Set("Access-Control-Allow-Methods", joinStrings(methods, ", ")) // Set allowed headers headers := cfg.Security.CORS.AllowedHeaders if len(headers) == 0 { headers = []string{"Content-Type", "Authorization", "Accept", "Origin"} } c.Writer.Header().Set("Access-Control-Allow-Headers", joinStrings(headers, ", ")) // Handle preflight requests if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } // joinStrings joins a slice of strings with a separator func joinStrings(strs []string, sep string) string { if len(strs) == 0 { return "" } if len(strs) == 1 { return strs[0] } result := strs[0] for _, s := range strs[1:] { result += sep + s } return result }