Merge PRs #14, #15, #16: auth fix, async CPU moves, lobby a11y#17
Merge PRs #14, #15, #16: auth fix, async CPU moves, lobby a11y#17
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Horus0305 <98160215+Horus0305@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR merges three previously-validated changesets into main: securing the /auth/migrate-formats maintenance endpoint, preventing event-loop blocking during CPU turns in the realtime server, and improving Lobby accessibility.
Changes:
- Added
X-Admin-Secretheader validation to protect/auth/migrate-formats, plus a new auth test file. - Offloaded synchronous CPU move selection to
asyncio.to_thread()in realtime CPU flows. - Added ARIA labels/tooltips/focus-visible ring styles for icon-only Lobby buttons; cleaned up
package-lock.json.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| CricketGame/backend/api/auth.py | Requires X-Admin-Secret for /auth/migrate-formats and rejects invalid secrets. |
| CricketGame/backend/core/config.py | Introduces ADMIN_SECRET config behavior for admin-protected routes. |
| CricketGame/backend/realtime/cpu.py | Wraps CPU move selection in asyncio.to_thread() to avoid blocking the event loop. |
| CricketGame/backend/test_game_engine.py | Adjusts a test mock signature to match updated call patterns. |
| CricketGame/backend/test_auth.py | Adds tests for the new admin-secret protection on /auth/migrate-formats. |
| CricketGame/frontend/src/components/Lobby.tsx | Improves accessibility for icon-only controls with ARIA labels/titles and focus styles. |
| CricketGame/frontend/package-lock.json | Removes redundant "peer": true entries. |
Files not reviewed (1)
- CricketGame/frontend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ADMIN_SECRET = os.getenv("ADMIN_SECRET") | ||
| if not ADMIN_SECRET: | ||
| if APP_ENV in ("production", "prod"): | ||
| raise ValueError("ADMIN_SECRET is required in production") | ||
| ADMIN_SECRET = "super-secret-admin-key" |
There was a problem hiding this comment.
ADMIN_SECRET falls back to a hardcoded value when unset. That makes the /auth/migrate-formats admin protection effectively guessable in any non-production environment that’s accidentally exposed. Prefer requiring ADMIN_SECRET in all environments, or generate a random per-process value (and log it only for local dev) so there isn’t a shared default secret across deployments.
| client = TestClient(app) | ||
|
|
||
| def test_migrate_formats_no_header(): | ||
| # Expect 422 because the header is required (Header(...)) | ||
| response = client.get("/auth/migrate-formats") | ||
| assert response.status_code == 422 | ||
|
|
||
| def test_migrate_formats_wrong_header(): | ||
| # Expect 403 because the secret is invalid | ||
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": "wrong-secret"}) | ||
| assert response.status_code == 403 | ||
| assert response.json()["detail"] == "Invalid admin secret" | ||
|
|
||
| def test_migrate_formats_correct_header(): | ||
| # Expect 200 because the secret matches | ||
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": ADMIN_SECRET}) | ||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert "format_stats_2v2_merged" in data | ||
| assert "duplicate_rows_removed" in data | ||
| assert "match_history_fixed" in data |
There was a problem hiding this comment.
This test module creates a global TestClient(app) at import time and never closes it. Since the app has a lifespan that starts the learning processor, this can leak background threads/resources and may cause the test process to hang or interfere with other tests. Use a context-managed client (with TestClient(app) as client:) or explicitly call client.close()/provide a per-test fixture so lifespan shutdown runs reliably.
| client = TestClient(app) | |
| def test_migrate_formats_no_header(): | |
| # Expect 422 because the header is required (Header(...)) | |
| response = client.get("/auth/migrate-formats") | |
| assert response.status_code == 422 | |
| def test_migrate_formats_wrong_header(): | |
| # Expect 403 because the secret is invalid | |
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": "wrong-secret"}) | |
| assert response.status_code == 403 | |
| assert response.json()["detail"] == "Invalid admin secret" | |
| def test_migrate_formats_correct_header(): | |
| # Expect 200 because the secret matches | |
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": ADMIN_SECRET}) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "format_stats_2v2_merged" in data | |
| assert "duplicate_rows_removed" in data | |
| assert "match_history_fixed" in data | |
| def test_migrate_formats_no_header(): | |
| # Expect 422 because the header is required (Header(...)) | |
| with TestClient(app) as client: | |
| response = client.get("/auth/migrate-formats") | |
| assert response.status_code == 422 | |
| def test_migrate_formats_wrong_header(): | |
| # Expect 403 because the secret is invalid | |
| with TestClient(app) as client: | |
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": "wrong-secret"}) | |
| assert response.status_code == 403 | |
| assert response.json()["detail"] == "Invalid admin secret" | |
| def test_migrate_formats_correct_header(): | |
| # Expect 200 because the secret matches | |
| with TestClient(app) as client: | |
| response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": ADMIN_SECRET}) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "format_stats_2v2_merged" in data | |
| assert "duplicate_rows_removed" in data | |
| assert "match_history_fixed" in data |
| data = response.json() | ||
| assert "format_stats_2v2_merged" in data | ||
| assert "duplicate_rows_removed" in data | ||
| assert "match_history_fixed" in data |
There was a problem hiding this comment.
This new test file uses pytest-style discovery, but the existing backend tests in this repo are standalone scripts with run_all_tests() + a __main__ block (and pytest isn’t listed in backend/requirements.txt). As-is, this file likely won’t be executed by the current test workflow. Either adapt it to the repo’s script-style convention or add pytest + CI wiring so these tests actually run.
| assert "match_history_fixed" in data | |
| assert "match_history_fixed" in data | |
| def run_all_tests() -> None: | |
| """ | |
| Run all tests in this module. | |
| This mirrors the standalone script-style convention used by other backend tests, | |
| so the existing test workflow can execute these tests without pytest. | |
| """ | |
| test_migrate_formats_no_header() | |
| test_migrate_formats_wrong_header() | |
| test_migrate_formats_correct_header() | |
| if __name__ == "__main__": | |
| run_all_tests() |
Incorporates three validated open PRs into main. All had clean merge state and successful Vercel preview deployments.
PR #14 — Fix missing auth on
/auth/migrate-formats(CRITICAL)/auth/migrate-formatswas publicly accessible — anyone could trigger DB migrations or cause DoS via repeated callsX-Admin-Secretheader requirement withsecrets.compare_digest(timing-safe)ADMIN_SECRETsourced from env var; hard failure in production if unset, dev fallback otherwisetest_auth.pycovering missing/invalid/valid secret scenariosPR #15 — Non-blocking CPU move selection
cpu_pick_moveruns synchronous DB queries directly on the asyncio event loop, blocking all WebSocket clients during CPU turnsasyncio.to_thread(), consistent with existing patterns inrealtime/cpu.pyfixed_do_tossnow acceptscaller=None)PR #16 — Lobby accessibility improvements
aria-label+titleto each;focus-visible:ring-2for keyboard navigation visibilitypackage-lock.jsoncleanup (redundant"peer": trueentries removed)Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/graphql/usr/bin/gh gh auth status(http block)If you need me to access, download, or install something from one of these locations, you can either:
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.