"""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: ` через `header_up` в каждом reverse_proxy. Frontend дёргает /me чтобы понять кому что показывать. #2552: session-first. Валидная DB-session cookie (см. app.services.auth_session) отдаёт scope из tradein_users (role/display_name/org/email) БЕЗ похода в roles.yaml. Без cookie (или невалидная/истёкшая) — legacy X-Authenticated-User путь, БЕЗ ИЗМЕНЕНИЙ (regression недопустим — существующие тесты держат его бит-в-бит). """ 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.core.db import get_db from app.services.auth_session import get_db_role_scope, get_session_user logger = logging.getLogger(__name__) router = APIRouter() @router.get("/me") async def me( request: Request, db: Annotated[Session, Depends(get_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) — `"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 (Caddy basic_auth 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