Files
remnawave-bedolaga-telegram…/app/webapi/routes/websocket.py
PEDZEO 6b69ec750e feat: add cabinet (personal account) backend API
- Add JWT authentication for cabinet users
- Add Telegram WebApp authentication
- Add subscription management endpoints
- Add balance and transactions endpoints
- Add referral system endpoints
- Add tickets support for cabinet
- Add webhooks and websocket for real-time updates
- Add email verification service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 23:20:20 +03:00

114 lines
4.3 KiB
Python

from __future__ import annotations
import json
import logging
from typing import Any
from fastapi import APIRouter, Security, WebSocket, WebSocketDisconnect
from fastapi.security import APIKeyHeader
from app.services.event_emitter import event_emitter
from app.services.web_api_token_service import web_api_token_service
from app.database.database import AsyncSessionLocal
logger = logging.getLogger(__name__)
router = APIRouter()
api_key_header_scheme = APIKeyHeader(name="X-API-Key", auto_error=False)
async def verify_websocket_token(
websocket: WebSocket,
token: str | None = None,
) -> bool:
"""Проверить токен для WebSocket подключения."""
if not token:
# Пытаемся получить токен из query параметров
token = websocket.query_params.get("token") or websocket.query_params.get("api_key")
if not token:
return False
async with AsyncSessionLocal() as db:
try:
webhook_token = await web_api_token_service.authenticate(
db,
token,
remote_ip=websocket.client.host if websocket.client else None,
)
if webhook_token:
logger.debug("WebSocket token authenticated successfully")
else:
logger.warning("WebSocket token authentication failed: token not found or invalid")
return webhook_token is not None
except Exception as error:
logger.warning("WebSocket authentication error: %s", error, exc_info=True)
return False
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint для real-time обновлений."""
client_host = websocket.client.host if websocket.client else "unknown"
logger.info("WebSocket connection attempt from %s", client_host)
# Сначала проверяем авторизацию ДО принятия соединения
token = websocket.query_params.get("token") or websocket.query_params.get("api_key")
if not token:
logger.warning("WebSocket: No token provided from %s", client_host)
await websocket.close(code=1008, reason="Unauthorized: No token provided")
return
if not await verify_websocket_token(websocket, token):
logger.warning("WebSocket: Invalid token from %s", client_host)
await websocket.close(code=1008, reason="Unauthorized: Invalid token")
return
# Только после успешной проверки принимаем соединение
try:
await websocket.accept()
logger.info("WebSocket connection accepted from %s", client_host)
except Exception as e:
logger.error("WebSocket: Failed to accept connection from %s: %s", client_host, e)
return
# Регистрируем подключение
event_emitter.register_websocket(websocket)
try:
# Отправляем приветственное сообщение
await websocket.send_json({
"type": "connection",
"status": "connected",
"message": "WebSocket connection established",
})
# Обрабатываем входящие сообщения (ping/pong для keepalive)
while True:
try:
data = await websocket.receive_text()
message = json.loads(data)
# Обработка ping
if message.get("type") == "ping":
await websocket.send_json({"type": "pong"})
# Можно добавить другие типы сообщений (подписки на конкретные события и т.д.)
except json.JSONDecodeError:
logger.warning("Invalid JSON received from WebSocket client")
except WebSocketDisconnect:
break
except Exception as error:
logger.exception("Error processing WebSocket message: %s", error)
except WebSocketDisconnect:
logger.info("WebSocket client disconnected")
except Exception as error:
logger.exception("WebSocket error: %s", error)
finally:
# Отменяем регистрацию при отключении
event_emitter.unregister_websocket(websocket)