kin: BATON-SEC-003 Добавить аутентификацию на /api/signal

This commit is contained in:
Gros Frumos 2026-03-21 08:16:46 +02:00
parent 4b37703335
commit 6142770c0c
2 changed files with 57 additions and 5 deletions

View file

@ -40,6 +40,8 @@ _UUID_5 = "aa000005-0000-4000-8000-000000000005"
_UUID_6 = "aa000006-0000-4000-8000-000000000006" _UUID_6 = "aa000006-0000-4000-8000-000000000006"
_UUID_7 = "aa000007-0000-4000-8000-000000000007" _UUID_7 = "aa000007-0000-4000-8000-000000000007"
_UUID_8 = "aa000008-0000-4000-8000-000000000008" _UUID_8 = "aa000008-0000-4000-8000-000000000008"
_UUID_9 = "aa000009-0000-4000-8000-000000000009"
_UUID_10 = "aa00000a-0000-4000-8000-00000000000a"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -206,6 +208,48 @@ async def test_old_api_key_invalid_after_re_register():
assert new_resp.status_code == 200, "Новый ключ должен работать" assert new_resp.status_code == 200, "Новый ключ должен работать"
# ---------------------------------------------------------------------------
# Criterion 5 (task brief) — Token from another user → 401
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_signal_with_other_user_token_returns_401():
"""POST /api/signal с токеном другого пользователя должен вернуть 401.
Невозможно отправить сигнал от чужого имени даже зная UUID.
"""
async with make_app_client() as client:
# Регистрируем двух пользователей
r_a = await client.post("/api/register", json={"uuid": _UUID_9, "name": "UserA"})
r_b = await client.post("/api/register", json={"uuid": _UUID_10, "name": "UserB"})
assert r_a.status_code == 200
assert r_b.status_code == 200
api_key_a = r_a.json()["api_key"]
api_key_b = r_b.json()["api_key"]
# UserA пытается отправить сигнал с токеном UserB
resp_a_with_b_key = await client.post(
"/api/signal",
json={"user_id": _UUID_9, "timestamp": 1742478000000},
headers={"Authorization": f"Bearer {api_key_b}"},
)
# UserB пытается отправить сигнал с токеном UserA
resp_b_with_a_key = await client.post(
"/api/signal",
json={"user_id": _UUID_10, "timestamp": 1742478000000},
headers={"Authorization": f"Bearer {api_key_a}"},
)
assert resp_a_with_b_key.status_code == 401, (
"Нельзя отправить сигнал от имени UserA с токеном UserB"
)
assert resp_b_with_a_key.status_code == 401, (
"Нельзя отправить сигнал от имени UserB с токеном UserA"
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Criterion 8 — SHA-256 hash is stored, not the raw key # Criterion 8 — SHA-256 hash is stored, not the raw key
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -302,26 +302,34 @@ async def test_different_x_real_ip_values_have_independent_counters():
Verifies that rate-limit keys are truly per-IP. Verifies that rate-limit keys are truly per-IP.
""" """
async with make_app_client() as client: async with make_app_client() as client:
await client.post( r_a = await client.post(
"/api/register", json={"uuid": _UUID_XREALIP_A, "name": "IPA"} "/api/register", json={"uuid": _UUID_XREALIP_A, "name": "IPA"}
) )
await client.post( r_b = await client.post(
"/api/register", json={"uuid": _UUID_XREALIP_B, "name": "IPB"} "/api/register", json={"uuid": _UUID_XREALIP_B, "name": "IPB"}
) )
api_key_a = r_a.json()["api_key"]
api_key_b = r_b.json()["api_key"]
# Exhaust limit for IP-A # Exhaust limit for IP-A (with valid auth so requests reach the rate limiter)
for _ in range(11): for _ in range(11):
await client.post( await client.post(
"/api/signal", "/api/signal",
json={"user_id": _UUID_XREALIP_A, "timestamp": 1742478000000}, json={"user_id": _UUID_XREALIP_A, "timestamp": 1742478000000},
headers={"X-Real-IP": "198.51.100.100"}, headers={
"X-Real-IP": "198.51.100.100",
"Authorization": f"Bearer {api_key_a}",
},
) )
# IP-B has its own independent counter — must not be blocked # IP-B has its own independent counter — must not be blocked
r = await client.post( r = await client.post(
"/api/signal", "/api/signal",
json={"user_id": _UUID_XREALIP_B, "timestamp": 1742478000000}, json={"user_id": _UUID_XREALIP_B, "timestamp": 1742478000000},
headers={"X-Real-IP": "198.51.100.200"}, headers={
"X-Real-IP": "198.51.100.200",
"Authorization": f"Bearer {api_key_b}",
},
) )
assert r.status_code == 200, ( assert r.status_code == 200, (