From e8e7371f09308a9c09a35916dd7c79fd500f2200 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sat, 4 Jul 2026 20:36:50 +0700 Subject: [PATCH] fix: drain Identity error body (keep-alive); reject whitespace-only credential fields in form Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/provider/selectel/selectel.go | 3 +++ web/src/pages/AccountsPage.test.tsx | 20 ++++++++++++++++++++ web/src/pages/AccountsPage.tsx | 6 +++--- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/internal/provider/selectel/selectel.go b/internal/provider/selectel/selectel.go index 6add0e6..cf90465 100644 --- a/internal/provider/selectel/selectel.go +++ b/internal/provider/selectel/selectel.go @@ -118,6 +118,9 @@ func (c *Client) authenticate(ctx context.Context, cr Creds) (string, time.Time, } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { + // Drain the body so the underlying connection can be reused by the + // transport's keep-alive pool; the error itself stays generic. + io.Copy(io.Discard, resp.Body) return "", time.Time{}, fmt.Errorf("selectel: identity auth failed: %d", resp.StatusCode) } tok := resp.Header.Get("X-Subject-Token") diff --git a/web/src/pages/AccountsPage.test.tsx b/web/src/pages/AccountsPage.test.tsx index 8943130..39003a1 100644 --- a/web/src/pages/AccountsPage.test.tsx +++ b/web/src/pages/AccountsPage.test.tsx @@ -60,6 +60,26 @@ test("сабмит с пустыми полями показывает zod-ош expect(createSpy).not.toHaveBeenCalled() }) +test("сабмит с пробельным вводом в обязательных полях показывает zod-ошибки и не вызывает createAccount", async () => { + const createSpy = vi.spyOn(api, "createAccount") + const user = userEvent.setup() + renderPage() + + await screen.findByText("Main") + + await user.type(screen.getByLabelText(/имя сервисного пользователя/i), " ") + await user.type(screen.getByLabelText(/пароль/i), "some-password") + await user.type(screen.getByLabelText(/номер аккаунта/i), " ") + await user.type(screen.getByLabelText(/имя проекта/i), " ") + + await user.click(screen.getByRole("button", { name: /добавить учётку/i })) + + expect(await screen.findByText("Укажите имя сервисного пользователя")).toBeInTheDocument() + expect(screen.getByText("Укажите номер аккаунта")).toBeInTheDocument() + expect(screen.getByText("Укажите имя проекта")).toBeInTheDocument() + expect(createSpy).not.toHaveBeenCalled() +}) + test("заполнение 4 полей и сабмит вызывает createAccount с secret в snake_case и не показывает пароль после", async () => { const createSpy = vi.spyOn(api, "createAccount").mockResolvedValue({ id: "acc3", diff --git a/web/src/pages/AccountsPage.tsx b/web/src/pages/AccountsPage.tsx index b782bcd..fb0542c 100644 --- a/web/src/pages/AccountsPage.tsx +++ b/web/src/pages/AccountsPage.tsx @@ -26,10 +26,10 @@ import { useAccounts, useCreateAccount, useDeleteAccount } from "@/hooks/useApi" const createAccountSchema = z.object({ provider: z.literal("selectel"), - username: z.string().min(1, "Укажите имя сервисного пользователя"), + username: z.string().trim().min(1, "Укажите имя сервисного пользователя"), password: z.string().min(1, "Укажите пароль"), - accountId: z.string().min(1, "Укажите номер аккаунта"), - projectName: z.string().min(1, "Укажите имя проекта"), + accountId: z.string().trim().min(1, "Укажите номер аккаунта"), + projectName: z.string().trim().min(1, "Укажите имя проекта"), comment: z.string(), })