diff --git a/CricketGame/backend/api/auth.py b/CricketGame/backend/api/auth.py index dd97d4b..d748cf7 100644 --- a/CricketGame/backend/api/auth.py +++ b/CricketGame/backend/api/auth.py @@ -1,9 +1,11 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Header, status from sqlalchemy.orm import Session from pydantic import BaseModel +import secrets import json from ..data.database import get_db +from ..core.config import ADMIN_SECRET from ..core.auth import register_player, login_player, get_player_stats, decode_token, create_token from ..data.models import Player, MatchHistory, TournamentHistory, FormatStats @@ -62,7 +64,15 @@ def _merge_format_stats(keeper: FormatStats, src: FormatStats) -> None: keeper.best_bowling_runs = src.best_bowling_runs @router.get("/migrate-formats") -def migrate_formats(db: Session = Depends(get_db)): +def migrate_formats( + x_admin_secret: str = Header(..., alias="X-Admin-Secret"), + db: Session = Depends(get_db), +): + if not secrets.compare_digest(x_admin_secret, ADMIN_SECRET): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Invalid admin secret" + ) + stats_merged_2v2 = 0 stats_deduped = 0 legacy_rows = db.query(FormatStats).filter(FormatStats.format == "2v2").all() diff --git a/CricketGame/backend/core/config.py b/CricketGame/backend/core/config.py index e36c09d..256d7a2 100644 --- a/CricketGame/backend/core/config.py +++ b/CricketGame/backend/core/config.py @@ -11,6 +11,12 @@ raise ValueError("SECRET_KEY is required in production") SECRET_KEY = "cricket-dev-key-2026" +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" + ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440")) diff --git a/CricketGame/backend/test_auth.py b/CricketGame/backend/test_auth.py new file mode 100644 index 0000000..e156c6b --- /dev/null +++ b/CricketGame/backend/test_auth.py @@ -0,0 +1,29 @@ +from fastapi.testclient import TestClient +from backend.main import app +from backend.core.config import ADMIN_SECRET +from backend.data.database import init_db + +# Initialize DB for tests +init_db() + +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 diff --git a/cricket.db b/cricket.db new file mode 100644 index 0000000..be81601 Binary files /dev/null and b/cricket.db differ