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
-10
@@ -57,24 +57,37 @@ class HistoryCase(unittest.TestCase):
|
||||
for _ in range(n):
|
||||
username = "u_" + uuid.uuid4().hex[:8]
|
||||
data = run(auth.register_account(username))
|
||||
ident = run(auth.login(username, pyotp.TOTP(data["secret"]).now()))
|
||||
ident = run(auth.confirm_account(username, pyotp.TOTP(data["secret"]).now()))
|
||||
ids.append(ident["player_id"])
|
||||
return ids
|
||||
|
||||
@staticmethod
|
||||
def _next_step_code(secret):
|
||||
"""Kod pre NASLEDUJUCI casovy krok -- confirm uz spotreboval aktualny."""
|
||||
return pyotp.TOTP(secret).at((auth._current_step() + 1) * auth.TOTP_PERIOD)
|
||||
|
||||
def test_register_login_token(self):
|
||||
username = "alice_" + uuid.uuid4().hex[:6]
|
||||
data = run(auth.register_account(username))
|
||||
self.assertIn("otpauth_uri", data)
|
||||
|
||||
# Zle meno je obsadene
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.register_account(username))
|
||||
# Nedokoncena registracia: opakovany register vyda NOVY secret
|
||||
data = run(auth.register_account(username))
|
||||
self.assertIn("otpauth_uri", data)
|
||||
|
||||
# Login pred potvrdenim -> RegistrationIncomplete (novy QR namiesto chyby)
|
||||
with self.assertRaises(auth.RegistrationIncomplete):
|
||||
run(auth.login(username, "000000"))
|
||||
|
||||
code = pyotp.TOTP(data["secret"]).now()
|
||||
ident = run(auth.login(username, code))
|
||||
ident = run(auth.confirm_account(username, code))
|
||||
self.assertEqual(ident["username"], username)
|
||||
self.assertTrue(ident["token"])
|
||||
|
||||
# Po potvrdeni je uz meno obsadene
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.register_account(username))
|
||||
|
||||
# Token sa da spatne rozlustit na identitu
|
||||
resolved = run(auth.player_by_token(ident["token"]))
|
||||
self.assertEqual(resolved["player_id"], ident["player_id"])
|
||||
@@ -83,29 +96,41 @@ class HistoryCase(unittest.TestCase):
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.login(username, "000000"))
|
||||
|
||||
def test_reissued_secret_invalidates_old_qr(self):
|
||||
username = "fred_" + uuid.uuid4().hex[:6]
|
||||
old = run(auth.register_account(username))
|
||||
new = run(auth.register_account(username))
|
||||
self.assertNotEqual(old["secret"], new["secret"])
|
||||
|
||||
# Kod zo stareho QR uz neplati, z noveho ano
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.confirm_account(username, pyotp.TOTP(old["secret"]).now()))
|
||||
ident = run(auth.confirm_account(username, pyotp.TOTP(new["secret"]).now()))
|
||||
self.assertEqual(ident["username"], username)
|
||||
|
||||
def test_login_lockout_after_repeated_failures(self):
|
||||
username = "bob_" + uuid.uuid4().hex[:6]
|
||||
data = run(auth.register_account(username))
|
||||
run(auth.confirm_account(username, pyotp.TOTP(data["secret"]).now()))
|
||||
|
||||
for _ in range(auth._LOGIN_ATTEMPT_LIMIT):
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.login(username, "000000"))
|
||||
|
||||
# Lockout odmietne aj spravny kod, kym neubehne okno
|
||||
code = pyotp.TOTP(data["secret"]).now()
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.login(username, code))
|
||||
run(auth.login(username, self._next_step_code(data["secret"])))
|
||||
|
||||
def test_successful_login_clears_failed_attempts(self):
|
||||
username = "carol_" + uuid.uuid4().hex[:6]
|
||||
data = run(auth.register_account(username))
|
||||
run(auth.confirm_account(username, pyotp.TOTP(data["secret"]).now()))
|
||||
|
||||
for _ in range(auth._LOGIN_ATTEMPT_LIMIT - 1):
|
||||
with self.assertRaises(auth.AuthError):
|
||||
run(auth.login(username, "000000"))
|
||||
|
||||
code = pyotp.TOTP(data["secret"]).now()
|
||||
ident = run(auth.login(username, code))
|
||||
ident = run(auth.login(username, self._next_step_code(data["secret"])))
|
||||
self.assertEqual(ident["username"], username)
|
||||
self.assertNotIn(username, auth._failed_attempts)
|
||||
|
||||
@@ -241,7 +266,7 @@ class HistoryCase(unittest.TestCase):
|
||||
def test_auth_token_stored_hashed_not_plaintext(self):
|
||||
username = "erin_" + uuid.uuid4().hex[:6]
|
||||
data = run(auth.register_account(username))
|
||||
ident = run(auth.login(username, pyotp.TOTP(data["secret"]).now()))
|
||||
ident = run(auth.confirm_account(username, pyotp.TOTP(data["secret"]).now()))
|
||||
|
||||
async def _raw_token():
|
||||
async with async_session() as session:
|
||||
|
||||
Reference in New Issue
Block a user