docs: «нет заголовка Authorization или схема не Bearer» — не попытка входа (ревью R-7, #9)
Формулировка уточнена в докстрингах `services/session_audit.py` и `api/dependencies/auth.py`, README «Безопасность dev-режима» и CLAUDE.md; тест событий входа дополнен запросом со схемой Basic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
e8d14edfbb
commit
7b28554817
@ -75,7 +75,8 @@
|
|||||||
- **Аутентификация** — `core/auth.py` (проверка JWT), `src/api/dependencies/auth.py` (`current_user`, `require(code)`);
|
- **Аутентификация** — `core/auth.py` (проверка JWT), `src/api/dependencies/auth.py` (`current_user`, `require(code)`);
|
||||||
dev-эмитент (`POST /api/v1/dev/token`) существует только при `AUTH_DEV_ISSUER=true` **и** `DEBUG=true`.
|
dev-эмитент (`POST /api/v1/dev/token`) существует только при `AUTH_DEV_ISSUER=true` **и** `DEBUG=true`.
|
||||||
Аудит входа — `services/session_audit.py` (`app.state.session_audit`): `LOGIN` (dev-токен; первый запрос после старта),
|
Аудит входа — `services/session_audit.py` (`app.state.session_audit`): `LOGIN` (dev-токен; первый запрос после старта),
|
||||||
`LOGIN_FAILED` (`AuthenticationFailedError` 401/403 → обработчик пишет в отдельной транзакции `session_transaction`).
|
`LOGIN_FAILED` (`AuthenticationFailedError` 401/403 → обработчик пишет в отдельной транзакции `session_transaction`);
|
||||||
|
нет заголовка `Authorization` или схема не `Bearer`, 403 «недостаточно прав» — не события входа.
|
||||||
- **Seed** — `python -m scripts.seed [--dev]` (`make seed|seed-dev`), идемпотентен (матрица `role_permission` — целиком только в
|
- **Seed** — `python -m scripts.seed [--dev]` (`make seed|seed-dev`), идемпотентен (матрица `role_permission` — целиком только в
|
||||||
пустую таблицу, в заполненную — лишь пары впервые созданных ролей/прав); `--dev` — только для разработки (CLI отказывает с кодом 2 при `DEBUG=false`).
|
пустую таблицу, в заполненную — лишь пары впервые созданных ролей/прав); `--dev` — только для разработки (CLI отказывает с кодом 2 при `DEBUG=false`).
|
||||||
**Сверка ORM↔DBML** — `scripts/check_dbml_vs_orm.py` (`make dbml-check`).
|
**Сверка ORM↔DBML** — `scripts/check_dbml_vs_orm.py` (`make dbml-check`).
|
||||||
|
|||||||
@ -604,7 +604,8 @@ Dockerfile только для локального compose
|
|||||||
- `LOGIN_FAILED` — 401 (токен не принят: `user_id` пуст, в `description` причина, сам токен не пишется) и 403
|
- `LOGIN_FAILED` — 401 (токен не принят: `user_id` пуст, в `description` причина, сам токен не пишется) и 403
|
||||||
аутентификации (пользователь не зарегистрирован, отключён или неоднозначен: email в `description`, `user_id` —
|
аутентификации (пользователь не зарегистрирован, отключён или неоднозначен: email в `description`, `user_id` —
|
||||||
если пользователь найден). Запись делается в отдельной транзакции и остаётся, хотя транзакция запроса откатилась.
|
если пользователь найден). Запись делается в отдельной транзакции и остаётся, хотя транзакция запроса откатилась.
|
||||||
- Не события входа: запрос без заголовка `Authorization` и 403 «недостаточно прав». Ошибка записи аудита входа
|
- Не события входа: запрос без заголовка `Authorization` или со схемой не `Bearer` (401 «требуется заголовок…») и
|
||||||
|
403 «недостаточно прав». Ошибка записи аудита входа
|
||||||
ответ не меняет (только лог). Выход (`LOGOUT`) не пишется: сессий на сервере нет.
|
ответ не меняет (только лог). Выход (`LOGOUT`) не пишется: сессий на сервере нет.
|
||||||
- Поля, которые присылает клиент, обрезаются: `user_agent` — до 512 символов, `description` — до 1000.
|
- Поля, которые присылает клиент, обрезаются: `user_agent` — до 512 символов, `description` — до 1000.
|
||||||
- **`audit_event.ip_address` достоверен только за доверенным прокси.** `X-Forwarded-For` принимается лишь от адресов
|
- **`audit_event.ip_address` достоверен только за доверенным прокси.** `X-Forwarded-For` принимается лишь от адресов
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
Аудит входа (OQ-64, `services/session_audit.py`): отказ токена и 403 аутентификации — `AuthenticationFailedError`,
|
Аудит входа (OQ-64, `services/session_audit.py`): отказ токена и 403 аутентификации — `AuthenticationFailedError`,
|
||||||
обработчик пишет LOGIN_FAILED в отдельной транзакции; первый успешный `current_user` пользователя после старта процесса
|
обработчик пишет LOGIN_FAILED в отдельной транзакции; первый успешный `current_user` пользователя после старта процесса
|
||||||
пишет LOGIN. Нет заголовка `Authorization` и 403 «недостаточно прав» — не события входа.
|
пишет LOGIN. Нет заголовка `Authorization` или схема не `Bearer` (`parse_bearer`) и 403 «недостаточно прав» — не
|
||||||
|
события входа.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
и сбрасывается при рестарте, у каждого экземпляра приложения — своё);
|
и сбрасывается при рестарте, у каждого экземпляра приложения — своё);
|
||||||
- LOGIN_FAILED — 401 (токен не принят: `user_id` пуст, в `description` причина без самого токена) и 403 аутентификации
|
- LOGIN_FAILED — 401 (токен не принят: `user_id` пуст, в `description` причина без самого токена) и 403 аутентификации
|
||||||
(пользователь не найден, отключён, неоднозначен: email в `description`, `user_id` — если пользователь найден).
|
(пользователь не найден, отключён, неоднозначен: email в `description`, `user_id` — если пользователь найден).
|
||||||
Запрос без заголовка `Authorization` попыткой входа не считается; 403 «недостаточно прав» — авторизация, не вход.
|
Запрос без заголовка `Authorization` или со схемой не `Bearer` попыткой входа не считается (токен не предъявлен);
|
||||||
|
403 «недостаточно прав» — авторизация, не вход.
|
||||||
|
|
||||||
Раздел `SESSION`, `object_type = "app_user"`, `object_id = user_id`; IP и user-agent — из запроса. Поля, которые
|
Раздел `SESSION`, `object_type = "app_user"`, `object_id = user_id`; IP и user-agent — из запроса. Поля, которые
|
||||||
присылает клиент, обрезаются: `user_agent` — до 512 символов, `description` — до 1000.
|
присылает клиент, обрезаются: `user_agent` — до 512 символов, `description` — до 1000.
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
LOGIN — успешная выдача dev-токена и первый успешный запрос пользователя с токеном после старта процесса;
|
LOGIN — успешная выдача dev-токена и первый успешный запрос пользователя с токеном после старта процесса;
|
||||||
LOGIN_FAILED — 401 (токен не принят) и 403 аутентификации (пользователь не найден, отключён, неоднозначен).
|
LOGIN_FAILED — 401 (токен не принят) и 403 аутентификации (пользователь не найден, отключён, неоднозначен).
|
||||||
Запрос без заголовка `Authorization` и 403 «недостаточно прав» входом не считаются. LOGIN_FAILED пишется в отдельной
|
Запрос без заголовка `Authorization` или со схемой не `Bearer` и 403 «недостаточно прав» входом не считаются.
|
||||||
транзакции и переживает откат транзакции запроса — проверяется на `committed_app` (сессия запроса как в проде).
|
LOGIN_FAILED пишется в отдельной транзакции и переживает откат транзакции запроса — проверяется на `committed_app`
|
||||||
|
(сессия запроса как в проде).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -170,12 +171,14 @@ async def test_disabled_user_records_login_failed_with_user_id(app: FastAPI, see
|
|||||||
async def test_missing_header_and_missing_permission_are_not_login_events(
|
async def test_missing_header_and_missing_permission_are_not_login_events(
|
||||||
committed_app: FastAPI, committed_sessionmaker: async_sessionmaker[AsyncSession]
|
committed_app: FastAPI, committed_sessionmaker: async_sessionmaker[AsyncSession]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Без заголовка — не попытка входа; 403 «недостаточно прав» — авторизация, не аутентификация.
|
"""Без заголовка или со схемой не Bearer — не попытка входа; 403 «недостаточно прав» — авторизация, не вход.
|
||||||
|
|
||||||
LOGIN первого запроса expert записан, хотя сам запрос (403 по праву) откатил свою транзакцию.
|
LOGIN первого запроса expert записан, хотя сам запрос (403 по праву) откатил свою транзакцию.
|
||||||
"""
|
"""
|
||||||
async with _client(committed_app) as client:
|
async with _client(committed_app) as client:
|
||||||
assert (await client.get("/api/v1/me")).status_code == HTTP_UNAUTHORIZED
|
assert (await client.get("/api/v1/me")).status_code == HTTP_UNAUTHORIZED
|
||||||
|
basic = {"Authorization": "Basic dXNlcjpwYXNz"}
|
||||||
|
assert (await client.get("/api/v1/me", headers=basic)).status_code == HTTP_UNAUTHORIZED
|
||||||
expert = _bearer(_token(EXPERT))
|
expert = _bearer(_token(EXPERT))
|
||||||
assert (await client.get("/api/v1/admin/users", headers=expert)).status_code == HTTP_FORBIDDEN
|
assert (await client.get("/api/v1/admin/users", headers=expert)).status_code == HTTP_FORBIDDEN
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user