ci(deploy): deploy-пользователь вместо root, DEPLOY_HOST в секретах; fix jsdom для Node 20

Шаг 0.3 плана production-готовности:
- deploy.yml: username deploy, host из секрета DEPLOY_HOST
- сервер: пользователь deploy (группа docker), /opt/crm переписан на deploy
- SSH_KEY = ключ deploy (не root)
- pr-check fix: jsdom ^26 (jsdom 30 несовместим с Node 20 CI-раннера)
This commit is contained in:
2026-09-08 14:05:07 +03:00
parent 06bfd2aa75
commit 03871b76a2
4 changed files with 246 additions and 273 deletions

View File

@@ -465,3 +465,36 @@
- JWT детерминирован по (payload, iat в секундах): две ротации одной и той же сессии в пределах одной секунды выдадут одинаковый токен — безвредно (хэш и семантика совпадают).
- `bot_sessions` (refresh-токены bot_login) сознательно не тронуты — отдельная модель audience `workflow-bots`; кандидат на аналогичное хэширование отдельным шагом. In-memory `resetTokenStore` суперадмина — не в БД, вне скоупа.
- Удаление legacy plain-колонок — отдельная миграция после подтверждения стабильности на проде.
---
## [0.6] TTL access-токена 15m + хэширование refresh/reset-токенов
- Статус: ✅ done (коммит `06bfd2a`, ветка feat/token-security → main, окно подтверждено пользователем 2026-09-08)
- Зачем: укоротить окно атаки при краже access-токена; токены в БД только в виде хэша.
- Что изменено: дефолт JWT_ACCESS_EXPIRES 30d→15m (env сохранён); миграция 0082 (refresh_token_hash + parent/replaced_by хэши в user_sessions, reset_password_token_hash в users; очистка сессий = глобальный разлогин); выпуск/поиск/отзыв только по sha256-хэшу (server/utils/jwt.ts hashToken, users.storage.ts, auth.service.ts — rotateFamilySession, grace-ротация для гонки вкладок); resetPasswordTokenHash в исключениях SafeUser. Фронт не тронут — refresh реактивный по 401.
- Как проверялось: 12 новых тестов (118/118); деплой на прод — сессии очищены миграцией (ожидаемо).
- Проверка на проде: re-login работает, refresh-ротация (access 15 мин) — см. ниже в этой записи после подтверждения.
- Влияние на поиск/UX: нет; пользователи перелогинились один раз.
- Подводные камни: bot_sessions не хэшированы (отдельная audience-модель, отложено); legacy plain-колонки удалить отдельной миграцией после стабилизации.
## [0.3] Не-root deploy-пользователь
- Статус: ✅ done
- Зачем: убрать постоянный root-SSH из CI.
- Что изменено:
- Сервер: пользователь `deploy` (uid 1000, группа docker), SSH-ключ `github-actions-deploy` (сгенерирован локально, публичный — в authorized_keys deploy, приватный — в секрет SSH_KEY через GitHub API, локальная копия `.ssh/deploy_ci` вне репо).
- `/opt/crm` переписан на deploy:deploy; бэкап compose доступен на чтение.
- `.github/workflows/deploy.yml`: `username: deploy`, хост — секрет `DEPLOY_HOST` (вместо хардкода).
- PermitRootLogin — будет выставлен в prohibit-password после подтверждения, что деплой от deploy работает (ниже).
- Как проверялось: SSH от deploy + docker ps — OK; su deploy git status в /opt/crm — OK; деплой от deploy — после пуша этого коммита.
- Влияние на поиск/UX: нет.
- Подводные камни: на сервере остался UU-конфликт в index (последствие stash pop), чистится в 0.14; root-SSH пока остаётся как fallback до подтверждения.
## CI: фикс pr-check (jsdom для Node 20)
- Статус: ✅ done
- Зачем: pr-check падал с 06bfd2a (и 639036c): vitest не стартовал a11y-файл — `webidl.util.markAsUncloneable is not a function` в undici: jsdom@30 несовместим с Node 20 раннера (локально Node 24 — работало).
- Что изменено: jsdom 30 → ^26 (devDependencies).
- Как проверялось: vitest 118/118 локально; pr-check на этом коммите.
- Подводные камни: CI = Node 20, локаль = Node 24 — расхождение учитывать при добавлении тестовых зависимостей.