food-market/docs/sso.md
nns 346b7bfd48
Some checks failed
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 (push) Has been cancelled
Docker Public / Deploy Public on stage (push) Has been cancelled
feat(s20): Mapster + SSO scaffold + maintenance automation (7 пунктов)
1. TD-3 Mapster — Application/Mapping/MapsterConfig.cs с
   TypeAdapterConfig для Product, Counterparty + collections.
   ProductsController.List/Get/GetInternalAsync + CounterpartiesController.
   List/Get переведены на .ProjectToType<TDto>(MapsterConfig.Config).
   Inline Projection-Expression удалён.

2. SSO scaffold — Microsoft.AspNetCore.Authentication.Google + .MicrosoftAccount
   пакеты, условная регистрация в Program.cs (только если ClientId задан).
   ExternalAuthController с GET /api/auth/external/{provider} (Challenge или
   503 если не настроено), /callback (501 с email — invite-flow TODO),
   /providers (булевый список). docs/sso.md инструкция.

3. Stale-data cleanup — HousekeepingJobs расширен:
   PruneOrgAuditLogAsync (>90д из Cleanup:OrgAuditLogDays),
   PruneDraftsAsync (Supply/RetailSale/Demand старше 30д),
   PruneRevokedRefreshTokensAsync (raw SQL DELETE из OpenIddictTokens).
   3 новых cron'a в HangfireJobsConfigurator (03:00-03:20 UTC).

4. DB VACUUM automation — DatabaseMaintenanceJobs.VacuumTopTablesAsync:
   pg_total_relation_size → топ-5 таблиц → VACUUM (ANALYZE) per table
   с замером времени. Default cron еженедельно вс 04:00 UTC.

5. Disk usage monitoring — DiskMonitoringJob ежечасно: DriveInfo.AvailableFreeSpace
   на пути из Monitoring:DiskPaths (default "/opt,/var/lib/docker").
   <1GB → Telegram-alert на Monitoring:SuperAdminTelegramChatIds.
   Anti-spam cooldown 6h. Gauge food_market_disk_free_bytes{mount}.

6. Performance regression detection — ~/nightly-perf-check.sh после
   nightly-verify. Парсит /metrics, считает db_avg_ms, сравнивает с
   baseline в ~/.fm-watchdog/perf-baseline.json. Δ>30% → Telegram alert
   + baseline НЕ обновляется (sliding window).

7. Public-site analytics placeholder — Astro BaseLayout рендерит
   gtag/Yandex.Metrika только если задан PUBLIC_GA_ID / PUBLIC_YM_ID;
   иначе <script data-id="REPLACE_ME" data-doc="docs/analytics.md">
   маркер. docs/analytics.md с инструкцией подключения.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 21:54:12 +05:00

5.7 KiB
Raw Permalink Blame History

SSO — Google и Microsoft

Sprint 20: добавлен скелет SSO. Сейчас можно перейти на consent screen у Google/Microsoft и получить email пользователя, но автоматического создания учётной записи нет — это требует invite-flow от администратора организации (см. multi-tenant ниже).

Как получить keys

Google

  1. Перейти в Google Cloud Console → APIs & Services → Credentials.
  2. Create credentials → OAuth client ID.
  3. Application type: Web application.
  4. Authorized redirect URIs — добавить:
    • https://admin.food-market.kz/signin-google (prod)
    • https://test.admin.food-market.kz/signin-google (stage)
    • http://localhost:5081/signin-google (dev)
  5. Скопировать Client ID и Client secret.
  6. Положить в appsettings.Production.json:
    {
      "Authentication": {
        "Google": {
          "ClientId": "...",
          "ClientSecret": "..."
        }
      }
    }
    

Microsoft

  1. Перейти в Azure Portal → App registrationsNew registration.
  2. Supported account types: «Accounts in any organizational directory and personal Microsoft accounts».
  3. Redirect URI (Web): добавить три URL аналогично Google (заменив signin-googlesignin-microsoft).
  4. После создания: Certificates & secrets → New client secret. Скопировать value.
  5. Overview → Application (client) ID.
  6. Конфиг:
    {
      "Authentication": {
        "Microsoft": {
          "ClientId": "...",
          "ClientSecret": "..."
        }
      }
    }
    

Как использовать

Без настроенных keys

GET /api/auth/external/google503 с подсказкой:

{
  "error": "SSO для Google не настроено.",
  "hint": "Добавьте в appsettings: Authentication:Google:ClientId и :ClientSecret. См. docs/sso.md."
}

GET /api/auth/external/providers → текущее состояние:

{ "google": false, "microsoft": false }

Web-фронт скрывает кнопки SSO когда оба провайдера = false.

С настроенными keys

  1. Пользователь жмёт «Войти через Google» → фронт делает редирект на GET /api/auth/external/google.
  2. Сервер возвращает 302 на Google consent screen.
  3. После consent — Google редиректит на /signin-google, который обрабатывает ASP.NET middleware и сохраняет identity во временный cookie fm.external.
  4. Middleware вызывает /api/auth/external/callback?provider=google.
  5. Sprint 20 scaffold: callback возвращает 501 с информацией:
    {
      "status": "scaffolded",
      "message": "SSO-callback получен, но автоматическая регистрация ещё не реализована.",
      "email": "user@example.com",
      "name": "John Doe",
      "next": "Попросите администратора организации пригласить вас..."
    }
    

Что осталось доделать (после v1)

  • Invite-flow: org-админ создаёт Employee запись с email'ом, после чего SSO-callback находит этот email и линкует SSO-identity к существующему User'у.
  • Выпуск OpenIddict access+refresh токенов после успешного линка (использовать тот же flow что и в AuthController.PasswordGrant).
  • Связь identity_provider+sub для повторных логинов (новая таблица external_logins (UserId, Provider, ProviderKey)).
  • UI: на /login рендерить кнопки «Войти через Google/Microsoft» только если /api/auth/external/providers вернул true для провайдера.
  • Конфликт email: если пользователь с этим email уже есть и привязан к другой org → отказ либо choice «выбрать org».

Multi-tenant специфика

SSO per-organization — реальный пользователь в системе всегда привязан к конкретной org через Employee. SSO-логин по email не определяет org однозначно (один email может работать в нескольких организациях через Employee-приглашения).

Поэтому invite-flow обязателен: SSO не создаёт User'а вслепую, а лишь верифицирует «вы — владелец этого email» и линкует к ранее приглашённому Employee.

Тестирование

Скелет тестируется без реальных keys:

# 503 — провайдер не настроен
curl -i https://test.admin.food-market.kz/api/auth/external/google

# Список — все false
curl -s https://test.admin.food-market.kz/api/auth/external/providers

С реальными keys (нужно настроить в appsettings.Stage.json):

# Редирект на Google consent
curl -i 'https://test.admin.food-market.kz/api/auth/external/google?returnUrl=/dashboard'