test qr code

This commit is contained in:
2025-12-20 15:48:15 +05:00
parent 41711d68c8
commit e035334417
4 changed files with 124 additions and 2 deletions

View File

@ -1,6 +1,9 @@
import os
import secrets
from fastapi import APIRouter, HTTPException, Body, Response
from fastapi.params import Query
from app.models.user import UserCreate, UserLogin, VerifyCode
from app import db
from app.models.user import QrApprove, UserCreate, UserLogin, VerifyCode
from app.models.request import ValidateRequest
from app.services.auth import AuthService
from app.db.database import users_collection, sessions_collection
@ -16,6 +19,8 @@ from app.services.dailyquests import DailyQuestsService
coins_service = CoinsService()
qr_logins_collection = db["qr_logins"]
router = APIRouter(
tags=["Users"]
)
@ -148,6 +153,33 @@ async def get_me(
"""
return await AuthService().get_current_user(accessToken, clientToken)
@router.post("/auth/qr/init")
async def qr_init(device_id: str | None = Query(default=None)):
token = secrets.token_urlsafe(24)
expires_at = datetime.utcnow() + timedelta(minutes=2)
await qr_logins_collection.insert_one({
"token": token,
"device_id": device_id,
"status": "pending",
"approved_username": None,
"created_at": datetime.utcnow(),
"expires_at": expires_at,
})
# deep-link в бота
BOT_USERNAME = os.getenv("TELEGRAM_BOT_USERNAME")
qr_url = f"https://t.me/{BOT_USERNAME}?start=qr_{token}"
return {"token": token, "qr_url": qr_url, "expires_at": expires_at.isoformat()}
@router.post("/auth/qr/approve")
async def qr_approve(payload: QrApprove):
return await AuthService().approve_qr_login(payload.token, payload.telegram_user_id)
@router.get("/auth/qr/status")
async def qr_status(token: str = Query(...), device_id: str | None = Query(default=None)):
return await AuthService().qr_status(token, device_id)
### daily reward
@router.post("/users/daily/claim")

View File

@ -38,4 +38,8 @@ class VerifyCode(BaseModel):
username: str
code: str
telegram_user_id: int
telegram_username: Optional[str] = None
telegram_username: Optional[str] = None
class QrApprove(BaseModel):
token: str
telegram_user_id: int

View File

@ -2,6 +2,7 @@ import base64
import json
from fastapi import HTTPException, UploadFile
from fastapi.responses import JSONResponse
from app import db
from app.models.user import UserLogin, UserInDB, UserCreate, Session
from app.utils.misc import (
verify_password,
@ -23,6 +24,8 @@ env_path = Path(__file__).parent.parent / ".env"
load_dotenv(dotenv_path=env_path)
FILES_URL = os.getenv("FILES_URL")
qr_logins_collection = db["qr_logins"]
class AuthService:
async def register(self, user: UserCreate):
# Проверяем, существует ли пользователь
@ -380,3 +383,75 @@ class AuthService:
"value": base64_textures
}]
}
async def approve_qr_login(self, token: str, telegram_user_id: int):
qr = await qr_logins_collection.find_one({"token": token})
if not qr:
raise HTTPException(404, "QR token not found")
if qr["status"] != "pending":
raise HTTPException(400, "QR token already used or not pending")
if datetime.utcnow() > qr["expires_at"]:
await qr_logins_collection.update_one({"token": token}, {"$set": {"status": "expired"}})
raise HTTPException(400, "QR token expired")
# находим пользователя по telegram_user_id
user = await users_collection.find_one({"telegram_user_id": telegram_user_id})
if not user:
raise HTTPException(403, "Telegram аккаунт не привязан")
if not user.get("is_verified"):
raise HTTPException(403, "Пользователь не верифицирован")
await qr_logins_collection.update_one(
{"token": token},
{"$set": {"status": "approved", "approved_username": user["username"]}}
)
return {"status": "success"}
async def qr_status(self, token: str, device_id: str | None = None):
qr = await qr_logins_collection.find_one({"token": token})
if not qr:
raise HTTPException(404, "QR token not found")
if datetime.utcnow() > qr["expires_at"] and qr["status"] == "pending":
await qr_logins_collection.update_one({"token": token}, {"$set": {"status": "expired"}})
return {"status": "expired"}
# если хотите привязку к устройству:
if device_id and qr.get("device_id") and qr["device_id"] != device_id:
raise HTTPException(403, "Device mismatch")
if qr["status"] == "approved":
username = qr["approved_username"]
user = await users_collection.find_one({"username": username})
if not user:
raise HTTPException(404, "User not found")
# генерим токены как в login()
access_token = create_access_token({"sub": user["username"], "uuid": user["uuid"]})
client_token = str(uuid.uuid4())
session = Session(
access_token=access_token,
client_token=client_token,
user_uuid=user["uuid"],
expires_at=datetime.utcnow() + timedelta(minutes=1440),
)
await sessions_collection.insert_one(session.dict())
# одноразовость
await qr_logins_collection.update_one(
{"token": token},
{"$set": {"status": "consumed"}}
)
return {
"status": "ok",
"accessToken": access_token,
"clientToken": client_token,
"selectedProfile": {"id": user["uuid"], "name": user["username"]},
}
return {"status": qr["status"]}

View File

@ -29,6 +29,17 @@ class Register(StatesGroup):
@dp.message(CommandStart())
async def start(message: Message, state: FSMContext, command: CommandObject):
if command.args and command.args.startswith("qr_"):
token = command.args.removeprefix("qr_").strip()
tg_user = message.from_user
try:
await auth_service.approve_qr_login(token=token, telegram_user_id=tg_user.id)
await message.answer("✅ Вход подтверждён. Вернитесь в лаунчер.")
except Exception as e:
await message.answer(f"Не удалось подтвердить вход: {e}")
return
# старое поведение регистрации/верификации:
if command.args:
await state.update_data(username=command.args)
await state.set_state(Register.code)