95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
|
|
"""FastAPI dependencies: auth, db, tenant context, RBAC."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
import redis.asyncio as aioredis
|
||
|
|
from fastapi import Depends, HTTPException, Request, status
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.config import get_settings
|
||
|
|
from app.core.auth import get_redis, get_session_data
|
||
|
|
from app.core.db import get_db, set_tenant_context
|
||
|
|
|
||
|
|
|
||
|
|
async def get_redis_dep() -> aioredis.Redis:
|
||
|
|
"""FastAPI dependency for Redis client."""
|
||
|
|
return get_redis()
|
||
|
|
|
||
|
|
|
||
|
|
async def get_current_user(
|
||
|
|
request: Request,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
redis: aioredis.Redis = Depends(get_redis_dep),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""Get the current authenticated user from session cookie.
|
||
|
|
Returns session data dict with user_id, tenant_id, email, name, role.
|
||
|
|
"""
|
||
|
|
settings = get_settings()
|
||
|
|
session_id = request.cookies.get(settings.session_cookie_name)
|
||
|
|
if not session_id:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail={"detail": "Not authenticated", "code": "not_authenticated"},
|
||
|
|
)
|
||
|
|
|
||
|
|
session_data = await get_session_data(redis, session_id)
|
||
|
|
if session_data is None:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail={"detail": "Session expired or invalid", "code": "session_invalid"},
|
||
|
|
)
|
||
|
|
|
||
|
|
if not session_data.get("is_active", True):
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail={"detail": "User account deactivated", "code": "user_inactive"},
|
||
|
|
)
|
||
|
|
|
||
|
|
# Set RLS tenant context
|
||
|
|
tenant_id = uuid.UUID(session_data["tenant_id"])
|
||
|
|
await set_tenant_context(db, tenant_id)
|
||
|
|
|
||
|
|
return session_data
|
||
|
|
|
||
|
|
|
||
|
|
async def require_admin(
|
||
|
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""Require admin role."""
|
||
|
|
if current_user.get("role") != "admin":
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail={"detail": "Admin access required", "code": "forbidden"},
|
||
|
|
)
|
||
|
|
return current_user
|
||
|
|
|
||
|
|
|
||
|
|
async def require_write(
|
||
|
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""Require write permission (admin or editor)."""
|
||
|
|
role = current_user.get("role", "viewer")
|
||
|
|
if role not in ("admin", "editor"):
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail={"detail": "Write access required", "code": "forbidden"},
|
||
|
|
)
|
||
|
|
return current_user
|
||
|
|
|
||
|
|
|
||
|
|
async def get_tenant_id(
|
||
|
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||
|
|
) -> uuid.UUID:
|
||
|
|
"""Extract tenant_id from current user session."""
|
||
|
|
return uuid.UUID(current_user["tenant_id"])
|
||
|
|
|
||
|
|
|
||
|
|
async def get_current_user_id(
|
||
|
|
current_user: dict[str, Any] = Depends(get_current_user),
|
||
|
|
) -> uuid.UUID:
|
||
|
|
"""Extract user_id from current user session."""
|
||
|
|
return uuid.UUID(current_user["user_id"])
|