# Раздел интерфейса «Пользователи» (изменение 006) ## Context Роли `admin` (запись) и `viewer` (чтение) заявлены в README и в UI (журнал: настройки и очистка скрыты для не-админа), но назначить роль негде: таблица `users` заполняется только записью администратора при первом старте (`seed()` в `app/main.py`), эндпоинтов управления пользователями нет, сменить пароль — тоже нельзя. Нужен раздел интерфейса, где администратор заводит учётные записи, меняет им роль/пароль и отключает доступ, а любой пользователь может сменить свой пароль. ## Модель (без изменения схемы БД) Таблица `users` (миграция 0001) уже содержит всё необходимое: `id`, `username` (уникальный), `password_hash` (argon2), `role` (`user_role`: admin/viewer), `is_active`. Миграция не требуется. Пароли не хранятся и не пишутся в журнал в открытом виде. ## API (`/api/v1/users`, все — под авторизацией) | Метод | Кто | Назначение | |---|---|---| | `GET /users` | admin | список: логин (поиск `q`), роль, признак активности, `limit`/`offset` | | `POST /users` | admin | создание: `username`, `password` (≥ 8), `role`, `is_active` | | `PATCH /users/{id}` | admin | роль, `is_active`, необязательный новый `password` | | `DELETE /users/{id}` | admin | удаление учётной записи | | `POST /users/me/password` | любой | смена **своего** пароля с подтверждением текущего | Правила (иначе 409/422, состояние не меняется): - логин: 3–100 символов, `[A-Za-z0-9._-]`, уникален; зарезервированные `system` и `anonymous` запрещены — журнал различает служебные события по этим именам (`actor_of` в `app/services.py`); - нельзя удалить или отключить/понизить **свою** учётную запись (иначе текущая сессия теряет доступ); - нельзя отключить, понизить или удалить **единственного активного администратора** (система осталась бы без прав записи); - неверный текущий пароль при смене своего пароля → 403. Аудит: `user.created`, `user.updated`, `user.password_reset` (пароль администратором и своя смена), `user.deleted`; в `entity_label` — логин, в `diff` — только изменённые поля (`password` отмечен как «изменён», без значения). Сущность `user` добавлена в словарь человекочитаемых сообщений (`_NOUNS` в `app/services.py`). ## UI (`web/app.js`) - Пункт навигации «Пользователи» — только для администратора (`NAV` фильтруется в `shell()`); при ручном вводе `#/users` не-админ видит заглушку «Раздел доступен только администратору». - Экран: счётчик записей, поиск по логину, таблица «Логин · Роль · Статус · Действия»; строка кликабельна (паттерн изменения 005), в меню «⋯» — «Редактировать», «Отключить/Активировать», «Удалить». - Окно создания/редактирования: логин (при создании), роль, флажок «Доступ разрешён», необязательное поле «Новый пароль» (пустое — пароль не меняется); окно смены своего пароля — в шапке («Сменить пароль»). - В журнале: подпись сущности «Пользователь», тип события в фильтре типов появляется автоматически (`/audit/facets`). ## Ограничения (осознанные) - JWT без списка отзыва: после **смены пароля** уже выданные токены продолжают работать до истечения `JWT_TTL_MINUTES`; **отключение** учётной записи действует немедленно (токен проверяется по `users.is_active` в `current_user`). - Логин менять нельзя (он же `sub` в токене) — вместо этого создаётся новая учётная запись. - Нет саморегистрации, восстановления пароля по e-mail, групп прав и журналирования последнего входа. ## Проверка - Интеграционный тест `tests/test_users.py` (один сценарий, против контейнеров): создание, дубль логина → 409, запрет служебного логина → 422, вход новым пользователем, запрет записи для `viewer` → 403, отключение → вход 401, сброс пароля → вход с новым паролем, смена своего пароля и вход с ним, удаление своей записи → 409. - Синтаксическая проверка Python и JS; ручной сценарий в браузере (создание, редактирование, отключение, вход отключённым, удаление).