food-market/docs/quick-login-progress.md
nns e1718ec972
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
feat(s30): sentinel pwd для SPA — реальный мастер-пароль только в server-env
Координатор отротировал мастер-пароль стенда: теперь значение живёт в
~/.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>
2026-10-04 21:02:36 +05:00

11 KiB
Raw Blame History

Quick Login — прогресс

Задача владельца: на test.admin.food-market.kz не набирать логин/пароль руками. Выбрать аккаунт из списка — ты внутри в один клик. У всех аккаунтов один shared-пароль; его значение хранится в ~/.fm-watchdog/stage-shared- password на dev-vm (не коммитится). На admin.food-market.kz быстрого входа нет никогда (несколько независимых предохранителей).

Координатор отдельно проверил: на стейдже нет реальных секретов (МойСклад токенов нет, ОФД-ключей нет, Telegram не привязан, SMTP пустой) — потому открытый мастер-пароль на стенде безопасен.

Чек-лист — все 9/9 ✓

  • 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.
  • 2. Мастер-пароль — password-grant: QuickLogin:MasterPassword работает как второй вход рядом со своим паролем. Неактивные/без-org пользователи — отшиваются как раньше. Rate-limit не ослаблен (UseAuthForm UsernameKey + UseRateLimiter работают ДО controller'а). Commit: 7647af7.
  • 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.
  • 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.
  • 5. Страница входа (web) — QuickLoginBlock над формой логина. Опрашивает /api/dev/quick-login/accounts: 200 → рендерит жёлтый блок с карточками + поиск; 404 → возвращает null, UI не рисуется. Клик по карточке → моментальный login через мастер-пароль. Один web-билд ходит и в прод и на стенд. Commit: 6582333.
  • 6. Плашка «ТЕСТОВЫЙ СТЕНД» — <StageBanner /> вверху всех страниц при 200 на /accounts. Решение кешируется в sessionStorage. Динамически добавляет <meta name="robots" content="noindex,nofollow">. Commit: 5728b72.
  • 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.
  • 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.
  • 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. ✓

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

# на 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.