fix: harden login for MLPS requirements
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SecurityHeaders() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
h := c.Writer.Header()
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
h.Set("X-Permitted-Cross-Domain-Policies", "none")
|
||||
h.Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||
h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()")
|
||||
if isHTTPSRequest(c) {
|
||||
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
if isSensitivePath(c.Request.URL.Path) {
|
||||
h.Set("Cache-Control", "no-store")
|
||||
h.Set("Pragma", "no-cache")
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func isHTTPSRequest(c *gin.Context) bool {
|
||||
if c == nil || c.Request == nil {
|
||||
return false
|
||||
}
|
||||
if c.Request.TLS != nil {
|
||||
return true
|
||||
}
|
||||
return strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https")
|
||||
}
|
||||
|
||||
func isSensitivePath(path string) bool {
|
||||
switch strings.TrimSpace(path) {
|
||||
case "/api/auth/login",
|
||||
"/api/auth/refresh",
|
||||
"/api/auth/me",
|
||||
"/api/auth/logout",
|
||||
"/api/auth/password-key",
|
||||
"/api/ops/auth/login",
|
||||
"/api/ops/auth/me",
|
||||
"/api/ops/auth/password",
|
||||
"/api/ops/auth/password-key":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type CORSConfig struct {
|
||||
AllowedOrigins []string
|
||||
AllowAll bool
|
||||
}
|
||||
|
||||
func CORSWithConfig(cfg CORSConfig) gin.HandlerFunc {
|
||||
allowedOrigins := compactOrigins(cfg.AllowedOrigins)
|
||||
allowedSet := make(map[string]struct{}, len(allowedOrigins))
|
||||
for _, origin := range allowedOrigins {
|
||||
allowedSet[origin] = struct{}{}
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
origin := strings.TrimSpace(c.GetHeader("Origin"))
|
||||
allowedOrigin := ""
|
||||
switch {
|
||||
case cfg.AllowAll:
|
||||
allowedOrigin = "*"
|
||||
case origin != "":
|
||||
if _, ok := allowedSet[origin]; ok {
|
||||
allowedOrigin = origin
|
||||
}
|
||||
}
|
||||
|
||||
if allowedOrigin != "" {
|
||||
c.Header("Access-Control-Allow-Origin", allowedOrigin)
|
||||
if allowedOrigin != "*" {
|
||||
c.Header("Vary", "Origin")
|
||||
}
|
||||
}
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization, X-Request-ID, X-Internal-Metrics-Token")
|
||||
c.Header("Access-Control-Expose-Headers", "X-Request-ID")
|
||||
c.Header("Access-Control-Max-Age", "86400")
|
||||
|
||||
if c.Request.Method == http.MethodOptions {
|
||||
if origin != "" && allowedOrigin == "" {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func compactOrigins(values []string) []string {
|
||||
out := make([]string, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
out = append(out, value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user