Skip to content

Соглашение о неразглашении (NDA) ​

При первом входе сотрудник видит блокирующий экран с текстом соглашения и не получает данные, пока не нажмёт «Принимаю». Проверка живёт на сервере, а не только в интерфейсе: гейт стоит сразу после аутентификации и закрывает все маршруты, кроме /auth и /nda, поэтому обойти экран прямым обращением к API нельзя.

Реализовано: модуль internal/modules/nda, миграция 0136 (таблицы nda_documents, nda_acceptances, первая версия 1.0, коды nda.report / nda.manage), гейт подключён в internal/app/app.go через authHTTP.AddPostAuthMiddleware(nda.GateMiddleware(ndaSvc)). Спецификация «Структура RMS», п.12.


Схема ​

Пользователь: "Логинюсь, получаю токен"
    ↓
Фронт: "GET /nda/current"
    ↓
Система: "accepted=false, document={version, title, body}"
    ↓
Пользователь: "Читаю, нажимаю «Принимаю» (POST /nda/accept) или «Отказываюсь» (POST /nda/decline)"
    ↓
Система: "Пишу в журнал: user, версия, действие, ip, user-agent"
    ↓
Гейт: "accepted=true — все остальные маршруты открыты; иначе 403 nda_not_accepted"
    ↓
OWNER/OPS: "POST /nda/versions — новая версия, все принимают заново"

Сущности ​

Версия соглашения (Document, таблица nda_documents) ​

ПолеОписание
versionУникальная строка версии (1.0, 2026-09, …)
titleЗаголовок; по умолчанию «Соглашение о неразглашении»
bodyТекст соглашения
is_activeДействующая версия. Частичный уникальный индекс uq_nda_documents_active гарантирует, что активная ровно одна
published_atДата публикации

Публикация (Service.Publish) в одной транзакции снимает is_active со всех версий и вставляет новую активную. Старые версии остаются как история.

Запись журнала (Acceptance, таблица nda_acceptances) ​

ПолеОписание
user_id, user_nameКто
document_id, versionКакую версию
actionaccepted или declined
ipc.ClientIP() на момент действия
user_agentзаголовок User-Agent (хранится, в ответах API не отдаётся)
created_atКогда

Фиксируются оба действия: отказ означает отсутствие доступа, а не ограниченный доступ. Записи не перезаписываются — журнал append-only, статус определяется последним действием пользователя по действующей версии.

Статус для пользователя (Status) ​

GET /nda/current и гейт используют один расчёт (Service.StatusFor):

ПолеЗначение
documentДействующая версия (без неё — null)
acceptedПоследнее действие по действующей версии — accepted
declinedПоследнее действие — declined
accepted_atВремя принятия

Гейт ​

nda.GateMiddleware регистрируется как post-auth middleware — выполняется после разбора JWT на всех маршрутах под authMW.

СитуацияПоведение
Путь начинается с /api/v1/auth/ или /api/v1/nda/пропуск (иначе соглашение нельзя было бы загрузить и принять)
В контексте нет user_idпропуск (неаутентифицированные запросы отсекает auth middleware)
Действующей версии нет (nda_documents без is_active)считается принятым — пустой справочник не должен закрывать систему
Последнее действие по действующей версии — acceptedпропуск
Иначе (нет действия или declined)403 с кодом nda_not_accepted

Что из этого следует:

  • Новая версия = повторное принятие. Согласие привязано к document_id; после POST /nda/versions у всех пользователей accepted=false, и гейт закрывается до нового «Принимаю».
  • Сброс одному сотруднику = повторное принятие только для него. POST /nda/users/:id/reset (право nda.manage, кнопка «Попросить подписать заново» на вкладке NDA карточки сотрудника) пишет в журнал действие reset с автором (actor_id, миграция 0194). Последнее действие сотрудника — reset, поэтому accepted=false и при следующем запросе гейт снова показывает соглашение. Положительный ответ гейта в кеше другого процесса живёт не дольше минуты. В отчёте у строки появляются reset_at и reset_by_name (IP сброса — адрес администратора, в отчёт не попадает).
  • Отчёт и публикация тоже под гейтом? Нет: они лежат под /api/v1/nda/, поэтому доступны и до принятия — но защищены своими permission-кодами.
  • WebSocket-маршруты /ws/notifications и /ws/chat тоже идут через auth middleware и не входят в исключения, поэтому до принятия соглашения соединение отклоняется с тем же 403.
  • Гейт делает 1–2 SQL-запроса на каждый защищённый запрос; кэш на TTL запланирован в дорожной карте (этап 6).

Права ​

Принятие, отказ и своя история открыты каждому аутентифицированному пользователю. Два действия закрыты legacy permission-кодами (RequirePermissions), матрица прав их не покрывает:

КодКому выдан (0136)Что открывает
nda.reportOWNER, OPS, adminGET /nda/report
nda.manageOWNER, OPS, adminPOST /nda/versions, POST /nda/users/:id/reset

Носитель system.global_admin проходит обе проверки без кода.


API ​

МетодПутьДоступОписание
GET/api/v1/nda/currentлюбойДействующая версия и статус текущего пользователя
POST/api/v1/nda/acceptлюбойПринять действующую версию. Тело опционально: {"version": "1.0"} — только для явности намерения, на запись не влияет
POST/api/v1/nda/declineлюбойОтказаться. Ответ — тот же Status
GET/api/v1/nda/myлюбойИстория собственных действий по всем версиям: {"items": [Acceptance]}
GET/api/v1/nda/reportnda.reportКто принял, кто отказался, кто ещё не ответил по действующей версии
POST/api/v1/nda/versionsnda.manage{"version", "title"?, "body"} — выпустить новую версию, 201
POST/api/v1/nda/users/:id/resetnda.manageПопросить сотрудника принять действующую версию заново. Ответ — Status сотрудника

Ошибки: nda_no_active_document (404) при accept/decline без действующей версии; nda_version_required, nda_body_required (400) при публикации; nda_user_not_found (404) при сбросе несуществующему сотруднику.

Ответ отчёта:

json
{
  "document": { "id": 1, "version": "1.0", "title": "…", "is_active": true, "published_at": "…" },
  "rows": [
    { "user_id": 12, "user_name": "…", "email": "…", "accepted": true, "declined": false, "accepted_at": "…", "ip": "10.0.0.5" }
  ],
  "total_users": 40, "accepted_users": 31, "pending_users": 8, "declined_users": 1
}

Что важно для фронта ​

  • После логина первым делом вызывайте GET /nda/current; при accepted=false показывайте блокирующий экран с document.body и не грузите остальные данные — они всё равно вернут 403 nda_not_accepted.
  • Обрабатывайте код nda_not_accepted глобально в HTTP-клиенте: любой запрос может его вернуть после публикации новой версии посреди сессии — переводите пользователя на экран соглашения, не на «нет доступа».
  • decline не разлогинивает: пользователь остаётся с токеном, но без данных. Покажите объяснение и кнопку «Вернуться к соглашению».
  • Не кэшируйте accepted дольше сессии; сбрасывайте при logout и после публикации версии.
  • Экран отчёта и публикация версии — по permission-кодам nda.report / nda.manage, которых нет в матрице permissions из /org/me. Итог сервер отдаёт в capabilities того же ответа: nda_report и nda_manage (см. Матрица прав); 403 от сервера по-прежнему обрабатывайте.
  • ip в отчёте — служебная информация для администратора; в профиле пользователя (/nda/my) он тоже есть, user_agent не отдаётся.

Загружаем документацию…