Auth: nedokoncena registracia sa da dokoncit novym QR kodom
Ucet bez potvrdeneho kodu (auth_token is NULL a totp_last_step == 0) uz neblokuje meno: opakovany register_account vyda novy secret (stary QR prestane platit) a login vyhodi RegistrationIncomplete, na ktoru server odpovie novym QR -- klient sa prepne na registracny tab. Admin statistiky vykazuju nedokoncene registracie osobitne, Hraci celkom pocita len potvrdene ucty. Novy event register v analytike. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+35
-6
@@ -30,6 +30,18 @@ class AuthError(Exception):
|
||||
"""Chyba prihlasenia/registracie (slovenska sprava pre klienta)."""
|
||||
|
||||
|
||||
class RegistrationIncomplete(AuthError):
|
||||
"""Ucet existuje, ale registracia nebola nikdy potvrdena kodom.
|
||||
|
||||
Handler v api/__init__.py na nu reaguje novym QR kodom namiesto chyby.
|
||||
"""
|
||||
|
||||
|
||||
def _is_unconfirmed(player: Player) -> bool:
|
||||
"""Ucet, ktory nikdy neoveril TOTP kod (confirm_account nastavuje oboje)."""
|
||||
return player.auth_token is None and player.totp_last_step == 0
|
||||
|
||||
|
||||
def _new_token() -> str:
|
||||
return secrets.token_urlsafe(48)
|
||||
|
||||
@@ -74,7 +86,11 @@ def _verify_code(player: Player, code: str) -> None:
|
||||
|
||||
|
||||
async def register_account(username: str) -> dict:
|
||||
"""Zaregistruje meno a vygeneruje TOTP secret. Vrati otpauth URI pre QR."""
|
||||
"""Zaregistruje meno a vygeneruje TOTP secret. Vrati otpauth URI pre QR.
|
||||
|
||||
Nedokoncenu registraciu (meno existuje, ale kod nebol nikdy potvrdeny)
|
||||
prepise novym secretom -- povodny QR kod tym prestane platit.
|
||||
"""
|
||||
username = (username or "").strip()
|
||||
if not username:
|
||||
raise AuthError("Zadajte meno.")
|
||||
@@ -83,9 +99,12 @@ async def register_account(username: str) -> dict:
|
||||
existing = await session.scalar(
|
||||
select(Player).where(Player.username == username)
|
||||
)
|
||||
if existing is not None:
|
||||
if existing is not None and not _is_unconfirmed(existing):
|
||||
raise AuthError("Toto meno je už obsadené.")
|
||||
session.add(Player(username=username, totp_secret=crypto.encrypt(secret)))
|
||||
if existing is not None:
|
||||
existing.totp_secret = crypto.encrypt(secret)
|
||||
else:
|
||||
session.add(Player(username=username, totp_secret=crypto.encrypt(secret)))
|
||||
await session.commit()
|
||||
otpauth_uri = pyotp.TOTP(secret).provisioning_uri(name=username, issuer_name=ISSUER)
|
||||
return {"username": username, "secret": secret, "otpauth_uri": otpauth_uri}
|
||||
@@ -97,11 +116,17 @@ async def confirm_account(username: str, code: str) -> dict:
|
||||
|
||||
|
||||
async def login(username: str, code: str) -> dict:
|
||||
"""Prihlasi existujuci ucet a vrati session token."""
|
||||
return await _verify_and_issue_token(username, code)
|
||||
"""Prihlasi existujuci ucet a vrati session token.
|
||||
|
||||
Pre nedokoncenu registraciu vyhodi RegistrationIncomplete namiesto
|
||||
overovania kodu -- pouzivatel bez naskenovaneho QR ziadny kod nema.
|
||||
"""
|
||||
return await _verify_and_issue_token(username, code, unconfirmed_ok=False)
|
||||
|
||||
|
||||
async def _verify_and_issue_token(username: str, code: str) -> dict:
|
||||
async def _verify_and_issue_token(
|
||||
username: str, code: str, *, unconfirmed_ok: bool = True
|
||||
) -> dict:
|
||||
username = (username or "").strip()
|
||||
_check_lockout(username)
|
||||
async with async_session() as session:
|
||||
@@ -109,6 +134,10 @@ async def _verify_and_issue_token(username: str, code: str) -> dict:
|
||||
if player is None:
|
||||
_register_failure(username)
|
||||
raise AuthError("Účet neexistuje.")
|
||||
if not unconfirmed_ok and _is_unconfirmed(player):
|
||||
raise RegistrationIncomplete(
|
||||
"Registrácia nie je dokončená — naskenuj QR kód a potvrď prvým kódom."
|
||||
)
|
||||
try:
|
||||
_verify_code(player, (code or "").strip())
|
||||
except AuthError:
|
||||
|
||||
Reference in New Issue
Block a user