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:
2026-08-05 09:21:18 +02:00
co-authored by Claude Haiku 4.5
parent a8bcf7cdeb
commit f70ba93e7a
9 changed files with 231 additions and 12 deletions
+12
View File
@@ -92,6 +92,7 @@
session_restore_key: 'Restaurer depuis une clé',
session_note: 'Vos imprimantes sont liées à ce navigateur. La clé de secours les récupère ailleurs.',
ephemeral_warning: 'Cette session ne dure que tant que ce navigateur reste ouvert : vos imprimantes et réglages sont bien enregistrés, mais le lien vers eux est perdu à la fermeture. Téléchargez votre clé de secours pour les conserver.',
single_user_notice: 'Mode mono-utilisateur : tout est enregistré sur le serveur sans cookie de session, et toute personne qui accède à cette application voit les mêmes imprimantes.',
open_menu: 'Ouvrir le menu',
// Dashboard
dashboard_intro: 'Trois étapes du fichier ZIP au paquet déployable.',
@@ -277,6 +278,7 @@
session_restore_key: 'Restore from a key',
session_note: 'Your printers live in this browser. The backup key brings them back elsewhere.',
ephemeral_warning: 'This session lasts only while this browser stays open — your printers and configs are saved, but the link to them is lost when you close it. Download your backup key to keep them.',
single_user_notice: 'Single-user mode: everything is stored on the server without a session cookie, and anyone who can reach this app sees the same printers.',
open_menu: 'Open menu',
// Dashboard
dashboard_intro: 'Three steps from driver ZIP to deployable package.',
@@ -459,6 +461,8 @@
</ul>
</div>
{# Single-user mode has no cookie and no keys — the whole menu would 404. #}
{% if not request.state.single_user %}
<div class="sidebar-foot">
<details {% if request.url.path == "/session/restore" %}open{% endif %}>
<summary>{{ ico.i('key') }}<span x-data x-text="$store.i18n.t('session_menu')">This session</span></summary>
@@ -469,6 +473,7 @@
</div>
</details>
</div>
{% endif %}
</nav>
<div class="main-wrapper">
@@ -497,6 +502,13 @@
</header>
<main class="main-content">
{% if request.state.single_user %}
{# COOKIE_SECURE=single_user: one shared store, no cookie, no isolation. #}
<div class="notice session-single-user" id="single-user-notice">
{{ ico.i('key', 16) }}
<p x-data x-text="$store.i18n.t('single_user_notice')">Single-user mode: everything is stored on the server without a session cookie, and anyone who can reach this app sees the same printers.</p>
</div>
{% endif %}
{% if request.state.ephemeral_session %}
{# COOKIE_SECURE=false: the owner key is memory-only, so the session
dies with the browser window. Say it before work is lost. #}