add: verify code in telegram
All checks were successful
Build and Deploy / deploy (push) Successful in 41s
All checks were successful
Build and Deploy / deploy (push) Successful in 41s
This commit is contained in:
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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"]})
|
||||||
|
@ -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
|
||||||
|
@ -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"]
|
|
||||||
|
@ -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
46
telegram_bot.py
Normal 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)
|
Reference in New Issue
Block a user