add: verify code in telegram
All checks were successful
Build and Deploy / deploy (push) Successful in 41s

This commit is contained in:
2025-07-21 08:07:21 +05:00
parent dd71c19c6b
commit c8d8c65251
7 changed files with 123 additions and 9 deletions

View File

@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Body, Response 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.models.request import ValidateRequest
from app.services.auth import AuthService from app.services.auth import AuthService
from app.db.database import users_collection, sessions_collection 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}с" safe_user["total_time_formatted"] = f"{hours}ч {minutes}м {seconds}с"
return safe_user 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)

View File

@ -1,10 +1,9 @@
from pydantic import BaseModel, EmailStr from pydantic import BaseModel
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
class UserCreate(BaseModel): class UserCreate(BaseModel):
username: str username: str
email: EmailStr
password: str password: str
class UserLogin(BaseModel): class UserLogin(BaseModel):
@ -13,7 +12,6 @@ class UserLogin(BaseModel):
class UserInDB(BaseModel): class UserInDB(BaseModel):
username: str username: str
email: EmailStr
hashed_password: str hashed_password: str
uuid: str uuid: str
skin_url: Optional[str] = None skin_url: Optional[str] = None
@ -23,9 +21,17 @@ class UserInDB(BaseModel):
total_time_played: int = 0 # Общее время игры в секундах total_time_played: int = 0 # Общее время игры в секундах
is_active: bool = True is_active: bool = True
created_at: datetime = datetime.utcnow() 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): class Session(BaseModel):
access_token: str access_token: str
client_token: str client_token: str
user_uuid: str user_uuid: str
expires_at: datetime expires_at: datetime
class VerifyCode(BaseModel):
username: str
code: str
telegram_chat_id: int

View File

@ -17,6 +17,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
from pathlib import Path from pathlib import Path
import secrets
env_path = Path(__file__).parent.parent / ".env" env_path = Path(__file__).parent.parent / ".env"
load_dotenv(dotenv_path=env_path) load_dotenv(dotenv_path=env_path)
@ -37,18 +38,59 @@ class AuthService:
# Сохраняем в MongoDB # Сохраняем в MongoDB
new_user = UserInDB( new_user = UserInDB(
username=user.username, username=user.username,
email=user.email,
hashed_password=hashed_password, hashed_password=hashed_password,
uuid=user_uuid, uuid=user_uuid,
is_verified=False,
code=None,
code_expires_at=None
) )
await users_collection.insert_one(new_user.dict()) await users_collection.insert_one(new_user.dict())
return {"status": "success", "uuid": user_uuid} 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): async def login(self, credentials: UserLogin):
# Ищем пользователя # Ищем пользователя
user = await users_collection.find_one({"username": credentials.username}) user = await users_collection.find_one({"username": credentials.username})
if not user or not verify_password(credentials.password, user["hashed_password"]): if not user or not verify_password(credentials.password, user["hashed_password"]):
raise HTTPException(status_code=401, detail="Invalid credentials") 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"]}) access_token = create_access_token({"sub": user["username"], "uuid": user["uuid"]})

View File

@ -13,6 +13,19 @@ services:
- .env - .env
depends_on: depends_on:
- mongodb - 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: mongodb:
container_name: mongodb container_name: mongodb

View File

@ -12,5 +12,3 @@ RUN mkdir -p /app/static/skins /app/static/capes /app/static/capes_store && \
chown -R 1000:1000 /app/static chown -R 1000:1000 /app/static
EXPOSE 3000 EXPOSE 3000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]

View File

@ -8,6 +8,7 @@ python-multipart>=0.0.9
mongoengine>=0.24.2 mongoengine>=0.24.2
python-dotenv>=1.0.0 python-dotenv>=1.0.0
pydantic>=2.0.0 pydantic>=2.0.0
pydantic[email]>=2.0.0
cryptography>=43.0.0 cryptography>=43.0.0
pytelegrambotapi>=2.0.0
httpx>=0.27.2

46
telegram_bot.py Normal file
View File

@ -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)