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>
26 lines
3.1 KiB
Markdown
26 lines
3.1 KiB
Markdown
# Итог: последний вход пользователя — дата и 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 в журнал не попало.
|
||
|
||
## Не проверено
|
||
- Колонка в браузере — не просматривалась.
|