food-market/docs/quick-login.md
nns ee24a5ed8e fix(s30): прод-хост в QuickLoginOptions — точное совпадение, не suffix (шаг 9/9)
Критический фикс: раньше safety-override срабатывал на host.EndsWith(
".admin.food-market.kz") — это ВКЛЮЧАЛО сам стейдж (test.admin.food-market.kz
заканчивается на ".admin.food-market.kz"). Деплой на стенд это и показал:
Warning в логе "test.admin.food-market.kz — это ПРОД" и /api/dev/quick-
login/accounts возвращал 404.

Теперь проверка: host == "admin.food-market.kz" (точное совпадение). Любой
поддомен (test.admin.*, dev.admin.*) — стенд, быстрый вход включается.
Если в будущем появится второй прод-хост — дописать условие явно.

Также добавлено:
- deploy/docker-compose.yml: пробрасывает QuickLogin__Enabled и
  QuickLogin__MasterPassword из env (default off).
- docs/quick-login.md: как работает, предохранители, как выключить, как
  проверить прод.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 16:22:18 +05:00

8 KiB
Raw Blame History

Quick Login — быстрый вход на тестовом стенде

Статус: включено на https://test.admin.food-market.kz (стенд).
Прод: https://admin.food-market.kz — выключено, попытки активации принудительно блокируются safety-override'ом в коде.

Это dev-удобство: на стенде не нужно каждый раз вводить логин+пароль. Выбираешь карточку аккаунта на /login, клик — ты внутри.

Как это работает

Server-side

Опции в appsettings.json / env:

QuickLogin:
  Enabled: false         # default — выключено
  MasterPassword: "123456"

Включается через env в stage-compose:

QUICK_LOGIN_ENABLED=true
QUICK_LOGIN_MASTER_PASSWORD=123456

Логика работает в двух местах:

  • POST /connect/token (grant=password) — при EffectivelyEnabled=true пароль MasterPassword принимается у ЛЮБОГО активного пользователя наравне с его собственным паролем. Константное сравнение (CryptographicOperations. FixedTimeEquals), чтобы не светить длину. Деактивированные (IsActive= false) не пускаются ни при каких условиях. /connect/token всё так же гонит через rate-limit, 2FA и org-check (архивная org отшивает).

  • GET /api/dev/quick-login/accounts — анонимный эндпоинт, который возвращает «человеческие» аккаунты для карточек: SuperAdmin → demo-trio → остальные. Автотестовые логины отсеиваются по regex-патéрнам (всё с суффиксом из 10+ цифр или prefix cat31-/visual-/factory-smoke-/mt6*/ quality-/...). Поиск ?q= фильтрует по email/ФИО/роли/магазину. На проде эндпоинт отдаёт 404 — наличие пути не палит окружение.

Client-side (SPA)

  • <QuickLoginBlock /> над формой логина сам опрашивает /api/dev/quick- login/accounts. 200 → рендерит жёлтый блок с карточками + поиск. 404 → возвращает null, никакого UI не рисует. Клик по карточке → login с MasterPassword.
  • <StageBanner /> жёлтой полосой сверху всех страниц, когда эндпоинт доступен. При том же сигнале динамически добавляет <meta name="robots" content="noindex,nofollow"> — чтобы Google не индексировал стенд.

Один web-билд используется и в проде, и на стенде — секрет на проде не утекает: /api/dev/quick-login/accounts там 404, значит блока нет.

Безопасность — предохранители

  1. Enabled=false по умолчанию. Нужна явная env-переменная, чтобы включить. В прод-compose её нет и не должно быть.
  2. Issuer safety-override. В QuickLoginOptions.EffectivelyEnabled: если OpenIddict:Issuer указывает на admin.food-market.kz (ТОЧНО, без поддоменов), быстрый вход принудительно выключен. Это защищает от ошибки конфигурации — поставили QUICK_LOGIN_ENABLED=true в прод-env по ошибке, но Issuer остался прод-овым → ничего не включилось, в лог Warning.
  3. Пустой или кривой Issuer тоже отбивает. Не рискуем в неизвестном окружении: если Issuer пустой/мусорный — считаем выключенным.
  4. Rate-limit не ослаблен. Master-password идёт через ту же защиту, что обычный password-grant (UseAuthFormUsernameKey + UseRateLimiter).
  5. Неактивные пользователи не пускаются. Проверка IsActive сверху /connect/token — master-password это не обходит.
  6. Startup Warning. Program.cs логирует одно решение на старте: «включён для стенда» или «Enabled=true, но безопасность выключила». В логах прод-инстанса всегда видно, что происходит.

Демо-аккаунты

QuickLoginDemoSeeder при старте с EffectivelyEnabled=true поднимает:

  • Организацию «Демо-магазин» (идемпотентно; отдельная от «Demo Market» из DevDataSeeder, чтобы не ломать e2e).
  • Полный demo-каталог через DemoTenantSeeder (50 товаров / 30 продаж / приёмки / контрагенты).
  • Трио учёток с ролями:
    • demo-admin@food-market.local → Admin
    • demo-cashier@food-market.local → Cashier
    • demo-keeper@food-market.local → Storekeeper

Собственный пароль у них Admin12345! (унаследовано от шаблона), но мастер-пароль 123456 тоже работает.

Чистка автотестового мусора

HousekeepingJobs.PruneAutoTestEntitiesAsync — Hangfire-recurring каждый день в 02:45 UTC. Удаляет автотестовые организации (regex-match по имени) и orphan-юзеры по email-паттерну старше 3 дней. Защищены: SuperAdmin, admin@demo-market.local, demo-*@food-market.local, market@email.com, «Demo Market» / «Демо-магазин».

Единоразовая зачистка той же логикой — deploy/stage-prune-autotest.sql.

Как выключить обратно

На стенде:

# на prod-vm
nns@192.168.1.190:$ sed -i '/^QUICK_LOGIN_ENABLED=/d; /^QUICK_LOGIN_MASTER_PASSWORD=/d' \
  ~/food-market-stage/deploy/.env
nns@192.168.1.190:$ cd ~/food-market-stage/deploy && docker compose -p food-market-stage up -d --force-recreate api

API перестартует, EffectivelyEnabled увидит Enabled=false, эндпоинт снова 404, master-password не работает. Карточки на /login исчезнут — QuickLoginBlock увидит 404 и вернёт null.

Проверить что прод не задет

curl -s -o /dev/null -w "%{http_code}\n" \
  https://admin.food-market.kz/api/dev/quick-login/accounts
# ожидается: 404

Любой другой код (200/403/500) — проблема.

Файлы

Что Где
Опции + safety-override src/food-market.api/Infrastructure/QuickLoginOptions.cs
Master-password в token-grant src/food-market.api/Controllers/AuthorizationController.cs
Список аккаунтов src/food-market.api/Controllers/QuickLoginController.cs
Демо-сид src/food-market.api/Seed/QuickLoginDemoSeeder.cs
Чистка автотестов src/food-market.api/Background/HousekeepingJobs.cs + deploy/stage-prune-autotest.sql
UI блок src/food-market.web/src/components/QuickLoginBlock.tsx
Плашка стенда src/food-market.web/src/components/StageBanner.tsx
Integration-тесты tests/food-market.IntegrationTests/QuickLoginTests.cs
E2E-сценарий tests/e2e/scenarios/stage-quick-login.{yml,steps.ts}

История

  • 2026-10-04 (Sprint 30): первоначальный ввод на тестовый стенд.