Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Координатор отротировал мастер-пароль стенда: теперь значение живёт в ~/.fm-watchdog/stage-shared-password на dev-vm (не коммитится) и в QUICK_LOGIN_MASTER_PASSWORD в stage-env на prod-vm. Старый дефолт '123456' на обоих сторонах убран — он мог вводить в заблуждение и был записан в бандл. Клиент-серверный контракт: - Server (AuthorizationController.IsMasterPassword) принимает ДВА пароля как мастер-ключ: (a) QuickLoginOptions.MasterPassword (константное сравнение, FixedTimeEquals); (b) публичный sentinel QuickLoginOptions.SentinelPassword = '__QUICK_LOGIN_PWLESS__'. Оба гейтованы EffectivelyEnabled — вне стенда отбиваются как любой чужой пароль. - SPA (LoginPage.handleQuickLogin) при клике карточки шлёт sentinel в поле password. Реальный пароль в JS-бандл больше не попадает; ротация секрета больше не требует rebuild'а веба. - Форму мы НЕ наполняем sentinel'ом — поле password остаётся пустым, чтобы ни одно значение не светилось в DOM. Default QuickLoginOptions.MasterPassword = "" (пустая строка не матчится в IsMasterPassword). deploy/docker-compose.yml дефолт QUICK_LOGIN_MASTER_PASSWORD тоже пустой. Тесты: - integration (3/3 ok) — добавлены sentinel-кейсы: пропускает активного при Enabled=true, отвергается при Enabled=false. Admin12345!-санити- логин в Disabled-тесте перенесён вперёд, чтобы снять lockout-счётчик от соседей по коллекции (Identity lockoutOnFailure=true). - e2e stage-quick-login (4/4 ok) — ql02 шлёт sentinel вместо '123456' (симулирует реальный SPA flow). docs/quick-login.md: - без упоминания реального пароля; хранилище — ~/.fm-watchdog/ stage-shared-password; ротация-рецепт через python читает файл в-процессе (не экспонирует значение в ps/логи). - раздел «Доступ к стенду» — host-nginx basic-auth для внешнего интернета, 192.168.1.x бьёт без неё. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
169 lines
11 KiB
Markdown
169 lines
11 KiB
Markdown
# Quick Login — прогресс
|
||
|
||
Задача владельца: на `test.admin.food-market.kz` не набирать логин/пароль
|
||
руками. Выбрать аккаунт из списка — ты внутри в один клик. У всех аккаунтов
|
||
один shared-пароль; его значение хранится в `~/.fm-watchdog/stage-shared-
|
||
password` на dev-vm (не коммитится). На `admin.food-market.kz` быстрого
|
||
входа нет никогда (несколько независимых предохранителей).
|
||
|
||
Координатор отдельно проверил: на стейдже нет реальных секретов (МойСклад
|
||
токенов нет, ОФД-ключей нет, Telegram не привязан, SMTP пустой) — потому
|
||
открытый мастер-пароль на стенде безопасен.
|
||
|
||
## Чек-лист — все 9/9 ✓
|
||
|
||
- [x] 1. **Флаг конфигурации** — `QuickLogin:Enabled` (default false) +
|
||
`QuickLogin:MasterPassword` (default пустой; реальное значение — только
|
||
в stage-env, читается из `~/.fm-watchdog/stage-shared-password`).
|
||
Включается только через env-переменные `QuickLogin__Enabled=true` в
|
||
stage-compose. Жёсткий предохранитель: если `OpenIddict:Issuer`
|
||
указывает на admin.food-market.kz — быстрый вход принудительно
|
||
выключен и в лог Warning.
|
||
Commit: `1f70d90`.
|
||
- [x] 2. **Мастер-пароль** — password-grant: `QuickLogin:MasterPassword`
|
||
работает как второй вход рядом со своим паролем. Неактивные/без-org
|
||
пользователи — отшиваются как раньше. Rate-limit не ослаблен (`UseAuthForm
|
||
UsernameKey` + `UseRateLimiter` работают ДО controller'а). Commit: `7647af7`.
|
||
- [x] 3. **API списка аккаунтов** — `GET /api/dev/quick-login/accounts`
|
||
(анонимный, 404 если выключен). Порядок: SuperAdmin → демо-* → остальные
|
||
«человеческие». Autotest-патéрны (cat31-*, visual-*, factory-smoke-*, mt6*,
|
||
quality-*, i18n*-*, auth2*-*, doc4*-*, rep5*-*, rt8*-*, twofa*-*, wiz9*-*,
|
||
a11y-*, mob1*-*, isoB*-*, axdet-*, smoke-*, ratetest-*, perf-*, probe-*,
|
||
load-*, s19-test-* и всё с суффиксом из ≥10 цифр) — скрыты. Лимит 50 + `?q=`.
|
||
Commit: `ed5089b`.
|
||
- [x] 4. **Демо-аккаунты** — идемпотентный сид «Демо-магазин» + сотрудники с
|
||
ролями Admin / Cashier / Storekeeper (`demo-admin@food-market.local`,
|
||
`demo-cashier@...`, `demo-keeper@...`). Запускается только при Enabled=true.
|
||
Catalog-step (DemoTenantSeeder) падает с «Tenant not set» в startup-режиме
|
||
без HTTP-контекста — изолирован в try/catch в отдельном scope, не блокирует
|
||
создание юзеров. Commits: `35f633e`, `e3a8db3`, `f817abc`.
|
||
- [x] 5. **Страница входа (web)** — `QuickLoginBlock` над формой логина.
|
||
Опрашивает `/api/dev/quick-login/accounts`: 200 → рендерит жёлтый блок с
|
||
карточками + поиск; 404 → возвращает null, UI не рисуется. Клик по карточке →
|
||
моментальный login через мастер-пароль. Один web-билд ходит и в прод и на
|
||
стенд. Commit: `6582333`.
|
||
- [x] 6. **Плашка «ТЕСТОВЫЙ СТЕНД»** — `<StageBanner />` вверху всех страниц
|
||
при 200 на `/accounts`. Решение кешируется в sessionStorage. Динамически
|
||
добавляет `<meta name="robots" content="noindex,nofollow">`. Commit: `5728b72`.
|
||
- [x] 7. **Чистка автотестового мусора** — `HousekeepingJobs.PruneAutoTest
|
||
EntitiesAsync` — Hangfire-recurring (02:45 UTC), удаляет autotest orgs/
|
||
users старше 3 дней. `deploy/stage-prune-autotest.sql` — одноразовый
|
||
SQL-скрипт с той же логикой. Прогнал на stage: было **8653 users / 8634 orgs
|
||
→ стало 300 users / 305 orgs**. Protected: SuperAdmin, admin@demo-market.local,
|
||
market@email.com, demo-* trio, «Demo Market», «Демо-магазин». Commit: `c69114f`.
|
||
- [x] 8. **Тесты** — **integration 3/3 green** (QuickLoginTests: options
|
||
matrix, disabled→404+rejects, enabled→master pass активному / отшивает
|
||
неактивного + prod issuer выключает). **E2E stage-quick-login 4/4 green**
|
||
(api 3 + UI 1 через Playwright): /accounts → 200 + demo-trio, master-пароль
|
||
пускает demo-cashier, прод endpoint НЕ отдаёт список, UI клик по карточке →
|
||
редирект с /login без ссылки на /products. Commit: `338877e`.
|
||
- [x] 9. **Деплой + документация** — stage compose + .env обновлены
|
||
(`QUICK_LOGIN_ENABLED=true`), `~/deploy-stage.sh` отработал, стенд живой,
|
||
endpoint отдаёт SuperAdmin + 3 демо + 1 Admin'а org'и `market1`.
|
||
`docs/quick-login.md` описывает как работает, предохранители, как выключить.
|
||
Commits: `ee24a5e` (fix прод-host), `e3a8db3`+`f817abc` (safety wrap +
|
||
scope-split).
|
||
|
||
## Финальная проверка — что живо
|
||
|
||
```
|
||
curl https://test.admin.food-market.kz/api/dev/quick-login/accounts
|
||
```
|
||
|
||
Отдаёт 5 аккаунтов:
|
||
```
|
||
admin@food-market.local role=SuperAdmin org=Платформа super=true
|
||
demo-admin@food-market.local role=Администратор org=Демо-магазин demo=true
|
||
demo-cashier@food-market.local role=Кассир org=Демо-магазин demo=true
|
||
demo-keeper@food-market.local role=Кладовщик org=Демо-магазин demo=true
|
||
market@email.com role=Администратор org=market1
|
||
```
|
||
|
||
```
|
||
# SPA-поток: шлём sentinel вместо реального пароля
|
||
curl -X POST .../connect/token password=__QUICK_LOGIN_PWLESS__ username=demo-cashier@...
|
||
→ 200 + access_token; /api/me → role=Cashier
|
||
|
||
# API-поток: шлём реальный пароль из файла (без эха значения)
|
||
curl -X POST .../connect/token \
|
||
--data-urlencode "password@/home/nns/.fm-watchdog/stage-shared-password" \
|
||
--data-urlencode "username=demo-cashier@food-market.local" ...
|
||
→ 200 + access_token
|
||
```
|
||
|
||
```
|
||
curl https://admin.food-market.kz/api/dev/quick-login/accounts
|
||
→ 502 (прод-backend остановлен ~2 мес.; главное — не 200, список не экспонируется)
|
||
```
|
||
|
||
**Прод-outage.** `food-market-api` container на prod-vm — `Exited (0) 2 months
|
||
ago`. Это _пре-существующее состояние_, не связано с моим деплоем. Для
|
||
quick-login это даже более безопасно, чем 404 (нечего отдать), но admin.food-
|
||
market.kz в целом нуждается в восстановлении — это отдельная задача, не
|
||
блокирует закрытие S30. Отмечено в E2E-отчёте как `gap`.
|
||
|
||
## Что в коде
|
||
|
||
| Что | Где |
|
||
|---|---|
|
||
| Опции + 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` |
|
||
| Чистка autotests | `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-тесты (3) | `tests/food-market.IntegrationTests/QuickLoginTests.cs` |
|
||
| E2E-сценарий (4 шага) | `tests/e2e/scenarios/stage-quick-login.{yml,steps.ts}` |
|
||
| Инструкция | `docs/quick-login.md` |
|
||
| Прод-compose шаблон | `deploy/docker-compose.yml` (env default off) |
|
||
|
||
## Коммиты в main (хронологически)
|
||
|
||
```
|
||
1f70d90 feat(s30): QuickLoginOptions с safety-override по Issuer (1/9)
|
||
7647af7 feat(s30): мастер-пароль в /connect/token при EffectivelyEnabled (2/9)
|
||
ed5089b feat(s30): /api/dev/quick-login/accounts — анонимный список (3/9)
|
||
35f633e feat(s30): QuickLoginDemoSeeder — организация + Admin/Cashier/Keeper (4/9)
|
||
6582333 feat(s30): QuickLoginBlock + интеграция в LoginPage (5/9)
|
||
5728b72 feat(s30): плашка ТЕСТОВЫЙ СТЕНД + robots noindex (6/9)
|
||
c69114f feat(s30): PruneAutoTestEntities + одноразовая зачистка стейджа (7/9)
|
||
338877e test(s30): integration 3/3 + e2e stage-quick-login scenario (8/9)
|
||
ee24a5e fix(s30): прод-хост в QuickLoginOptions — точное совпадение, не suffix (9/9)
|
||
e3a8db3 fix(s30): QuickLoginDemoSeeder больше не кладёт API при сбое
|
||
f817abc fix(s30): QuickLoginDemoSeeder — свежий scope на каждый sub-step
|
||
```
|
||
|
||
## Правила (сам себе) — все соблюдены
|
||
|
||
- Мастер-пароль и список аккаунтов — **никогда не на проде**. Три слоя защиты:
|
||
(а) `QuickLogin:Enabled=false` по умолчанию; (б) `EffectivelyEnabled`
|
||
принудительно выключает при `Issuer=admin.food-market.kz`; (в) пустой/
|
||
мусорный Issuer — выключен.
|
||
- После каждого пункта: build + тесты + коммит порцией + `[x]`. ✓
|
||
- Не трогал: прод admin.food-market.kz (только read-only), `global.json`,
|
||
POS WPF, gateway nginx. ✓
|
||
|
||
## Как выключить обратно
|
||
|
||
```bash
|
||
# на prod-vm
|
||
sed -i '/^QUICK_LOGIN_ENABLED=/d; /^QUICK_LOGIN_MASTER_PASSWORD=/d' \
|
||
~/food-market-stage/deploy/.env
|
||
cd ~/food-market-stage/deploy && docker compose -p food-market-stage up -d --force-recreate api
|
||
```
|
||
|
||
После рестарта `EffectivelyEnabled=false`, endpoint снова 404, master-password
|
||
и sentinel не работают, UI-блок исчезает. См. `docs/quick-login.md` для деталей.
|
||
|
||
## История
|
||
|
||
- **2026-10-04 (Sprint 30, часть 1):** первоначальный ввод на тестовый стенд.
|
||
- **2026-10-04 (часть 2, координатор):** реальный пароль вынесен в
|
||
`~/.fm-watchdog/stage-shared-password` (не коммитится); SPA перешла на
|
||
публичный sentinel `__QUICK_LOGIN_PWLESS__` → мастер-пароль больше не
|
||
попадает в JS-бандл; `test.admin.food-market.kz` для внешнего интернета
|
||
закрыт basic-auth (пароль = тот же), внутренняя сеть `192.168.1.x`
|
||
бьёт без basic-auth. Default `MasterPassword=""`, sentinel-поведение
|
||
в `AuthorizationController.IsMasterPassword`.
|