Files
responder/tests/test_secure_sessions.py
kennethreitz ca1c259ff9 v5 B3: secure-by-default sessions
- secret_key default is now None, not the public "NOTASECRET" (which now
  raises). sessions="auto" (default) signs with secret_key /
  RESPONDER_SECRET_KEY, else mints a random per-process key with a loud
  warning; sessions=True requires a real key; sessions=False disables sessions.
- Session cookies are Secure by default in production (session_https_only=None
  -> Secure unless debug); SameSite=None without Secure is rejected. New
  session_max_age knob.
- resolve_secret_key() + SessionConfigError in ext/sessions.py.
- req.session raises a guiding RuntimeError when sessions are disabled;
  resp.session is now a delegating property (removed from __slots__ + the
  eager bind), so resp.session = {...} writes through to the cookie.
- Migrated test fixtures (conftest + make_api + test_round7) to
  session_https_only=False so the http TestClient round-trips cookies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:37:14 -04:00

71 lines
2.0 KiB
Python

"""v5: secure-by-default session configuration."""
import pytest
from starlette.testclient import TestClient
import responder
KEY = "a-real-private-secret-key-32chars!"
def _client(api):
return TestClient(api, base_url="http://;")
def test_secure_flag_set_in_production():
# debug=False + session_https_only default None => Secure cookie.
api = responder.API(allowed_hosts=[";"], secret_key=KEY)
@api.route("/", methods=["POST"])
def view(req, resp):
req.session["k"] = "v"
resp.text = "ok"
r = _client(api).post("/")
assert "secure" in r.headers.get("set-cookie", "").lower()
def test_secure_flag_absent_when_disabled():
api = responder.API(allowed_hosts=[";"], secret_key=KEY, session_https_only=False)
@api.route("/", methods=["POST"])
def view(req, resp):
req.session["k"] = "v"
resp.text = "ok"
r = _client(api).post("/")
assert "secure" not in r.headers.get("set-cookie", "").lower()
def test_samesite_none_without_secure_raises():
with pytest.raises(ValueError, match="SameSite=None"):
responder.API(
allowed_hosts=[";"],
secret_key=KEY,
session_same_site="none",
session_https_only=False,
)
def test_sessions_true_without_key_raises(monkeypatch):
monkeypatch.delenv("RESPONDER_SECRET_KEY", raising=False)
from responder.ext.sessions import SessionConfigError
with pytest.raises((SessionConfigError, ValueError)):
responder.API(allowed_hosts=[";"], sessions=True)
def test_secret_key_from_env(monkeypatch):
monkeypatch.setenv("RESPONDER_SECRET_KEY", KEY)
api = responder.API(allowed_hosts=[";"], session_https_only=False)
assert api.secret_key == KEY
def test_session_backend_with_sessions_false_raises():
from responder.ext.sessions import MemorySessionBackend
with pytest.raises(ValueError, match="sessions=False"):
responder.API(
allowed_hosts=[";"], sessions=False, session_backend=MemorySessionBackend()
)