Review
Territorial blackout for HarborCast Solent race streams
HarborCast's broadcast partners hold exclusive live rights for the Solent inshore series by territory. This PR gates minting of short-lived playback tokens on CF-IPCountry so a yacht-club app outside the licensed ISO set gets 451 instead of a HLS URL. Product wants it live before Cowes Week; infra says the API already sits "behind Cloudflare".
FastAPITier 2securitygeoipcloudflareheaders
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
app/playback/geo_gate.py+13-1
| 1 | 1 | from fastapi import APIRouter, HTTPException, Request | |
| 2 | 2 | ||
| 3 | 3 | from app.playback.tokens import mint_playback_token | |
| 4 | 4 | from app.races import get_race, territory_allowlist | |
| 5 | 5 | ||
| 6 | 6 | router = APIRouter(prefix="/races", tags=["playback"]) | |
| 7 | 7 | ||
| 8 | + | ||
| 9 | + | def viewer_country(request: Request) -> str: | |
| 10 | + | # Cloudflare stamps the ISO country on every edge request | |
| 11 | + | return (request.headers.get("CF-IPCountry") or "XX").upper() | |
| 12 | + | ||
| 8 | 13 | @router.get("/{race_id}/playback-token") | |
| 9 | - | async def playback_token(race_id: str): | |
| 14 | + | async def playback_token(race_id: str, request: Request): | |
| 10 | 15 | race = await get_race(race_id) | |
| 11 | 16 | if race is None: | |
| 12 | 17 | raise HTTPException(status_code=404, detail="race not found") | |
| 18 | + | country = viewer_country(request) | |
| 19 | + | allowed = await territory_allowlist(race_id) | |
| 20 | + | if country not in allowed: | |
| 21 | + | raise HTTPException( | |
| 22 | + | status_code=451, | |
| 23 | + | detail=f"stream not licensed in {country}", | |
| 24 | + | ) | |
| 13 | 25 | token = mint_playback_token(race_id, ttl_sec=90) | |
| 14 | 26 | return {"token": token, "expires_in": 90} |