import httpx from app.core.config import settings GOOGLE_CERTS_URL = "https://oauth2.googleapis.com/tokeninfo" # Google OpenID Connect issuers — accept both forms per the discovery # document (https://accounts.google.com/.well-known/openid-configuration). _GOOGLE_VALID_ISSUERS = frozenset({ "accounts.google.com", "https://accounts.google.com", }) async def verify_google_id_token(id_token: str) -> dict: """Verify Google id_token and return user info. Returns dict with: sub, email, email_verified, name, picture, etc. """ if not settings.GOOGLE_CLIENT_ID: raise ValueError("GOOGLE_CLIENT_ID is not configured") # Pre-launch P1 fix (Round 1, SEC-001): pin ``trust_env=False`` so a # rogue ``HTTPS_PROXY`` env var (or attacker-controlled K8s/EC2 # process env) cannot MitM the SSO bootstrap. Every other outbound # driver in the codebase already uses this; the SSO verify call # was the last gap and the highest-consequence one because it # gates identity for the whole platform. ``follow_redirects=False`` # matches the SF / Feishu pattern from PR #206 — ``tokeninfo`` # answers 200 directly, never 30x. async with httpx.AsyncClient( timeout=10.0, trust_env=False, follow_redirects=False, ) as client: resp = await client.get(GOOGLE_CERTS_URL, params={"id_token": id_token}) if resp.status_code != 200: raise ValueError("Invalid Google id_token") payload = resp.json() if payload.get("aud") != settings.GOOGLE_CLIENT_ID: raise ValueError("Token audience mismatch") # Defence in depth: even though ``oauth2.googleapis.com/tokeninfo`` # only validates Google-issued tokens, an unverified ``iss`` claim # is a recurring source of OIDC bypass bugs. Reject anything that # didn't come from accounts.google.com. if payload.get("iss") not in _GOOGLE_VALID_ISSUERS: raise ValueError("Token issuer mismatch") ev = payload.get("email_verified") if ev not in ("true", True): raise ValueError("Email not verified") return payload