Files
ayurishchevandClaude Sonnet 5 cd09ef0805 Задачи 006-010: пользователи, исправление удаления, журнал отказов, групповые операции, автовыделение префиксов
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>
2026-09-26 13:25:33 +03:00

50 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Итог: раздел интерфейса «Пользователи» (изменение 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 → вход в другом окне → отключение
(вход и текущая сессия отбиваются) → сброс пароля → вход с новым паролем → «Сменить пароль» в шапке.