//go:build unit package middleware import ( "net/http/httptest" "testing" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) // 反代场景:RemoteAddr 为 127.0.0.1,真实客户端 IP 在 X-Real-IP 中。 // 会话绑定注入与审计 IP 必须与 API Key IP 限制共用「信任反代传递的客户端 IP」开关语义。 func TestSessionBindingContextHonorsTrustForwardedToggle(t *testing.T) { gin.SetMode(gin.TestMode) for _, tc := range []struct { name string trustForwarded bool wantIP string }{ {name: "trust disabled records proxy address", trustForwarded: false, wantIP: "127.0.0.1"}, {name: "trust enabled records forwarded client IP", trustForwarded: true, wantIP: "1.2.3.4"}, } { t.Run(tc.name, func(t *testing.T) { cfg := &config.Config{} cfg.SetTrustForwardedIPForAPIKeyACL(tc.trustForwarded) r := gin.New() require.NoError(t, r.SetTrustedProxies(nil)) r.Use(SessionBindingContext(cfg)) r.GET("/t", func(c *gin.Context) { binding := service.SessionBindingFromContext(c.Request.Context()) require.NotNil(t, binding) require.Equal(t, tc.wantIP, binding.IP) require.Equal(t, "test-agent", binding.UserAgent) require.Equal(t, tc.wantIP, SecurityClientIP(c)) c.Status(200) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/t", nil) req.RemoteAddr = "127.0.0.1:54321" req.Header.Set("X-Real-IP", "1.2.3.4") req.Header.Set("User-Agent", "test-agent") r.ServeHTTP(w, req) require.Equal(t, 200, w.Code) }) } } // 未经过 SessionBindingContext 注入时(异常挂载顺序/单测直调),回退 trusted_proxies 链, // 等价于开关关闭时的历史行为。 func TestSecurityClientIPFallsBackWithoutInjectedBinding(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() require.NoError(t, r.SetTrustedProxies(nil)) r.GET("/t", func(c *gin.Context) { c.String(200, SecurityClientIP(c)) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/t", nil) req.RemoteAddr = "9.9.9.9:12345" req.Header.Set("X-Real-IP", "1.2.3.4") r.ServeHTTP(w, req) require.Equal(t, 200, w.Code) require.Equal(t, "9.9.9.9", w.Body.String()) } // requestSessionBinding 优先取注入值:开关开启时校验哈希必须基于注入的转发 IP 计算, // 与 token 签发路径取值一致,否则同一客户端会被误判为指纹变化。 func TestRequestSessionBindingPrefersInjectedBinding(t *testing.T) { gin.SetMode(gin.TestMode) cfg := &config.Config{} cfg.SetTrustForwardedIPForAPIKeyACL(true) r := gin.New() require.NoError(t, r.SetTrustedProxies(nil)) r.Use(SessionBindingContext(cfg)) r.GET("/t", func(c *gin.Context) { issued := &service.SessionBinding{IP: "1.2.3.4", UserAgent: "test-agent"} require.Equal(t, issued.Hash(), requestSessionBinding(c).Hash()) c.Status(200) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/t", nil) req.RemoteAddr = "127.0.0.1:54321" req.Header.Set("X-Real-IP", "1.2.3.4") req.Header.Set("User-Agent", "test-agent") r.ServeHTTP(w, req) require.Equal(t, 200, w.Code) }