test qr code
This commit is contained in:
@ -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")
|
||||
|
||||
@ -39,3 +39,7 @@ class VerifyCode(BaseModel):
|
||||
code: str
|
||||
telegram_user_id: int
|
||||
telegram_username: Optional[str] = None
|
||||
|
||||
class QrApprove(BaseModel):
|
||||
token: str
|
||||
telegram_user_id: int
|
||||
@ -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"]}
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user