feat(tradein/auth): auth-core — login/logout, sessions, dual-mode rbac (#2552) #2561

Open
lekss361 wants to merge 1 commit from feat/tradein-auth-core into main
Owner

Closes #2552 (эпик #2549, шаг 3/8).

  • app/services/auth_session.py: create/get/revoke сессий, sliding TTL (refresh ≤1 раз в 5 мин), DB_ROLE_PATHS (employee≈pilot, manager +/api/v1/team/**, admin /**)
  • app/api/v1/auth.py: POST /login (bcrypt verify, единый 401, rate-limit username+IP → 429, события login_success/login_failed в user_events, httponly+secure+lax cookie), POST /logout
  • app/core/rbac.py: dual-mode — session-cookie приоритетен, legacy X-Authenticated-User+roles.yaml без изменений при AUTH_MODE=dual; /auth/login|logout исключены из auth-гейта
  • app/api/v1/me.py: session-first; app/main.py: mount + non-fatal warning при пустом SESSION_SECRET
  • Прокидка имени в middleware: rbac_guard инжектит X-Authenticated-User в request.scope["headers"] для session-юзеров → RequestAudit (читает после call_next) видит корректное имя; RateLimit (читает до) для session-запросов остаётся по-IP — задокументированный trade-off, не регрессия

Тесты: +34 (session unit + API integration: 401-кейсы, 429, logout, dual/db_only, expired session, legacy regression). Полный uv run pytest -q: 1 failed — test_search_cache_hit, pre-existing (идентично падает на чистом forgejo/main, проверено stash-rerun'ом; на CI main зелёный — локальный env).

Деплой безопасен без SESSION_SECRET (opaque-токены не требуют подписи, только warning).

Closes #2552 (эпик #2549, шаг 3/8). - `app/services/auth_session.py`: create/get/revoke сессий, sliding TTL (refresh ≤1 раз в 5 мин), `DB_ROLE_PATHS` (employee≈pilot, manager +`/api/v1/team/**`, admin `/**`) - `app/api/v1/auth.py`: `POST /login` (bcrypt verify, единый 401, rate-limit username+IP → 429, события login_success/login_failed в user_events, httponly+secure+lax cookie), `POST /logout` - `app/core/rbac.py`: dual-mode — session-cookie приоритетен, legacy `X-Authenticated-User`+roles.yaml без изменений при `AUTH_MODE=dual`; `/auth/login|logout` исключены из auth-гейта - `app/api/v1/me.py`: session-first; `app/main.py`: mount + non-fatal warning при пустом SESSION_SECRET - Прокидка имени в middleware: rbac_guard инжектит `X-Authenticated-User` в `request.scope["headers"]` для session-юзеров → RequestAudit (читает после call_next) видит корректное имя; RateLimit (читает до) для session-запросов остаётся по-IP — задокументированный trade-off, не регрессия Тесты: +34 (session unit + API integration: 401-кейсы, 429, logout, dual/db_only, expired session, legacy regression). Полный `uv run pytest -q`: 1 failed — `test_search_cache_hit`, **pre-existing** (идентично падает на чистом forgejo/main, проверено stash-rerun'ом; на CI main зелёный — локальный env). Деплой безопасен без SESSION_SECRET (opaque-токены не требуют подписи, только warning).
lekss361 added 1 commit 2026-07-30 07:50:16 +00:00
feat(tradein/auth): auth-core — login/logout, sessions, dual-mode rbac (#2552)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m19s
0835266516
Foundation для эпика #2549: session-cookie auth поверх legacy Caddy
trusted-header. app.services.auth_session — CRUD для tradein_sessions
(create/get/revoke) + get_user_by_username для password-логина; opaque
secrets.token_urlsafe токены, sliding last_seen_at/expires_at refresh
(не чаще раза в 5 минут).

POST /api/v1/auth/login проверяет password_hash (bcrypt) через
app.core.password, ставит httponly+secure cookie, пишет
login_success/login_failed в user_events; per-username+IP rate-limit
(SlidingWindowLimiter) отдельно от общего RateLimitMiddleware. POST
/logout ревокает сессию и чистит cookie. Оба пути exempt из rbac_guard's
auth-required gate (иначе логин сам себя не пропустил бы).

rbac_guard теперь dual-mode: session-cookie резолвится первым (DB-роль
employee/manager/admin -> paths как у pilot/+team/admin), fallback на
legacy X-Authenticated-User + roles.yaml БЕЗ ИЗМЕНЕНИЙ когда auth_mode
== "dual"; auth_mode == "db_only" отключает legacy header полностью.
Резолвленный сессией username инжектится в ASGI scope headers (до
call_next) — RequestAuditMiddleware и downstream route-хендлеры видят
его прозрачно; RateLimitMiddleware (внешний относительно rbac_guard)
для session-запросов лимитирует по IP, не по username — документированный
trade-off, не регрессия.

GET /me — session-first: валидная cookie отдаёт scope из tradein_users
без похода в roles.yaml; без cookie — прежний legacy путь. session_secret
остаётся опциональным (opaque-токены не требуют подписи) — пустое
значение только logger.warning на старте, не startup-fail.

Полный набор тестов (tests/test_rbac.py, test_internal_auth_secret.py,
test_account_quota.py) проходит без правок — regression-safe.
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m19s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/tradein-auth-core:feat/tradein-auth-core
git checkout feat/tradein-auth-core

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout main
git merge --no-ff feat/tradein-auth-core
git checkout feat/tradein-auth-core
git rebase main
git checkout main
git merge --ff-only feat/tradein-auth-core
git checkout feat/tradein-auth-core
git rebase main
git checkout main
git merge --no-ff feat/tradein-auth-core
git checkout main
git merge --squash feat/tradein-auth-core
git checkout main
git merge --ff-only feat/tradein-auth-core
git checkout main
git merge feat/tradein-auth-core
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2561
No description provided.