006 Пользователи: API /users (CRUD, смена своего пароля), раздел UI «Пользователи»,
события журнала user.*, защита от отключения/удаления себя и последнего админа.
007 Исправление удаления организации: VRF удаляются явным DELETE до организации
(без relationship() порядок DELETE не гарантирован → ложный 409).
008 Журнал фиксирует отказы в удалении (<entity>.delete_blocked) со списком
мешающих объектов в «Данных»: организация, VRF, тип устройства, префикс, пользователь.
009 Выбор строк чекбоксами и групповые операции в UI (удаление, смена типа устройств,
статус префиксов и адресов, доступ пользователей); цикл запросов из UI, итог и список отказов.
010 Автовыделение следующего вложенного префикса: POST/GET /prefixes/{id}/subnets/next,
первый свободный выровненный блок; пункт «Добавить вложенный (авто)» в меню префикса.
Тесты: 14 (добавлены сценарии для 006, 007/008, 010); исправлена нестабильность
тестов журнала (IPv6-группы с ведущими нулями нормализуются PostgreSQL).
Документация: README.md, docs/changes/006-010 (планы и итоги).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 lines
6.1 KiB
Markdown
53 lines
6.1 KiB
Markdown
# Раздел интерфейса «Пользователи» (изменение 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; ручной сценарий в браузере (создание, редактирование, отключение, вход отключённым, удаление).
|