diff --git a/server/admin/internal/modules/externaladmin/global_username_test.go b/server/admin/internal/modules/externaladmin/global_username_test.go index 09d3b04a..7b611f6e 100644 --- a/server/admin/internal/modules/externaladmin/global_username_test.go +++ b/server/admin/internal/modules/externaladmin/global_username_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" + adminmiddleware "hyapp-admin-server/internal/middleware" + "github.com/DATA-DOG/go-sqlmock" "github.com/gin-gonic/gin" "gorm.io/driver/mysql" @@ -76,6 +78,9 @@ func TestLoginIgnoresLegacyRequestAppAndAuditsUnknownAccountWithoutTenant(t *tes gin.SetMode(gin.TestMode) engine := gin.New() + // Production installs the global App middleware before external routes. Login + // must remove its fallback "lalu" response header until credentials resolve. + engine.Use(adminmiddleware.AppCode()) handler := New(adminDB, nil, Config{}, nil, WithLoginRateLimiter(allowFixedWindowLimiter{})) RegisterExternalRoutes(engine.Group("/api/v1"), handler, BusinessHandlers{}) request := httptest.NewRequest(http.MethodPost, "/api/v1/external/auth/login", strings.NewReader(`{"appCode":"lalu","account":"missing-operator","password":"wrong-password"}`)) @@ -86,6 +91,9 @@ func TestLoginIgnoresLegacyRequestAppAndAuditsUnknownAccountWithoutTenant(t *tes if responseRecorder.Code != http.StatusUnauthorized { t.Fatalf("status=%d body=%s", responseRecorder.Code, responseRecorder.Body.String()) } + if got := responseRecorder.Header().Get("X-App-Code"); got != "" { + t.Fatalf("failed tenant-less login exposed default App header %q", got) + } if !strings.Contains(responseRecorder.Body.String(), `"code":40100`) || strings.Contains(responseRecorder.Body.String(), "App") { t.Fatalf("login response must expose only stable auth code and generic text: %s", responseRecorder.Body.String()) } diff --git a/server/admin/internal/modules/externaladmin/handler.go b/server/admin/internal/modules/externaladmin/handler.go index 700c9c79..b9ffe03b 100644 --- a/server/admin/internal/modules/externaladmin/handler.go +++ b/server/admin/internal/modules/externaladmin/handler.go @@ -233,6 +233,10 @@ func (h *Handler) ListApps(c *gin.Context) { } func (h *Handler) Login(c *gin.Context) { + // The global App middleware supplies the main-admin default before this anonymous + // route runs. No tenant is authoritative until a credential row resolves, so do + // not leak or imply that default on malformed, throttled or failed login responses. + c.Writer.Header().Del(appctx.HeaderAppCode) // Limit parsing memory before decoding untrusted credentials. Binding tags are // intentionally absent on loginRequest: every syntactically valid JSON attempt, // including missing fields, must reach the distributed limiter before rejection.