sec: server-side email domain check + IP block on violations

Only @tutlot.com emails allowed for registration (checked server-side,
invisible to frontend inspect). Wrong domain → scary message + IP
violation tracked. 5 violations → IP permanently blocked from login
and registration. Block screen with OK button on frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Gros Frumos 2026-03-21 15:58:16 +02:00
parent 47b89ded8d
commit 0562cb4e47
8 changed files with 123 additions and 30 deletions

View file

@ -33,7 +33,7 @@ _WEBHOOK_SECRET = "test-webhook-secret"
_WEBHOOK_HEADERS = {"X-Telegram-Bot-Api-Secret-Token": _WEBHOOK_SECRET}
_VALID_PAYLOAD = {
"email": "user@example.com",
"email": "user@tutlot.com",
"login": "testuser",
"password": "strongpassword123",
}
@ -68,7 +68,7 @@ async def test_auth_register_fire_and_forget_telegram_error_still_returns_201():
):
resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "other@example.com", "login": "otheruser"},
json={**_VALID_PAYLOAD, "email": "other@tutlot.com", "login": "otheruser"},
)
await asyncio.sleep(0)
@ -106,7 +106,7 @@ async def test_auth_register_409_on_duplicate_login():
r2 = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "different@example.com"},
json={**_VALID_PAYLOAD, "email": "different@tutlot.com"},
)
assert r2.status_code == 409, f"Expected 409 on duplicate login, got {r2.status_code}"
@ -365,7 +365,7 @@ async def test_register_without_push_subscription():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "nopush@example.com", "login": "nopushuser"},
json={**_VALID_PAYLOAD, "email": "nopush@tutlot.com", "login": "nopushuser"},
)
assert resp.status_code == 201
assert resp.json()["status"] == "pending"
@ -424,7 +424,7 @@ async def test_webhook_callback_approve_edits_message():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
reg_resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "edit@example.com", "login": "edituser"},
json={**_VALID_PAYLOAD, "email": "edit@tutlot.com", "login": "edituser"},
)
assert reg_resp.status_code == 201
@ -469,7 +469,7 @@ async def test_webhook_callback_answer_sent():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
reg_resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "answer@example.com", "login": "answeruser"},
json={**_VALID_PAYLOAD, "email": "answer@tutlot.com", "login": "answeruser"},
)
assert reg_resp.status_code == 201
@ -562,7 +562,7 @@ async def test_password_hash_stored_in_pbkdf2_format():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "pbkdf2@example.com", "login": "pbkdf2user"},
json={**_VALID_PAYLOAD, "email": "pbkdf2@tutlot.com", "login": "pbkdf2user"},
)
async with aiosqlite.connect(_cfg.DB_PATH) as conn:
@ -601,7 +601,7 @@ async def test_webhook_callback_double_approve_does_not_send_push():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
reg_resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "double@example.com", "login": "doubleuser", "push_subscription": push_sub},
json={**_VALID_PAYLOAD, "email": "double@tutlot.com", "login": "doubleuser", "push_subscription": push_sub},
)
assert reg_resp.status_code == 201
@ -653,7 +653,7 @@ async def test_webhook_callback_double_approve_status_stays_approved():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
reg_resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "stay@example.com", "login": "stayuser"},
json={**_VALID_PAYLOAD, "email": "stay@tutlot.com", "login": "stayuser"},
)
assert reg_resp.status_code == 201
@ -698,7 +698,7 @@ async def test_webhook_callback_approve_after_reject_status_stays_rejected():
with patch("backend.telegram.send_registration_notification", new_callable=AsyncMock):
reg_resp = await client.post(
"/api/auth/register",
json={**_VALID_PAYLOAD, "email": "artest@example.com", "login": "artestuser"},
json={**_VALID_PAYLOAD, "email": "artest@tutlot.com", "login": "artestuser"},
)
assert reg_resp.status_code == 201
@ -759,7 +759,7 @@ async def test_auth_register_rate_limit_fourth_request_returns_429():
r = await client.post(
"/api/auth/register",
json={
"email": f"ratetest{i}@example.com",
"email": f"ratetest{i}@tutlot.com",
"login": f"ratetest{i}",
"password": "strongpassword123",
},
@ -770,7 +770,7 @@ async def test_auth_register_rate_limit_fourth_request_returns_429():
r4 = await client.post(
"/api/auth/register",
json={
"email": "ratetest4@example.com",
"email": "ratetest4@tutlot.com",
"login": "ratetest4",
"password": "strongpassword123",
},