Files
ayurishchevandClaude Opus 5.5 8384c2c311 Задачи 034-035: публикация через Caddy, последний вход пользователя
034 Публикация стенда как https://rxipam.rxmsk.ru через общий Caddy хоста (Caddyfile вне репозитория):
    UI и /api/v1 доступны из интернета, /docs, /redoc, /openapi.json — только из частных сетей
    (RFC 1918/4193, loopback). Caddy проксирует на опубликованный порт приложения — общая Docker-сеть
    отклонена из-за коллизии имён app/db с Nextcloud. TRUSTED_PROXIES=172.16.0.0/12: реальный IP
    клиента из X-Forwarded-For. Интеграционный тест журнала больше не проверяет подделку XFF с хоста
    (хост за Caddy доверенный), логику покрывает unit-тест.
035 Дата и IP последнего входа в разделе «Пользователи»: users.last_login_at / last_login_ip
    (миграция 0014 с заполнением из журнала), запись при успешном входе, UserOut, колонка в UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:45:30 +03:00

26 lines
3.1 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.
# Итог: последний вход пользователя — дата и IP (изменение 035)
План: `PLAN.md`. В разделе «Пользователи» появилась колонка «Последний вход»: дата (UTC) и IP последнего успешного входа.
## Что сделано
| Файл | Изменение |
|---|---|
| `app/models.py` | `User.last_login_at` (`timestamptz`), `User.last_login_ip` (`INET`), nullable |
| `alembic/versions/0014_users_last_login.py` | Колонки и перенос данных из журнала: последнее событие `session.login` на логин. Если журнал ротирован или очищен — `NULL` |
| `app/api/v1/auth.py` | Успешный вход записывает `last_login_at = now()` и `last_login_ip` в той же транзакции, что и `session.login`. IP — тот же, что в журнале (реальный клиент за Caddy, изменение 034). Неудачные попытки поля не трогают |
| `app/schemas.py` | `UserOut.last_login_at`, `UserOut.last_login_ip`. INET приводится к строке через `field_validator` с существующей `ip_text()`. Поля видны в `/users` (только `superadmin`) и в `/auth/me` (свои) |
| `web/app.js` | Колонка «Последний вход» после «Статус»: дата и IP моноширинным шрифтом, «—», если входа не было |
| `tests/test_users.py` | В `test_users_management`: после входа созданного пользователя поля заполнены в `GET /users` |
| `README.md` | «Интерфейс», миграции `0001`–`0014`, строка 035 в истории |
## Проверки
- Стенд пересобран: `alembic current` — `0014`, `alembic check` — чисто, откат до 0013 и повторный upgrade — без ошибок.
- Перенос данных: у `tstark` дата и IP (`192.168.5.253`) совпадают с последним `session.login` в журнале; у `sbas-sa` входов не было — `NULL`.
- Временный `viewer` (создан и удалён через SQL): вход через Caddy с LAN-адреса хоста дал `last_login_ip = 192.168.5.9`
и дату входа, в JSON — строки. Неудачная попытка поля не изменила.
- `node --check web/app.js`, `import app.main` — чисто.
- `pytest -q` — 15 passed. Перед прогоном учётная запись `admin` из `.env` (удалена через UI в 08:54, фикстура `client` без неё не работает) создана заново через SQL как `superadmin` с паролем из `.env`; попытки входа под удалённым `admin` (блокировка «логин + IP») очищены. Создание через SQL в журнал не попало.
## Не проверено
- Колонка в браузере — не просматривалась.