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 import APIRouter, HTTPException, Body, Response
|
||||||
from fastapi.params import Query
|
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.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
|
||||||
@ -16,6 +19,8 @@ from app.services.dailyquests import DailyQuestsService
|
|||||||
|
|
||||||
coins_service = CoinsService()
|
coins_service = CoinsService()
|
||||||
|
|
||||||
|
qr_logins_collection = db["qr_logins"]
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
tags=["Users"]
|
tags=["Users"]
|
||||||
)
|
)
|
||||||
@ -148,6 +153,33 @@ async def get_me(
|
|||||||
"""
|
"""
|
||||||
return await AuthService().get_current_user(accessToken, clientToken)
|
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
|
### daily reward
|
||||||
|
|
||||||
@router.post("/users/daily/claim")
|
@router.post("/users/daily/claim")
|
||||||
|
|||||||
@ -38,4 +38,8 @@ class VerifyCode(BaseModel):
|
|||||||
username: str
|
username: str
|
||||||
code: str
|
code: str
|
||||||
telegram_user_id: int
|
telegram_user_id: int
|
||||||
telegram_username: Optional[str] = None
|
telegram_username: Optional[str] = None
|
||||||
|
|
||||||
|
class QrApprove(BaseModel):
|
||||||
|
token: str
|
||||||
|
telegram_user_id: int
|
||||||
@ -2,6 +2,7 @@ import base64
|
|||||||
import json
|
import json
|
||||||
from fastapi import HTTPException, UploadFile
|
from fastapi import HTTPException, UploadFile
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
from app import db
|
||||||
from app.models.user import UserLogin, UserInDB, UserCreate, Session
|
from app.models.user import UserLogin, UserInDB, UserCreate, Session
|
||||||
from app.utils.misc import (
|
from app.utils.misc import (
|
||||||
verify_password,
|
verify_password,
|
||||||
@ -23,6 +24,8 @@ env_path = Path(__file__).parent.parent / ".env"
|
|||||||
load_dotenv(dotenv_path=env_path)
|
load_dotenv(dotenv_path=env_path)
|
||||||
FILES_URL = os.getenv("FILES_URL")
|
FILES_URL = os.getenv("FILES_URL")
|
||||||
|
|
||||||
|
qr_logins_collection = db["qr_logins"]
|
||||||
|
|
||||||
class AuthService:
|
class AuthService:
|
||||||
async def register(self, user: UserCreate):
|
async def register(self, user: UserCreate):
|
||||||
# Проверяем, существует ли пользователь
|
# Проверяем, существует ли пользователь
|
||||||
@ -380,3 +383,75 @@ class AuthService:
|
|||||||
"value": base64_textures
|
"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())
|
@dp.message(CommandStart())
|
||||||
async def start(message: Message, state: FSMContext, command: CommandObject):
|
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:
|
if command.args:
|
||||||
await state.update_data(username=command.args)
|
await state.update_data(username=command.args)
|
||||||
await state.set_state(Register.code)
|
await state.set_state(Register.code)
|
||||||
|
|||||||
Reference in New Issue
Block a user