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

146 lines
8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)
- `<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`.
## Как выключить обратно
На стенде:
```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):** первоначальный ввод на тестовый стенд.