15728c3bcb
- Capture the Google profile picture (picture claim) into users.avatar_url; an Avatar component shows it, falling back to the initial. Used in the desktop header and the mobile "You" tab (which now shows the user when signed in). - Move account/settings to its own route /account (robust + scrolls to top), reached by the desktop avatar and the mobile You tab; drop the inline "You" sheet. AccountPanel gains a Sign out action; the page links to Saved/History/ Boundaries via home intent params (?view= / ?open=). - db: users.avatar_url (schema + idempotent migration). 118 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
2.0 KiB
Python
53 lines
2.0 KiB
Python
import base64
|
|
import hashlib
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from goodnews import oauth_google as g
|
|
|
|
|
|
def _seg(d: dict) -> str:
|
|
return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b"=").decode()
|
|
|
|
|
|
def _token(claims: dict) -> str:
|
|
return f"{_seg({'alg': 'RS256'})}.{_seg(claims)}.sig"
|
|
|
|
|
|
def test_pkce_challenge_matches_verifier():
|
|
verifier, challenge = g.new_pkce()
|
|
expected = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
|
|
assert challenge == expected
|
|
|
|
|
|
def test_auth_url_has_required_params(monkeypatch):
|
|
monkeypatch.setenv("GOODNEWS_GOOGLE_CLIENT_ID", "cid")
|
|
url = g.auth_url("https://x/cb", "state123", "chal")
|
|
for needle in ["client_id=cid", "redirect_uri=https", "state=state123",
|
|
"code_challenge=chal", "code_challenge_method=S256", "scope=openid"]:
|
|
assert needle in url
|
|
|
|
|
|
def test_verify_id_token_happy(monkeypatch):
|
|
monkeypatch.setenv("GOODNEWS_GOOGLE_CLIENT_ID", "cid")
|
|
tok = _token({"iss": "https://accounts.google.com", "aud": "cid", "exp": 9999999999,
|
|
"sub": "g-123", "email": "a@b.com", "email_verified": True, "name": "A",
|
|
"picture": "https://x/p.jpg"})
|
|
info = g.verify_id_token(tok)
|
|
assert info == {"sub": "g-123", "email": "a@b.com", "name": "A", "picture": "https://x/p.jpg"}
|
|
|
|
|
|
def test_verify_id_token_rejects(monkeypatch):
|
|
monkeypatch.setenv("GOODNEWS_GOOGLE_CLIENT_ID", "cid")
|
|
base = {"iss": "https://accounts.google.com", "aud": "cid", "exp": 9999999999,
|
|
"sub": "g", "email": "a@b.com", "email_verified": True}
|
|
with pytest.raises(ValueError): # wrong audience
|
|
g.verify_id_token(_token({**base, "aud": "other"}))
|
|
with pytest.raises(ValueError): # bad issuer
|
|
g.verify_id_token(_token({**base, "iss": "evil.com"}))
|
|
with pytest.raises(ValueError): # expired
|
|
g.verify_id_token(_token({**base, "exp": 1}))
|
|
with pytest.raises(ValueError): # email not verified
|
|
g.verify_id_token(_token({**base, "email_verified": False}))
|