arm-core/api/src/db/db.py
Andrey Epifancev ef7458eddb feat: аудит входа LOGIN / LOGIN_FAILED (OQ-64, #9 п.5)
Раздел SESSION, object_type app_user, IP и user-agent запроса
(`services/session_audit.py`):
- LOGIN — выдача dev-токена (в транзакции запроса) и первый успешный
  `current_user` пользователя после старта процесса (`SessionAudit.logged_in`,
  в памяти процесса; параллельные первые запросы — одно событие);
- LOGIN_FAILED — 401 (токен не принят: user_id пуст, причина без токена) и 403
  аутентификации (не зарегистрирован / отключён / неоднозначен: email в
  description, user_id — если найден). `AuthenticationFailedError` (подкласс
  HTTPException) обрабатывается после отката транзакции запроса, запись — в
  отдельной транзакции `session_transaction`, поэтому остаётся в журнале.
Без заголовка Authorization и 403 «недостаточно прав» — не события входа;
ошибка записи аудита входа ответ не меняет.

Тесты: `test_login_audit.py` (в т.ч. фиксация при откате запроса на
`committed_app`); счётчики следов импорта не учитывают события SESSION.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 13:27:38 +04:00

35 lines
1.7 KiB
Python
Raw 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.

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
# from fastapi_users.db import SQLAlchemyUserDatabase, SQLAlchemyBaseUserTableUUID
from src.core.config import settings
engine = create_async_engine(
settings.OPENBAO.DATABASE.DATABASE_URL, connect_args={"server_settings": {"search_path": "dkb"}}
)
async_session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_async_session() -> AsyncIterator[AsyncSession]:
"""Сессия запроса = одна транзакция: commit при успешном ответе, rollback при любом исключении (R-1, design #12).
В маршрутах объявляется только через `src.api.dependencies.db.SessionDep` (`scope="function"`): тогда commit
выполняется до отправки ответа, а все зависимости запроса получают одну и ту же сессию.
"""
async with async_session_maker() as session, session.begin():
yield session
@asynccontextmanager
async def session_transaction(
maker: async_sessionmaker[AsyncSession] | None = None,
) -> AsyncIterator[AsyncSession]:
"""Отдельная короткая транзакция вне сессии запроса (аудит входа): commit на выходе, rollback при исключении.
`maker` по умолчанию — `async_session_maker` приложения (читается при вызове: тесты его подменяют).
"""
async with (maker or async_session_maker)() as session, session.begin():
yield session