Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 46s
CI / Web (React + Vite) (push) Successful in 41s
Docker API / Build + push API (push) Successful in 1m12s
Docker Web / Build + push Web (push) Successful in 31s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Аудит 2026-04-27. Полный отчёт — docs/audit-2026-04-27.md. Что закрыто: — /connect/token (AuthorizationController) теперь отказывает в login если AppUser привязан к удалённой/архивной Organization. SuperAdmin обходит проверку (ему org не нужна). Жалоба: nurnetps@gmail.com мог логиниться после удаления своей org из SuperAdmin консоли. — SuperAdminOrganizationsController.Delete (DELETE org) каскадно деактивирует всех AppUser привязанных к этой org (IsActive=false, OrganizationId=null) и помечает Status='revoked' для всех их OpenIddictTokens. Раньше Org удалялась, а юзеры оставались валидными с активными refresh-tokens на 30 дней. — EmployeesController.Delete теперь soft-delete (IsActive=false, FiredAt). Запрещены: 403 если попытка удалить себя; 403 если попытка удалить Owner (Organization.AccountOwnerUserId == employee.UserId). Сообщения с инструкцией («передайте права», «покинуть через настройки»). — /api/me возвращает hasLiveOrg и hasActiveEmployee — frontend использует это для редиректа на /no-organization вместо белого экрана. — Новая страница /no-organization (NoOrganizationPage) — fallback для orphan AppUser. CTA: создать новую org через публичный /signup или попросить инвайт. Кнопка «выйти». TenantRouteGuard редиректит orphan юзеров туда. — SuperAdminAsOrgBanner: добавлена проверка через useMe — баннер рендерится только если у текущего юзера есть Identity-роль SuperAdmin. Lingering localStorage override от прошлой сессии (другой юзер логинился до этого) автоматически чистится. — auth.ts: clearTokens() теперь сбрасывает superAdminAsOrg и superAdminEditMode. login() вызывает clearTokens() ПЕРЕД запросом чтобы новый юзер не унаследовал override-состояние от предыдущего. — deploy/recovery-restore-orphan-owners.sql — идемпотентный скрипт деактивирующий уже накопленных orphan AppUser (как nurnetps) и revoke их токены. Применён на стейдже: 1 user деактивирован, 9 токенов revoked. — deploy/Dockerfile.api: убран `--no-restore` из publish — два раздельных шага роняли build с NETSDK1064 на свежих analyzer- зависимостях, теперь restore идёт внутри publish. Smoke (стейдж): - nurnetps@gmail.com /connect/token → invalid_grant. - admin@food-market.local /connect/token → access_token выдан. - food-market.zat.kz/, /signup/, app.../login, /health → 200.
52 lines
1.9 KiB
PL/PgSQL
52 lines
1.9 KiB
PL/PgSQL
-- Recovery: orphan AppUser cleanup.
|
||
--
|
||
-- Применяется один раз вручную на стейдже/проде после деплоя
|
||
-- AuthorizationController + SuperAdminOrganizationsController фиксов
|
||
-- (audit 2026-04-27 #1, #2, #7).
|
||
--
|
||
-- Что делает:
|
||
-- 1. Находит users у которых OrganizationId указывает на отсутствующую
|
||
-- или архивированную организацию.
|
||
-- 2. Деактивирует таких users (IsActive=false), сбрасывает OrganizationId.
|
||
-- 3. Отзывает все OpenIddict refresh/access токены этих users
|
||
-- (Status='revoked') чтобы существующие сессии оборвались.
|
||
--
|
||
-- Идемпотентен: повторный запуск ничего не ломает.
|
||
-- Не удаляет данные — только статусы. Юзер при необходимости может
|
||
-- быть восстановлен ручным UPDATE users SET "IsActive"=true.
|
||
|
||
BEGIN;
|
||
|
||
WITH orphan_users AS (
|
||
SELECT u."Id"
|
||
FROM users u
|
||
LEFT JOIN organizations o ON o."Id" = u."OrganizationId"
|
||
WHERE u."IsActive" = true
|
||
AND (
|
||
u."OrganizationId" IS NULL
|
||
OR o."Id" IS NULL
|
||
OR o."IsArchived" = true
|
||
)
|
||
AND NOT EXISTS (
|
||
-- Не трогаем SuperAdmin'ов — у них org=null это норма.
|
||
SELECT 1
|
||
FROM "AspNetUserRoles" ur
|
||
JOIN roles r ON r."Id" = ur."RoleId"
|
||
WHERE ur."UserId" = u."Id" AND r."NormalizedName" = 'SUPERADMIN'
|
||
)
|
||
)
|
||
UPDATE users
|
||
SET "IsActive" = false,
|
||
"OrganizationId" = NULL
|
||
WHERE "Id" IN (SELECT "Id" FROM orphan_users);
|
||
|
||
UPDATE "OpenIddictTokens" t
|
||
SET "Status" = 'revoked'
|
||
WHERE t."Status" = 'valid'
|
||
AND t."Subject" IN (
|
||
SELECT u."Id"::text FROM users u
|
||
WHERE u."IsActive" = false
|
||
);
|
||
|
||
COMMIT;
|