From ba541552253016fc66fe552bac6c7f958b3e0fe5 Mon Sep 17 00:00:00 2001 From: nns Date: Thu, 4 Jun 2026 01:36:42 +0500 Subject: [PATCH] =?UTF-8?q?fix(stage):=20rate-limit=205/min=20=D0=BD=D0=B0?= =?UTF-8?q?=20/connect/token,=20nginx=20route=20/metrics+/swagger,=20Swagg?= =?UTF-8?q?er=20=D0=B2=20Production=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Inc?= =?UTF-8?q?ludeSwagger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify-Sprint баги A-D: - A: на stage docker-compose.yml был "RateLimiting__PerMinute=200" — убран, теперь работают дефолты (5/мин, 20/час). 6-я попытка с тем же IP/паролем → 429. - B: web-контейнер nginx не имел location = /metrics → запрос ловился SPA fallback'ом (index.html, 947 байт). Добавлен proxy_pass на api:8080. - C: web-nginx не имел location /swagger/ → swagger.json возвращал SPA HTML. Добавлены /swagger/ + редирект /swagger → /swagger/. - D: Swagger подключался только в Development. Добавлен флаг IncludeSwagger (env IncludeSwagger=true) — Program.cs включает UseSwagger() и в Production если флаг выставлен. На prod admin.food-market.kz флаг не ставим. Проверено через https://test.admin.food-market.kz: - 6 неверных логинов подряд: 1-5 → 400, 6-7 → 429 ✓ - /metrics → 14967 байт prometheus exposition ✓ - /swagger/v1/swagger.json → 422 КБ openapi 3.0.1 ✓ - /swagger/ → swagger-ui (redirect на /swagger/index.html) ✓ Co-Authored-By: Claude Opus 4.7 --- deploy/nginx.conf | 26 +++++++++++++++++++++ docs/verify-progress.md | 42 ++++++++++++++++++++++++++++++++++ src/food-market.api/Program.cs | 10 ++++---- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 docs/verify-progress.md diff --git a/deploy/nginx.conf b/deploy/nginx.conf index aeae811..61f14a5 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -59,6 +59,32 @@ server { proxy_pass http://api:8080; } + # Prometheus метрики API. Без этого блока запрос ловится SPA fallback'ом и + # возвращает index.html (947 байт) вместо exposition format. На prod-домене + # имеет смысл закрыть IP-фильтром (allow 192.168.0.0/16; deny all;), на + # stage оставляем открытым — за gateway nginx уже есть auth/TLS-обвязка. + location = /metrics { + proxy_pass http://api:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Swagger UI + OpenAPI-doc. На контейнере api подключается только когда + # IncludeSwagger=true (env-флаг, см. Program.cs). На prod-домене флаг не + # выставляем, /swagger вернёт 404 от api — это ожидаемо. + location /swagger/ { + proxy_pass http://api:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location = /swagger { + return 301 /swagger/; + } + # Статика изображений товаров — api раздаёт /uploads/... из volume. location /uploads/ { proxy_pass http://api:8080; diff --git a/docs/verify-progress.md b/docs/verify-progress.md new file mode 100644 index 0000000..59cd563 --- /dev/null +++ b/docs/verify-progress.md @@ -0,0 +1,42 @@ +# Верификационный спринт — независимая проверка через домен + +Цель: расхождения между «отчёт говорит [x]» и «реально работает на https://test.admin.food-market.kz». + +Старт: 2026-06-04. Исполнитель: Claude Opus 4.7 (автономный режим). + +## Правила + +- Всё через **https://test.admin.food-market.kz**, не localhost. +- Каждая проверка либо `[x] подтверждено + работает`, либо `[x] подтверждено + баг найден + починен + retest зелёный`. +- Когда баг: reproduce → fix → build + тесты → `~/deploy-stage.sh` → retest → коммит порцией. +- НЕ трогать: global.json, прод admin.food-market.kz, POS WPF. + +## Предварительные баги (найдены пользователем) + +- [ ] **A. Rate-limit на /connect/token не срабатывает** — 7 неверных логинов → все 400, ни одного 429. И через nginx, и напрямую api:8085. Найти/починить middleware/конфиг. +- [ ] **B. /metrics через nginx-домен отдаёт SPA HTML** — 947 байт SPA вместо prometheus. Прямой api работает. Routing. +- [ ] **C. /swagger/v1/swagger.json через nginx-домен** отдаёт SPA. Аналогично. +- [ ] **D. Swagger вообще не подключён на api в Production** — прямой api:8085 /swagger → 404. Включить для Stage. + +## Верификация фич (через домен) + +- [ ] 1. Каталог CRUD через UI (товар + картинка + дубликат). +- [ ] 2. Складские документы (Supply/Enter/Loss/Transfer/Inventory/SupplierReturn/Demand) через UI с проверкой Stock. +- [ ] 3. RetailSale + CustomerReturn через UI. +- [ ] 4. Отчёты Sales/Stock/Profit/ABC через UI с проверкой чисел и экспорта. +- [ ] 5. 2FA TOTP через UI (enroll → verify → двухшаговый login → disable). +- [ ] 6. Permission-based authz через API (custom role без ProductsEdit → 403). +- [ ] 7. OrgAuditLog через UI (3 действия + multi-tenant изоляция). +- [ ] 8. SignalR real-time (2 вкладки, продажа в одной → toast/виджет в другой). +- [ ] 9. Локализация ru/en (топ-5 страниц). +- [ ] 10. Loyalty + Promotions через UI (карта + промокод в чеке). +- [ ] 11. PWA (Lighthouse PWA ≥80, offline дашборд + 3 отчёта). +- [ ] 12. Mobile 375x667 через Playwright. +- [ ] 13. Inventory CSV-импорт через UI. +- [ ] 14. POS Sync API с idempotency-key. +- [ ] 15. Stock-инвариант под конкуренцией (5 параллельных POST, остаток=3). +- [ ] 16. Email-шаблоны через smtp4dev. +- [ ] 17. Telegram-бот (или пометить «требует юзера»). +- [ ] 18. MinIO storage (картинка в bucket). + +## Журнал diff --git a/src/food-market.api/Program.cs b/src/food-market.api/Program.cs index c2434c7..4f2d5ce 100644 --- a/src/food-market.api/Program.cs +++ b/src/food-market.api/Program.cs @@ -383,11 +383,13 @@ [new Microsoft.OpenApi.Models.OpenApiSecurityScheme RequestPath = "/uploads", }); - if (app.Environment.IsDevelopment()) + // Swagger/OpenAPI: всегда в Development, в Production — только при IncludeSwagger=true + // (используется на stage для дебага без отдельного билда). На prod admin.food-market.kz + // флаг не выставляем — sensitive endpoint enumeration. + var includeSwagger = app.Environment.IsDevelopment() + || builder.Configuration.GetValue("IncludeSwagger"); + if (includeSwagger) { - // Swagger/OpenAPI: только в Development. /swagger/v1/swagger.json — JSON-документ, - // /swagger — UI. На prod не раскрываем (sensitive endpoint enumeration), на dev - // используется фронтом для генерации TS-клиента (`pnpm run gen:api`). app.UseSwagger(); app.UseSwaggerUI(opts => {