From 159d024a3241b1725181bafaaf2e3ce337c1f15b Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sat, 26 Sep 2026 16:52:28 +0000 Subject: [PATCH] fix(session): warn when cookie_same_site="none" lacks cookie_https_only Browsers reject a SameSite=None cookie without the Secure flag, so this configuration silently never stored the session cookie. SessionConfig now emits a UserWarning at validation time. The combination is still accepted; rejecting it would break existing configurations. Closes #167 --- CHANGELOG.md | 7 ++++++- docs/SESSION.md | 2 +- fastapi_cachex/session/config.py | 19 +++++++++++++++++++ i18n/zh-TW/docs/SESSION.md | 2 +- tests/session/test_config.py | 22 ++++++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6314a8d..80ce4a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,7 +153,12 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. like a miss, `set()` dropped the write, `increment()` returned 0 and `delete_if_equals()` raised `TypeError`. A failed server is now taken out of rotation at once and tried again after one second, instead of after 60. - ([#197](https://github.com/allen0099/FastAPI-CacheX/issues/197)) + ([#197](https://github.com/allen0099/FastAPI-CacheX/issues/197))- **`SessionConfig` warns when `cookie_same_site="none"` is set without + `cookie_https_only=True`.** Browsers reject a `SameSite=None` cookie that is + not `Secure`, so the session cookie was silently never stored. The + combination is still accepted. + ([#167](https://github.com/allen0099/FastAPI-CacheX/issues/167)) + ## [0.3.7] - 2026-09-25 diff --git a/docs/SESSION.md b/docs/SESSION.md index f08e702..20eee69 100644 --- a/docs/SESSION.md +++ b/docs/SESSION.md @@ -418,7 +418,7 @@ SessionConfig( * 60 * 60, # None = no Max-Age (cookie ends with the browser session) cookie_path="/", - cookie_same_site="lax", # "lax" / "strict" / "none" + cookie_same_site="lax", # "lax" / "strict" / "none" ("none" needs cookie_https_only=True) cookie_https_only=False, # True adds the Secure flag cookie_domain=None, # None = no Domain attribute ) diff --git a/fastapi_cachex/session/config.py b/fastapi_cachex/session/config.py index c8f4683..8ebb7af 100644 --- a/fastapi_cachex/session/config.py +++ b/fastapi_cachex/session/config.py @@ -1,6 +1,7 @@ """Session configuration settings.""" import ipaddress +import warnings from functools import lru_cache from typing import Literal @@ -9,6 +10,7 @@ from pydantic import Field from pydantic import SecretStr from pydantic import field_validator +from pydantic import model_validator SameSitePolicy = Literal["lax", "strict", "none"] @@ -224,6 +226,23 @@ def is_trusted_proxy(self, address: str) -> bool: return True return False + @model_validator(mode="after") + def _warn_insecure_same_site_none(self) -> "SessionConfig": + """Warn about a SameSite=None cookie without the Secure flag. + + Browsers drop such a cookie, so the session would silently never stick. + Rejecting the combination would break existing configurations. + """ + if self.cookie_same_site == "none" and not self.cookie_https_only: + warnings.warn( + 'cookie_same_site="none" requires cookie_https_only=True: browsers ' + "reject a SameSite=None cookie without the Secure flag, so the " + "session cookie would never be stored.", + UserWarning, + stacklevel=3, + ) + return self + @field_validator("jwt_algorithm") @classmethod def _check_jwt_algorithm(cls, value: str) -> str: diff --git a/i18n/zh-TW/docs/SESSION.md b/i18n/zh-TW/docs/SESSION.md index a08fff5..6843232 100644 --- a/i18n/zh-TW/docs/SESSION.md +++ b/i18n/zh-TW/docs/SESSION.md @@ -369,7 +369,7 @@ SessionConfig( * 60 * 60, # None = 不設 Max-Age(Cookie 隨瀏覽器工作階段結束) cookie_path="/", - cookie_same_site="lax", # "lax" / "strict" / "none" + cookie_same_site="lax", # "lax" / "strict" / "none"("none" 需要 cookie_https_only=True) cookie_https_only=False, # True 會加上 Secure 旗標 cookie_domain=None, # None = 不設 Domain 屬性 ) diff --git a/tests/session/test_config.py b/tests/session/test_config.py index 4c58f07..c2b0f65 100644 --- a/tests/session/test_config.py +++ b/tests/session/test_config.py @@ -1,5 +1,7 @@ """Tests for SessionConfig validation.""" +import warnings + import pytest from pydantic import ValidationError @@ -24,3 +26,23 @@ def test_session_config_rejects_unknown_fields() -> None: with pytest.raises(ValidationError): SessionConfig(secret_key="a" * 32, enable_csrf=True) # type: ignore[call-arg] + + +def test_same_site_none_without_https_only_warns() -> None: + """Browsers drop a SameSite=None cookie that is not Secure (#167).""" + with pytest.warns(UserWarning, match='cookie_same_site="none"'): + SessionConfig(secret_key="a" * 32, cookie_same_site="none") + + +@pytest.mark.parametrize( + ("same_site", "https_only"), + [("none", True), ("lax", False), ("strict", False)], +) +def test_other_cookie_settings_do_not_warn(same_site, https_only) -> None: + with warnings.catch_warnings(): + warnings.simplefilter("error") + SessionConfig( + secret_key="a" * 32, + cookie_same_site=same_site, + cookie_https_only=https_only, + )