harden telegram webapp production readiness
This commit is contained in:
94
app/api/deps.py
Normal file
94
app/api/deps.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, Header, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.session import get_session
|
||||
from app.models.car import Car
|
||||
from app.models.user import User
|
||||
from app.services.telegram_auth import verify_webapp_init_data
|
||||
|
||||
|
||||
async def get_or_create_telegram_user(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
telegram_id: int,
|
||||
username: str | None = None,
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
locale: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> User:
|
||||
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||
user = result.scalar_one_or_none()
|
||||
payload = {
|
||||
"telegram_id": telegram_id,
|
||||
"username": str(telegram_id),
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"locale": locale,
|
||||
"currency": currency,
|
||||
}
|
||||
if user is None:
|
||||
user = User(**{key: value for key, value in payload.items() if value is not None})
|
||||
session.add(user)
|
||||
else:
|
||||
for field, value in payload.items():
|
||||
if value is not None:
|
||||
setattr(user, field, value)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
def require_internal_api_token(token: str | None) -> None:
|
||||
if not settings.internal_api_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Internal API token is not configured",
|
||||
)
|
||||
if not token or token != settings.internal_api_token:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
|
||||
|
||||
|
||||
async def get_current_telegram_user(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
x_telegram_init_data: Annotated[str | None, Header(alias="X-Telegram-Init-Data")] = None,
|
||||
x_internal_api_token: Annotated[str | None, Header(alias="X-Internal-API-Token")] = None,
|
||||
x_telegram_user_id: Annotated[int | None, Header(alias="X-Telegram-User-Id")] = None,
|
||||
x_dev_telegram_id: Annotated[int | None, Header(alias="X-Dev-Telegram-Id")] = None,
|
||||
) -> User:
|
||||
if x_telegram_init_data:
|
||||
user_data = verify_webapp_init_data(x_telegram_init_data, settings.bot_token)
|
||||
return await get_or_create_telegram_user(
|
||||
session,
|
||||
telegram_id=int(user_data["id"]),
|
||||
username=user_data.get("username"),
|
||||
first_name=user_data.get("first_name"),
|
||||
last_name=user_data.get("last_name"),
|
||||
locale=user_data.get("language_code"),
|
||||
)
|
||||
|
||||
if x_internal_api_token and x_telegram_user_id:
|
||||
require_internal_api_token(x_internal_api_token)
|
||||
return await get_or_create_telegram_user(session, telegram_id=x_telegram_user_id)
|
||||
|
||||
if settings.allow_dev_auth and not settings.is_production and x_dev_telegram_id:
|
||||
return await get_or_create_telegram_user(session, telegram_id=x_dev_telegram_id)
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Telegram initData required")
|
||||
|
||||
|
||||
async def get_owned_car(
|
||||
car_id: int,
|
||||
current_user: Annotated[User, Depends(get_current_telegram_user)],
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
) -> Car:
|
||||
car = await session.get(Car, car_id)
|
||||
if car is None:
|
||||
raise HTTPException(status_code=404, detail="Car not found")
|
||||
if car.owner_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
return car
|
||||
Reference in New Issue
Block a user