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>
50 lines
5.8 KiB
Markdown
50 lines
5.8 KiB
Markdown
# Итог: раздел интерфейса «Пользователи» (изменение 006)
|
||
|
||
## Что сделано
|
||
- **API `/api/v1/users`** (`app/api/v1/users.py`, роутер подключён в `app/main.py`):
|
||
`GET /users` (поиск по логину, `limit`/`offset`), `POST /users`, `PATCH /users/{id}`, `DELETE /users/{id}` — только `admin`;
|
||
`POST /users/me/password` — смена своего пароля любой ролью с подтверждением текущего.
|
||
- **Схемы** (`app/schemas.py`): `UserIn`, `UserUpdate`, `PasswordChange`, `UserOut` (`id`, `username`, `role`, `is_active`);
|
||
валидатор логина (`Login`, 3–100 символов, `[A-Za-z0-9._-]`) и запрет служебных логинов `system`/`anonymous`.
|
||
- **Журнал**: сущность `user` добавлена в словарь сообщений (`app/services.py`), события `user.created`, `user.updated`,
|
||
`user.password_reset` («пароль изменён администратором» / «пароль изменён пользователем»), `user.deleted`; в `diff` — только
|
||
изменённые поля, для пароля — пометка «изменён» без значения. В UI — подпись «Пользователь» и цвет бейджа для `password_reset`.
|
||
- **UI** (`web/app.js`): пункт навигации «Пользователи» (только у администратора), экран со счётчиком, поиском и таблицей
|
||
«Логин · Роль · Статус» (строка кликабельна, меню «⋯»: редактировать, отключить/разрешить доступ, удалить), окно создания
|
||
и редактирования (логин после создания не редактируется, необязательный «Новый пароль», флажок «Доступ разрешён»),
|
||
кнопка «Сменить пароль» в шапке, заглушка для не-администратора при ручном вводе `#/users`.
|
||
- **Тест** `tests/test_users.py` — один сквозной сценарий.
|
||
- **Документация**: обновлён `README.md`, план и итог в `docs/changes/006-users-management/`.
|
||
|
||
## Поведение
|
||
| Ситуация | Ответ |
|
||
|---|---|
|
||
| Логин занят (в т. ч. в другом регистре) | 409 «Пользователь с таким логином уже существует» |
|
||
| Логин `system`/`anonymous`, короче 3 символов, пароль короче 8 | 422 с указанием поля |
|
||
| `viewer` читает `/users` или что-либо изменяет | 403 |
|
||
| `PATCH`/`DELETE` своей учётной записи (роль, доступ, удаление) | 409 |
|
||
| Отключение или удаление последнего активного администратора | 409 |
|
||
| Неверный текущий пароль при смене своего пароля | 403 |
|
||
| Новый пароль совпадает с текущим | 422 |
|
||
|
||
## Действие ролей
|
||
- `admin` — полный доступ, включая управление пользователями и запись данных.
|
||
- `viewer` — чтение всех справочников, реестра, журнала и обзора; запись (403) и раздел «Пользователи» недоступны.
|
||
- **Отключение** учётной записи действует немедленно: токен проверяется по `users.is_active` в `current_user`.
|
||
- **Смена пароля** не отзывает уже выданные токены (JWT без списка отзыва) — они действуют до истечения `JWT_TTL_MINUTES`.
|
||
|
||
## Инварианты
|
||
- Схема БД не менялась: таблица `users` (миграция 0001) уже содержит `username`, `password_hash`, `role`, `is_active`.
|
||
- Пароли хранятся только в виде argon2-хэша; значение пароля никогда не попадает в журнал.
|
||
- Логин не меняется: он же `sub` в JWT; переименование = создание новой записи.
|
||
- Логин уникален, в том числе без учёта регистра; служебные акторы журнала зарезервированы.
|
||
- Перед каждым изменением проверяется, что в системе останется хотя бы один активный администратор.
|
||
|
||
## Проверка
|
||
- Python: `python3 -m py_compile` по изменённым модулям — без ошибок; автоматическая проверка после правок — успешно.
|
||
- Интеграционный тест: `docker compose up -d --build && venv/bin/python -m pytest tests/test_users.py -q`
|
||
(создание viewer, запреты 403/409/422, вход, отключение с немедленным отзывом токена, сброс пароля администратором,
|
||
смена своего пароля, защита своей записи и последнего администратора, удаление, записи в журнале).
|
||
- Ручной сценарий в UI: вход администратором → «Пользователи» → создание viewer → вход в другом окне → отключение
|
||
(вход и текущая сессия отбиваются) → сброс пароля → вход с новым паролем → «Сменить пароль» в шапке.
|