Fix: capture Google avatar on returning sign-in (+ userinfo fallback)
find_or_create_user returned early when the identity already existed, so a returning Google sign-in never refreshed the profile picture (the name had been set earlier, at link time — which is why name worked but avatar stayed null). Now profile bits refresh on every sign-in. Also fall back to the OIDC userinfo endpoint for the picture if the ID token omits it. 119 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from datetime import datetime, timezone
|
||||
|
||||
AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
USERINFO_URL = "https://openidconnect.googleapis.com/v1/userinfo"
|
||||
_VALID_ISS = {"accounts.google.com", "https://accounts.google.com"}
|
||||
|
||||
|
||||
@@ -75,6 +76,19 @@ def exchange_code(code: str, redirect_uri: str, code_verifier: str) -> dict:
|
||||
return json.loads(response.read())
|
||||
|
||||
|
||||
def fetch_userinfo(access_token: str) -> dict:
|
||||
"""Call the OIDC userinfo endpoint — a reliable source for the profile picture
|
||||
if the ID token happens to omit it. Best-effort; returns {} on any error."""
|
||||
request = urllib.request.Request(
|
||||
USERINFO_URL, headers={"Authorization": f"Bearer {access_token}", "Accept": "application/json"}
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=10) as response:
|
||||
return json.loads(response.read())
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _decode_jwt_payload(token: str) -> dict:
|
||||
parts = token.split(".")
|
||||
if len(parts) != 3:
|
||||
|
||||
Reference in New Issue
Block a user