security: require verified email for admin + harden sessions
build-image / test (push) Successful in 49s
build-image / build (push) Successful in 19s

Admin (email allowlist) is now gated on a verified email and re-evaluated per
request instead of trusting a frozen cookie flag; session max_age cut to 8h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 10:39:05 -04:00
parent 67f631ac50
commit ecf4df88d3
3 changed files with 49 additions and 5 deletions
+24
View File
@@ -15,6 +15,30 @@ USER = {"sub": "sub-user", "email": "user@x.com", "name": "User", "is_admin": Fa
ADMIN = {"sub": "sub-admin", "email": "admin@x.com", "name": "Admin", "is_admin": True}
def test_current_user_admin_requires_verified_email():
"""is_admin is recomputed per request and requires a VERIFIED allowlisted email."""
from types import SimpleNamespace
cfg = SimpleNamespace(admin_emails=frozenset({"admin@x.com"}))
def req(session_user):
return SimpleNamespace(
session={"user": session_user} if session_user else {},
app=SimpleNamespace(state=SimpleNamespace(cfg=cfg)),
)
# verified + allowlisted -> admin
assert appmod.current_user(req({"email": "admin@x.com", "email_verified": True}))["is_admin"] is True
# allowlisted but NOT verified -> not admin (the security fix)
assert appmod.current_user(req({"email": "admin@x.com", "email_verified": False}))["is_admin"] is False
# missing email_verified claim -> not admin
assert appmod.current_user(req({"email": "admin@x.com"}))["is_admin"] is False
# verified but not on the allowlist -> not admin
assert appmod.current_user(req({"email": "user@x.com", "email_verified": True}))["is_admin"] is False
# no session user -> None
assert appmod.current_user(req(None)) is None
@pytest.fixture
def app_url(tmp_path, monkeypatch):
monkeypatch.setenv("INTERVALS_ENC_KEY", KEY)