Spaces:
Running
Running
| from typing import Annotated | |
| from fastapi import Depends, HTTPException, Request, status | |
| from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from app.config import get_settings | |
| from app.core.quota import DailyQuota | |
| from app.core.ratelimit import SlidingWindowLimiter, client_key, enforce | |
| from app.core.security import decode_token | |
| from app.database import get_db | |
| from app.models import ChatSession, User | |
| settings = get_settings() | |
| bearer_scheme = HTTPBearer(auto_error=False) | |
| HOUR = 3600 | |
| auth_limiter = SlidingWindowLimiter( | |
| settings.rate_limit_auth, settings.rate_limit_window_seconds | |
| ) | |
| chat_limiter = SlidingWindowLimiter( | |
| settings.rate_limit_chat, settings.rate_limit_window_seconds | |
| ) | |
| demo_session_limiter = SlidingWindowLimiter(settings.demo_sessions_per_hour, HOUR) | |
| demo_chat_limiter = SlidingWindowLimiter(settings.demo_messages_per_hour, HOUR) | |
| demo_daily_quota = DailyQuota(settings.demo_daily_message_cap) | |
| async def get_current_user( | |
| credentials: Annotated[ | |
| HTTPAuthorizationCredentials | None, Depends(bearer_scheme) | |
| ], | |
| db: Annotated[AsyncSession, Depends(get_db)], | |
| ) -> User: | |
| if credentials is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Not authenticated", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| user_id = decode_token(credentials.credentials, expected_type="access") | |
| if user_id is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid or expired token", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| user = await db.get(User, user_id) | |
| if user is None or not user.is_active: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found" | |
| ) | |
| return user | |
| CurrentUser = Annotated[User, Depends(get_current_user)] | |
| DB = Annotated[AsyncSession, Depends(get_db)] | |
| async def get_owned_session( | |
| session_id: str, user: CurrentUser, db: DB | |
| ) -> ChatSession: | |
| session = await db.get(ChatSession, session_id) | |
| if session is None or session.user_id != user.id: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, detail="Session not found" | |
| ) | |
| return session | |
| def rate_limit_auth(request: Request) -> None: | |
| enforce(auth_limiter, request) | |
| def rate_limit_chat(request: Request) -> None: | |
| enforce(chat_limiter, request) | |
| if not settings.demo_mode: | |
| return | |
| if not demo_chat_limiter.check(client_key(request)): | |
| raise HTTPException( | |
| status_code=status.HTTP_429_TOO_MANY_REQUESTS, | |
| detail=( | |
| f"Demo limit reached: {settings.demo_messages_per_hour} messages " | |
| "per hour. Run Synapse locally from the GitHub repo for unlimited use." | |
| ), | |
| ) | |
| if not demo_daily_quota.try_consume(): | |
| raise HTTPException( | |
| status_code=status.HTTP_429_TOO_MANY_REQUESTS, | |
| detail=( | |
| "The shared demo has reached its daily message budget. " | |
| "Please try again tomorrow, or run Synapse locally with your own key." | |
| ), | |
| ) | |
| def rate_limit_demo_session(request: Request) -> None: | |
| if not demo_session_limiter.check(client_key(request)): | |
| raise HTTPException( | |
| status_code=status.HTTP_429_TOO_MANY_REQUESTS, | |
| detail="Too many demo sessions from this address. Please try again later.", | |
| ) | |