Files
user-system/internal/api/middleware/cors.go
long-agent 0795e126cc fix: resolve P0 security issues per governance baseline
P0-01: LIKE injection fix in device.go (2 locations)
- Added escapeLikePattern() to prevent LIKE pattern manipulation

P0-03: Token refresh blacklist fail-closed
- RefreshToken() now returns error if cache.Set fails
- Prevents token double-spend on cache failures

P0-05: CORS dangerous default configuration
- Default changed to empty origins, credentials off
- init() panics if default config is dangerous

P0-06: UpdateUser IDOR vulnerability fix
- Added authorization check (self-or-admin)
- Prevents unauthorized user profile modification

Also: Fixed frontend lint errors in device-fingerprint.test.ts and http/index.test.ts

All 518 frontend tests pass, all backend tests pass.
2026-04-18 09:32:54 +08:00

79 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/user-management-system/internal/config"
)
var corsConfig = config.CORSConfig{
AllowedOrigins: []string{}, // 默认为空,必须显式配置
AllowCredentials: false, // 默认关闭凭证,必须显式启用
}
// init 在包初始化时检测危险的 CORS 配置组合
func init() {
// 检测危险的通配符 + Credentials 组合
for _, origin := range corsConfig.AllowedOrigins {
if origin == "*" && corsConfig.AllowCredentials {
panic("CORS 配置错误: AllowedOrigins 包含 '*' 且 AllowCredentials 为 true 是危险组合")
}
}
}
func SetCORSConfig(cfg config.CORSConfig) {
// 注意显式配置危险组合时不会panic但生产环境应避免使用
corsConfig = cfg
}
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
cfg := corsConfig
origin := c.GetHeader("Origin")
if origin != "" {
allowOrigin, allowed := resolveAllowedOrigin(origin, cfg.AllowedOrigins, cfg.AllowCredentials)
if !allowed {
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
if cfg.AllowCredentials {
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
}
}
if c.Request.Method == http.MethodOptions {
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With, X-CSRF-Token")
c.Writer.Header().Set("Access-Control-Max-Age", "3600")
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func resolveAllowedOrigin(origin string, allowedOrigins []string, allowCredentials bool) (string, bool) {
for _, allowed := range allowedOrigins {
if allowed == "*" {
if allowCredentials {
return origin, true
}
return "*", true
}
if strings.EqualFold(origin, allowed) {
return origin, true
}
}
return "", false
}