feat: session mode parsing, memory-only cookies, single-user mode
- COOKIE_SECURE env now accepts: 'true' (secure), 'false' (memory-only), 'single_user' (no cookie) - config.parse_cookie_mode() returns (COOKIE_SECURE, SINGLE_USER) tuple for routing - single_user_owner() returns oldest Owner for test/local deployments - session cookie respects mode: Max-Age only in secure mode, dropped for memory-only - base.html renders ephemeral-session and single-user banners per mode - Tests: comprehensive coverage for all three modes with monkeypatch configs - CLAUDE.md + README docs updated Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -268,3 +268,96 @@ def test_restore_also_issues_a_session_cookie_when_insecure(tmp_data_dir, monkey
|
||||
assert resp.status_code == 303
|
||||
header = _set_cookie_header(resp)
|
||||
assert "Max-Age" not in header and "Expires" not in header
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# COOKIE_SECURE=single_user — no cookie at all, one shared Owner
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_parse_cookie_mode_reads_three_modes():
|
||||
from imptune.config import parse_cookie_mode
|
||||
|
||||
assert parse_cookie_mode("true") == (True, False)
|
||||
assert parse_cookie_mode("") == (True, False) # unset-ish → secure default
|
||||
assert parse_cookie_mode("False") == (False, False)
|
||||
for spelling in ("single_user", "single-user", "SingleUser", " single "):
|
||||
assert parse_cookie_mode(spelling) == (False, True)
|
||||
|
||||
|
||||
def _single_user(monkeypatch):
|
||||
import imptune.config as cfg
|
||||
|
||||
monkeypatch.setattr(cfg, "SINGLE_USER", True)
|
||||
monkeypatch.setattr(cfg, "COOKIE_SECURE", False)
|
||||
|
||||
|
||||
def test_single_user_mode_sets_no_cookie_and_skips_onboarding(tmp_data_dir, monkeypatch):
|
||||
from imptune.main import app
|
||||
|
||||
_single_user(monkeypatch)
|
||||
with TestClient(app) as fresh:
|
||||
resp = fresh.get("/")
|
||||
assert resp.status_code == 200
|
||||
assert COOKIE_NAME not in fresh.cookies
|
||||
assert not [v for k, v in resp.headers.multi_items() if k.lower() == "set-cookie"]
|
||||
assert "session-choice-modal" not in resp.text
|
||||
assert "ephemeral-session-warning" not in resp.text
|
||||
assert "single-user-notice" in resp.text
|
||||
|
||||
|
||||
def test_single_user_mode_shares_data_across_browsers(tmp_data_dir, monkeypatch):
|
||||
"""The point of the mode: a cookie-less client sees the same printers."""
|
||||
from imptune.main import app
|
||||
|
||||
_single_user(monkeypatch)
|
||||
with TestClient(app) as client_a, TestClient(app) as client_b:
|
||||
client_a.post(
|
||||
"/printers",
|
||||
data={"name": "Shared Printer", "ip_address": "10.0.7.1", "port_name": "IP_S"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert "Shared Printer" in client_b.get("/printers").text
|
||||
|
||||
|
||||
def test_single_user_mode_creates_exactly_one_owner(tmp_data_dir, monkeypatch):
|
||||
from imptune.db.models import Owner
|
||||
from imptune.main import app
|
||||
|
||||
_single_user(monkeypatch)
|
||||
with TestClient(app) as fresh:
|
||||
for _ in range(4):
|
||||
assert fresh.get("/printers").status_code == 200
|
||||
assert Owner.select().count() == 1
|
||||
|
||||
|
||||
def test_single_user_mode_adopts_the_existing_owner(tmp_data_dir, monkeypatch):
|
||||
"""Switching a cookie-scoped deployment over must not hide its printers."""
|
||||
from imptune.main import app
|
||||
|
||||
with TestClient(app) as cookie_client:
|
||||
cookie_client.post(
|
||||
"/printers",
|
||||
data={"name": "Pre-switch Printer", "ip_address": "10.0.7.2", "port_name": "IP_T"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
_single_user(monkeypatch)
|
||||
with TestClient(app) as after_switch:
|
||||
assert "Pre-switch Printer" in after_switch.get("/printers").text
|
||||
|
||||
|
||||
def test_single_user_mode_disables_session_key_routes(tmp_data_dir, monkeypatch):
|
||||
"""No cookie to re-point, and the shared owner's key must not leak."""
|
||||
from imptune.main import app
|
||||
|
||||
_single_user(monkeypatch)
|
||||
with TestClient(app) as fresh:
|
||||
assert fresh.get("/session/key/download").status_code == 404
|
||||
assert fresh.get("/session/restore").status_code == 404
|
||||
assert fresh.post(
|
||||
"/session/restore",
|
||||
data={"key": "anything"},
|
||||
headers={"origin": "http://testserver"},
|
||||
).status_code == 404
|
||||
assert "/session/key/download" not in fresh.get("/printers").text
|
||||
|
||||
Reference in New Issue
Block a user