feat(config): SESSION_COOKIE_SECURE leggibile dall'ambiente (#38.4)
config.py: default invariato (sicuro salvo FLASK_DEBUG=1); nuova env SESSION_COOKIE_SECURE=false la spegne esplicitamente, per l'installazione on-premise dove nginx serve HTTP in LAN - un cookie Secure su origine non HTTPS viene scartato dal browser in silenzio e il login non regge mai. .env.example documenta la variabile (commentata, comportamento invariato di default). docker-compose e default dev restano al lotto C2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,11 @@ STATION_CODE=ST-DEFAULT
|
|||||||
# floor the station identity comes from the local install, and measuring against
|
# floor the station identity comes from the local install, and measuring against
|
||||||
# another station's recipes would silently break traceability.
|
# another station's recipes would silently break traceability.
|
||||||
STATION_SWITCH_ENABLED=0
|
STATION_SWITCH_ENABLED=0
|
||||||
|
# Cookie di sessione "Secure": di default segue FLASK_DEBUG (sicuro salvo
|
||||||
|
# debug). Impostare a false solo per l'installazione on-premise che serve
|
||||||
|
# HTTP in LAN (#38.4) - un cookie Secure su un'origine non-HTTPS viene
|
||||||
|
# scartato dal browser in silenzio e il login non regge mai.
|
||||||
|
# SESSION_COOKIE_SECURE=false
|
||||||
|
|
||||||
# --- Vision ---
|
# --- Vision ---
|
||||||
# Internal address of the vision worker. Never exposed outside tmflow-net.
|
# Internal address of the vision worker. Never exposed outside tmflow-net.
|
||||||
|
|||||||
@@ -23,7 +23,18 @@ class Config:
|
|||||||
# Session
|
# Session
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
SESSION_COOKIE_SAMESITE = "Lax"
|
SESSION_COOKIE_SAMESITE = "Lax"
|
||||||
SESSION_COOKIE_SECURE = not DEBUG # Only secure cookies in production (HTTPS)
|
# Default: secure unless FLASK_DEBUG=1 (unchanged). Explicit override via
|
||||||
|
# SESSION_COOKIE_SECURE=false for the on-premise install (#38.4): nginx
|
||||||
|
# there listens on plain HTTP inside the LAN, and a cookie marked Secure
|
||||||
|
# is silently dropped by the browser on a non-HTTPS origin - login looks
|
||||||
|
# like it works and the session never sticks.
|
||||||
|
_session_cookie_secure_env = os.getenv("SESSION_COOKIE_SECURE")
|
||||||
|
if _session_cookie_secure_env is not None:
|
||||||
|
SESSION_COOKIE_SECURE = _session_cookie_secure_env.strip().lower() not in (
|
||||||
|
"false", "0", "no",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
SESSION_COOKIE_SECURE = not DEBUG
|
||||||
PERMANENT_SESSION_LIFETIME = 28800 # 8 hours
|
PERMANENT_SESSION_LIFETIME = 28800 # 8 hours
|
||||||
# None = token valid for the life of the session (#12): a measurement page
|
# None = token valid for the life of the session (#12): a measurement page
|
||||||
# stays open for hours between saves, and a 1h-expired token turned every
|
# stays open for hours between saves, and a 1h-expired token turned every
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
"""SESSION_COOKIE_SECURE reads from the environment (#38.4).
|
||||||
|
|
||||||
|
Default unchanged (secure unless FLASK_DEBUG=1); SESSION_COOKIE_SECURE=false
|
||||||
|
overrides it for the on-premise HTTP-in-LAN install.
|
||||||
|
"""
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
|
def _reload_config():
|
||||||
|
import config
|
||||||
|
importlib.reload(config)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_secure_when_not_debug(monkeypatch):
|
||||||
|
monkeypatch.delenv("SESSION_COOKIE_SECURE", raising=False)
|
||||||
|
monkeypatch.delenv("FLASK_DEBUG", raising=False)
|
||||||
|
config = _reload_config()
|
||||||
|
assert config.Config.SESSION_COOKIE_SECURE is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_insecure_when_debug(monkeypatch):
|
||||||
|
monkeypatch.delenv("SESSION_COOKIE_SECURE", raising=False)
|
||||||
|
monkeypatch.setenv("FLASK_DEBUG", "1")
|
||||||
|
config = _reload_config()
|
||||||
|
assert config.Config.SESSION_COOKIE_SECURE is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_false_overrides_default_in_production(monkeypatch):
|
||||||
|
"""On-premise over plain HTTP: FLASK_DEBUG stays unset (real prod), but
|
||||||
|
the cookie must not be marked Secure or the browser silently drops it."""
|
||||||
|
monkeypatch.delenv("FLASK_DEBUG", raising=False)
|
||||||
|
monkeypatch.setenv("SESSION_COOKIE_SECURE", "false")
|
||||||
|
config = _reload_config()
|
||||||
|
assert config.Config.SESSION_COOKIE_SECURE is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_true_keeps_secure(monkeypatch):
|
||||||
|
monkeypatch.setenv("FLASK_DEBUG", "1")
|
||||||
|
monkeypatch.setenv("SESSION_COOKIE_SECURE", "true")
|
||||||
|
config = _reload_config()
|
||||||
|
assert config.Config.SESSION_COOKIE_SECURE is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_falsy_values_are_case_insensitive(monkeypatch):
|
||||||
|
monkeypatch.delenv("FLASK_DEBUG", raising=False)
|
||||||
|
for value in ("false", "FALSE", "0", "no", " False "):
|
||||||
|
monkeypatch.setenv("SESSION_COOKIE_SECURE", value)
|
||||||
|
config = _reload_config()
|
||||||
|
assert config.Config.SESSION_COOKIE_SECURE is False, value
|
||||||
Reference in New Issue
Block a user