Files
3x-ui/internal/web/controller/csrf_log_test.go
T
ericanannn-blip 3051aebb28 fix(middleware): log rejected CSRF requests with the trusted client address (#6692)
A panel or API request rejected for a missing or wrong CSRF token got a bare
403 and left nothing in the log, so an operator chasing failed logins from a
stale tab or a script had no trace. CSRFMiddleware now logs a warning with the
method, path and client address. The address comes from getRemoteIp, which
honours the trusted-proxy list, instead of gin's ClientIP, which trusts any
peer's X-Forwarded-For.
2026-10-24 04:51:21 +02:00

42 lines
1.1 KiB
Go

package controller
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
)
func TestLoginCSRFRejectionLogsPeerIPNotForwardedHeader(t *testing.T) {
gin.SetMode(gin.TestMode)
engine := gin.New()
engine.Use(sessions.Sessions("3x-ui", cookie.NewStore([]byte("csrf-log-test-secret"))))
NewIndexController(engine.Group("/"))
req := httptest.NewRequest(http.MethodPost, "/login", nil)
req.RemoteAddr = "198.51.100.9:40000"
req.Header.Set("X-Forwarded-For", "203.0.113.66")
rec := httptest.NewRecorder()
engine.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
var line string
for _, l := range logger.GetLogs(50, "WARNING") {
if strings.Contains(l, "CSRF validation failed") {
line = l
break
}
}
if !strings.Contains(line, "IP=198.51.100.9") || strings.Contains(line, "203.0.113.66") {
t.Fatalf("CSRF rejection log = %q, want the untrusted peer 198.51.100.9 and not the forwarded header", line)
}
}