ppt-tool/backend/api/middlewares/auth_middleware.py
Vadym Samoilenko cf21ba4516 Phase 1-2: Foundation + Admin Panel & Client Management
Phase 1 (Foundation):
- Project restructure (presenton-main → backend/ + frontend/)
- Database schema (8 new models, Alembic config, seed script)
- Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware)
- RBAC (access_service, rbac_middleware, admin routers)
- Audit logging (fire-and-forget, AuditMiddleware, admin router)
- i18n (react-i18next with 5 namespace files)

Phase 2 (Admin Panel & Client Management):
- Admin panel shell (sidebar layout, role guard, 12 pages)
- Redux admin slice with 18 async thunks
- User management (role changes, deactivation)
- Client management (CRUD, brand config, team management)
- Brand config editor (colors, fonts, logos, voice rules)
- Master deck upload & parser (PPTX → HTML → React pipeline)
- Audit log viewer with filters and CSV/JSON export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:37:17 +00:00

82 lines
2.4 KiB
Python

import uuid
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from services.auth_service import AuthService
from services.database import async_session_maker
from models.sql.user import UserModel
# Paths that skip authentication
PUBLIC_PATH_PREFIXES = [
"/api/v1/auth/",
"/docs",
"/openapi.json",
"/api/health",
]
auth_service = AuthService()
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
path = request.url.path
# Skip auth for public paths
for prefix in PUBLIC_PATH_PREFIXES:
if path.startswith(prefix):
request.state.user = None
return await call_next(request)
# Skip auth for non-API paths (Next.js frontend routes)
if not path.startswith("/api/"):
request.state.user = None
return await call_next(request)
# Extract token from cookie or Authorization header
token = request.cookies.get("session_token")
if not token:
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header[7:]
if not token:
return JSONResponse(
status_code=401,
content={"detail": "Authentication required"},
)
# Validate JWT
claims = auth_service.validate_token(token)
if not claims:
return JSONResponse(
status_code=401,
content={"detail": "Invalid or expired token"},
)
# Load user from DB
try:
user_id = uuid.UUID(claims["sub"])
async with async_session_maker() as session:
user = await session.get(UserModel, user_id)
except (ValueError, KeyError):
return JSONResponse(
status_code=401,
content={"detail": "Invalid token payload"},
)
if not user:
return JSONResponse(
status_code=401,
content={"detail": "User not found"},
)
if not user.is_active:
return JSONResponse(
status_code=401,
content={"detail": "Account deactivated"},
)
request.state.user = user
return await call_next(request)