# Quick Login — быстрый вход на тестовом стенде **Статус:** включено на `https://test.admin.food-market.kz` (стенд). **Прод:** `https://admin.food-market.kz` — выключено, попытки активации принудительно блокируются safety-override'ом в коде. Это dev-удобство: на стенде не нужно каждый раз вводить логин+пароль. Выбираешь карточку аккаунта на `/login`, клик — ты внутри. ## Как это работает ### Server-side Опции в `appsettings.json` / env: ```yaml 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) - `` над формой логина сам опрашивает `/api/dev/quick- login/accounts`. 200 → рендерит жёлтый блок с карточками + поиск. 404 → возвращает `null`, никакого UI не рисует. Клик по карточке → login с `MasterPassword`. - `` жёлтой полосой сверху всех страниц, когда эндпоинт доступен. При том же сигнале динамически добавляет `` — чтобы 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`. ## Как выключить обратно На стенде: ```bash # на 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`. ## Проверить что прод не задет ```bash 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):** первоначальный ввод на тестовый стенд.