gendesign/tradein-mvp/backend/app/api/v1/me.py
bot-backend ac0c472faf
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 2m38s
Deploy Trade-In / build-backend (push) Successful in 58s
Deploy Trade-In / deploy (push) Successful in 1m6s
fix(tradein/auth): убрать устаревшее «Caddy basic_auth required» из 401 (#2573) (#2649)
2026-08-05 08:45:57 +00:00

93 lines
4 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.

"""GET /me — отдаёт текущего пользователя и его RBAC-scope.
MIRROR of main backend's app/api/v1/me.py — kept in sync manually.
Mounted at /api/v1/me; через Caddy `uri strip_prefix /trade-in` это становится
`/trade-in/api/v1/me` снаружи.
Caddy basic_auth пропускает `X-Authenticated-User: <username>` через
`header_up` в каждом reverse_proxy. Frontend дёргает /me чтобы понять
кому что показывать.
#2552: session-first. Валидная DB-session cookie (см. app.services.auth_session)
отдаёт scope из реестра людей (role/display_name/org/email) БЕЗ похода в
roles.yaml. Без cookie (или невалидная/истёкшая) — legacy X-Authenticated-User
путь, БЕЗ ИЗМЕНЕНИЙ (regression недопустим — существующие тесты держат его
бит-в-бит).
Сессия БД берётся у `identity_store.get_identity_db` (реестр), а не у
`app.core.db.get_db` (продуктовая БД): при `IDENTITY_STORE=auth` люди и сессии
живут в другой БД. В дефолтном режиме это ТОТ ЖЕ объект `Session`, что отдал бы
`get_db`, — поведение прода не меняется.
"""
from __future__ import annotations
import logging
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Header, HTTPException, Request
from sqlalchemy.orm import Session
from app.core.auth import UserScope, get_user_scope
from app.core.config import settings
from app.services.auth_session import get_db_role_scope, get_session_user
from app.services.identity_store import get_identity_db
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/me")
async def me(
request: Request,
db: Annotated[Session, Depends(get_identity_db)],
x_authenticated_user: Annotated[str | None, Header(alias="X-Authenticated-User")] = None,
) -> UserScope | dict[str, Any]:
"""Return the current user's RBAC scope (role + allowed/deny paths).
Return type is a union (не только `UserScope`) — `UserScope.role` — это
`Literal["admin","pilot","analyst","expired"]` (legacy roles.yaml names),
а DB-роли (реестр: tradein_users.role / auth.users.role) —
`"admin"/"manager"/"employee"`. FastAPI
строит response-схему из return-аннотации; жёсткий `UserScope` завернул бы
"employee"/"manager" в ResponseValidationError. Итоговая JSON-форма
ОДИНАКОВАЯ (те же 8 ключей) для обеих веток.
"""
token = request.cookies.get(settings.session_cookie_name)
if token:
try:
session_user = get_session_user(db, token)
except Exception:
logger.exception("me: session lookup failed")
session_user = None
if session_user is not None:
role = session_user["role"]
allowed_paths, deny_paths = get_db_role_scope(role)
return {
"username": session_user["username"],
"role": role,
"allowed_paths": allowed_paths,
"deny_paths": deny_paths,
"brand": None,
"display_name": session_user["display_name"],
"org": session_user["org_name"],
"email": session_user["email"],
}
if not x_authenticated_user:
raise HTTPException(
status_code=401,
detail="no authenticated user (valid session required)",
)
try:
return get_user_scope(x_authenticated_user)
except KeyError:
logger.warning(
"user %r authenticated via Caddy but missing from roles.yaml",
x_authenticated_user,
)
raise HTTPException(
status_code=403,
detail="user not in roles config",
) from None