fix: resolve comprehensive review findings (security, concurrency, perf, Android, UI)
Multi-dimension review of v0.8.2. Excludes the deliberately deferred default_config.yaml weak-default-key item (C1). Backend: - calibration: create_default_calibration no longer exceeds led_count for small odd counts (bounded trim + regression test) - game-integration: generic webhook now requires auth_token; constant-time compare_digest in all adapters; per-IP failed-auth rate limit on the ingest route; auth_token encrypted at rest via secret_box (migration-safe) - playlist engine: serialize _state/_task under the lifecycle lock to close a delete-mid-play race (+ concurrency tests) - main: stop the calibration session on shutdown (restore prior target) - home_assistant: validate HA host via the LAN classifier on create/update - perf: drop slow preview-WS clients instead of blocking the send loop; cache composite full-strip resize linspaces; effect_stream lava reuses scratch Frontend: - setup/auto-calibration wizard: guard _state after awaits (cancel-safe), await session teardown before output start, busy-gate skip-calibration, manual display input keeps focus, move focus on step change - calibration: destroy EntitySelect on modal close - color-strips test: dirty-flag-gated render + cached ctx/ImageData - a11y/TV: focus-visible for new wizard/auto-cal/corner controls, aria-labels on the spatial corner/edge picker; theme-aware syntax tokens; dead/undefined CSS tokens removed; .modal-error styled; i18n titles (en/ru/zh) Android: - ApiKeyManager: EncryptedSharedPreferences with verified, data-safe legacy migration that never rotates an existing key - CaptureService: validate MediaProjection token before promoting; satisfy the startForeground 5s contract on the bail path - NotificationListener: connection-scoped executor with lazy fallback - BLE: request BLUETOOTH_SCAN/CONNECT at runtime + guard handler-thread SecurityExceptions - Root: cancellation-aware su grant probe Adds 14 tests. Gate: ruff + tsc 5.9.3 + esbuild + pytest (2185 passed) + compileDebugKotlin all green.
This commit is contained in:
@@ -341,6 +341,64 @@ class TestEventIngestion:
|
||||
assert len(recent) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failed-auth rate limiting (brute-force defence on the ingest route)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _reset_auth_fail_limiter():
|
||||
"""Clear the module-level failed-auth hit map before and after each test.
|
||||
|
||||
The limiter keeps per-IP state in a process-global dict, so without this
|
||||
reset, attempts from earlier tests would bleed into later ones.
|
||||
"""
|
||||
from ledgrab.api.routes import game_integration as gi
|
||||
|
||||
gi._auth_fail_hits.clear()
|
||||
yield
|
||||
gi._auth_fail_hits.clear()
|
||||
|
||||
|
||||
class TestIngestRateLimiting:
|
||||
def test_failed_auth_attempts_are_rate_limited(self, client, _reset_auth_fail_limiter):
|
||||
from ledgrab.api.routes import game_integration as gi
|
||||
|
||||
created = _create_integration(
|
||||
client,
|
||||
adapter_config={"auth_token": "correct_token"},
|
||||
)
|
||||
integration_id = created["id"]
|
||||
url = f"/api/v1/game-integrations/{integration_id}/event"
|
||||
bad = {"x-auth-token": "wrong_token"}
|
||||
|
||||
# Burn through the failed-auth budget — each returns 403.
|
||||
for _ in range(gi._AUTH_FAIL_LIMIT):
|
||||
resp = client.post(url, json={"data": {"health": 1}}, headers=bad)
|
||||
assert resp.status_code == 403
|
||||
|
||||
# The next attempt from the same IP is throttled with 429.
|
||||
resp = client.post(url, json={"data": {"health": 1}}, headers=bad)
|
||||
assert resp.status_code == 429
|
||||
|
||||
def test_successful_ingest_not_rate_limited(self, client, event_bus, _reset_auth_fail_limiter):
|
||||
from ledgrab.api.routes import game_integration as gi
|
||||
|
||||
created = _create_integration(
|
||||
client,
|
||||
adapter_config={"auth_token": "correct_token"},
|
||||
)
|
||||
integration_id = created["id"]
|
||||
url = f"/api/v1/game-integrations/{integration_id}/event"
|
||||
good = {"x-auth-token": "correct_token"}
|
||||
|
||||
# High-rate legitimate ingestion well past the failed-auth threshold
|
||||
# must NOT be throttled — only failures count toward the limit.
|
||||
for _ in range(gi._AUTH_FAIL_LIMIT + 10):
|
||||
resp = client.post(url, json={"data": {"health": 50}}, headers=good)
|
||||
assert resp.status_code == 204
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Status / diagnostics tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user