From c8d8c65251e7ba8b1fa7715fa2b589a76cbda9cf Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Mon, 21 Jul 2025 08:07:21 +0500 Subject: [PATCH] add: verify code in telegram --- app/api/users.py | 10 +++++++++- app/models/user.py | 14 ++++++++++---- app/services/auth.py | 44 +++++++++++++++++++++++++++++++++++++++++- docker-compose.yml | 13 +++++++++++++ dockerfile | 2 -- requirements.txt | 3 ++- telegram_bot.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 telegram_bot.py diff --git a/app/api/users.py b/app/api/users.py index ff0757b..f7ab0e6 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -1,5 +1,5 @@ from fastapi import APIRouter, HTTPException, Body, Response -from app.models.user import UserCreate, UserLogin +from app.models.user import UserCreate, UserLogin, VerifyCode from app.models.request import ValidateRequest from app.services.auth import AuthService from app.db.database import users_collection, sessions_collection @@ -117,3 +117,11 @@ async def get_user_by_uuid(uuid: str): safe_user["total_time_formatted"] = f"{hours}ч {minutes}м {seconds}с" return safe_user + +@router.post("/auth/verify_code") +async def verify_code(verify_code: VerifyCode): + return await AuthService().verify_code(verify_code.username, verify_code.code, verify_code.telegram_chat_id) + +@router.post("/auth/generate_code") +async def generate_code(username: str): + return await AuthService().generate_code(username) diff --git a/app/models/user.py b/app/models/user.py index f041fb3..e49038c 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,10 +1,9 @@ -from pydantic import BaseModel, EmailStr +from pydantic import BaseModel from datetime import datetime from typing import Optional class UserCreate(BaseModel): username: str - email: EmailStr password: str class UserLogin(BaseModel): @@ -13,7 +12,6 @@ class UserLogin(BaseModel): class UserInDB(BaseModel): username: str - email: EmailStr hashed_password: str uuid: str skin_url: Optional[str] = None @@ -23,9 +21,17 @@ class UserInDB(BaseModel): total_time_played: int = 0 # Общее время игры в секундах is_active: bool = True created_at: datetime = datetime.utcnow() - + code: Optional[str] = None + telegram_id: Optional[str] = None + is_verified: bool = False + code_expires_at: Optional[datetime] = None class Session(BaseModel): access_token: str client_token: str user_uuid: str expires_at: datetime + +class VerifyCode(BaseModel): + username: str + code: str + telegram_chat_id: int diff --git a/app/services/auth.py b/app/services/auth.py index 2faf691..aa2e24b 100644 --- a/app/services/auth.py +++ b/app/services/auth.py @@ -17,6 +17,7 @@ from cryptography.hazmat.primitives.asymmetric import padding from dotenv import load_dotenv import os from pathlib import Path +import secrets env_path = Path(__file__).parent.parent / ".env" load_dotenv(dotenv_path=env_path) @@ -37,18 +38,59 @@ class AuthService: # Сохраняем в MongoDB new_user = UserInDB( username=user.username, - email=user.email, hashed_password=hashed_password, uuid=user_uuid, + is_verified=False, + code=None, + code_expires_at=None ) await users_collection.insert_one(new_user.dict()) return {"status": "success", "uuid": user_uuid} + + async def generate_code(self, username: str): + if await users_collection.find_one({"username": username}): + if await users_collection.find_one({"username": username, "is_verified": True}): + raise HTTPException(400, "User already verified") + code = secrets.token_hex(3).upper() + await users_collection.update_one({"username": username}, {"$set": {"code": code, "code_expires_at": datetime.utcnow() + timedelta(minutes=10)}}) + return {"status": "success", "code": code} + else: + raise HTTPException(404, "User not found") + + async def verify_code(self, username: str, code: str, telegram_chat_id: int): + user = await users_collection.find_one({"username": username}) + if not user: + raise HTTPException(404, "User not found") + + if user["is_verified"]: + raise HTTPException(400, "User already verified") + + # Проверяем код и привязку к Telegram + if user.get("telegram_chat_id") and user["telegram_chat_id"] != telegram_chat_id: + raise HTTPException(403, "This account is linked to another Telegram") + + if user.get("code") != code: + raise HTTPException(400, "Invalid code") + + # Обновляем chat_id при первом подтверждении + await users_collection.update_one( + {"username": username}, + {"$set": { + "is_verified": True, + "telegram_chat_id": telegram_chat_id, + "code": None + }} + ) + return {"status": "success"} async def login(self, credentials: UserLogin): # Ищем пользователя user = await users_collection.find_one({"username": credentials.username}) if not user or not verify_password(credentials.password, user["hashed_password"]): raise HTTPException(status_code=401, detail="Invalid credentials") + + if not user["is_verified"]: + raise HTTPException(status_code=401, detail="User not verified") # Генерируем токены access_token = create_access_token({"sub": user["username"], "uuid": user["uuid"]}) diff --git a/docker-compose.yml b/docker-compose.yml index 06f0e6c..639bb08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,19 @@ services: - .env depends_on: - mongodb + command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"] + + telegram_bot: + container_name: telegram_bot + build: + context: . + dockerfile: Dockerfile + user: "${UID:-1000}:${GID:-1000}" + volumes: + - ./telegram_bot.py:/app/telegram_bot.py + env_file: + - .env + command: ["python", "telegram_bot.py"] mongodb: container_name: mongodb diff --git a/dockerfile b/dockerfile index c065589..0a292c4 100644 --- a/dockerfile +++ b/dockerfile @@ -12,5 +12,3 @@ RUN mkdir -p /app/static/skins /app/static/capes /app/static/capes_store && \ chown -R 1000:1000 /app/static EXPOSE 3000 - -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"] diff --git a/requirements.txt b/requirements.txt index f70d9a0..32255b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ python-multipart>=0.0.9 mongoengine>=0.24.2 python-dotenv>=1.0.0 pydantic>=2.0.0 -pydantic[email]>=2.0.0 cryptography>=43.0.0 +pytelegrambotapi>=2.0.0 +httpx>=0.27.2 diff --git a/telegram_bot.py b/telegram_bot.py new file mode 100644 index 0000000..1702b5b --- /dev/null +++ b/telegram_bot.py @@ -0,0 +1,46 @@ +from telebot import TeleBot +import httpx +import os +from dotenv import load_dotenv + +load_dotenv() + +bot = TeleBot(os.getenv("TELEGRAM_BOT_TOKEN")) +API_URL = os.getenv("API_URL") + +user_states = {} # {"chat_id": {"username": "DIKER0K"}} + +@bot.message_handler(commands=['start']) +def start(message): + bot.reply_to(message, "🔑 Введите ваш игровой никнейм:") + bot.register_next_step_handler(message, process_username) + +def process_username(message): + user_states[message.chat.id] = {"username": message.text.strip()} + bot.reply_to(message, "📋 Теперь введите код из лаунчера:") + +@bot.message_handler(func=lambda m: m.chat.id in user_states) +def verify_code(message): + username = user_states[message.chat.id]["username"] + code = message.text.strip() + print(username, code, message.chat.id) + + try: + response = httpx.post( + f"{API_URL}/auth/verify_code", + json={"username": username, "code": code, "telegram_chat_id": message.chat.id}, # JSON-сериализация автоматически + headers={"Content-Type": "application/json"} # Необязательно, httpx добавляет сам + ) + print(response.json()) + if response.status_code == 200: + bot.reply_to(message, "✅ Аккаунт подтвержден!") + else: + bot.reply_to(message, f"❌ Ошибка: {response.json().get('detail')}") + except Exception: + print(API_URL) + bot.reply_to(message, "⚠️ Сервер недоступен") + + del user_states[message.chat.id] + +if __name__ == "__main__": + bot.polling(none_stop=True)