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

132 lines
5.7 KiB
Markdown
Raw Permalink 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.

# SSO — Google и Microsoft
Sprint 20: добавлен скелет SSO. Сейчас можно перейти на consent screen
у Google/Microsoft и получить email пользователя, но автоматического
создания учётной записи **нет** — это требует invite-flow от
администратора организации (см. multi-tenant ниже).
## Как получить keys
### Google
1. Перейти в [Google Cloud Console → APIs & Services → Credentials](https://console.cloud.google.com/apis/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`:
```json
{
"Authentication": {
"Google": {
"ClientId": "...",
"ClientSecret": "..."
}
}
}
```
### Microsoft
1. Перейти в [Azure Portal → App registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) → **New registration**.
2. **Supported account types**: «Accounts in any organizational directory and personal Microsoft accounts».
3. **Redirect URI** (Web): добавить три URL аналогично Google (заменив `signin-google``signin-microsoft`).
4. После создания: **Certificates & secrets → New client secret**. Скопировать **value**.
5. **Overview → Application (client) ID**.
6. Конфиг:
```json
{
"Authentication": {
"Microsoft": {
"ClientId": "...",
"ClientSecret": "..."
}
}
}
```
## Как использовать
### Без настроенных keys
`GET /api/auth/external/google`**503** с подсказкой:
```json
{
"error": "SSO для Google не настроено.",
"hint": "Добавьте в appsettings: Authentication:Google:ClientId и :ClientSecret. См. docs/sso.md."
}
```
`GET /api/auth/external/providers` → текущее состояние:
```json
{ "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** с информацией:
```json
{
"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:
```bash
# 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`):
```bash
# Редирект на Google consent
curl -i 'https://test.admin.food-market.kz/api/auth/external/google?returnUrl=/dashboard'
```