fix: isolate profile cookie per webui instance
This commit is contained in:
1 parent
704f8ab16a
commit
d5a31a0f4d
2 files changed
+48
-8
No files matched your search
+16
-8
@@ -2,6 +2,7 @@
|
||||
Hermes Web UI -- HTTP helper functions.
|
||||
"""
|
||||
import json as _json
|
||||
import os
|
||||
import re as _re
|
||||
from pathlib import Path
|
||||
from api.config import IMAGE_EXTS, MD_EXTS
|
||||
@@ -252,8 +253,13 @@ def read_body(handler) -> dict:
|
||||
PROFILE_COOKIE_NAME = 'hermes_profile'
|
||||
|
||||
|
||||
def get_profile_cookie_name() -> str:
|
||||
"""Return the cookie name used to persist the active WebUI profile."""
|
||||
return os.getenv('WEBUI_PROFILE_COOKIE_NAME', PROFILE_COOKIE_NAME)
|
||||
|
||||
|
||||
def get_profile_cookie(handler) -> str | None:
|
||||
"""Extract the hermes_profile cookie value from the request, or None."""
|
||||
"""Extract the active-profile cookie value from the request, or None."""
|
||||
cookie_header = handler.headers.get('Cookie', '')
|
||||
if not cookie_header:
|
||||
return None
|
||||
@@ -263,7 +269,8 @@ def get_profile_cookie(handler) -> str | None:
|
||||
cookie.load(cookie_header)
|
||||
except _hc.CookieError:
|
||||
return None
|
||||
morsel = cookie.get(PROFILE_COOKIE_NAME)
|
||||
cookie_name = get_profile_cookie_name()
|
||||
morsel = cookie.get(cookie_name)
|
||||
if morsel and morsel.value:
|
||||
# Validate against profile-name pattern before trusting
|
||||
from api.profiles import _PROFILE_ID_RE
|
||||
@@ -274,7 +281,7 @@ def get_profile_cookie(handler) -> str | None:
|
||||
|
||||
|
||||
def build_profile_cookie(name: str) -> str:
|
||||
"""Build a Set-Cookie header value for the hermes_profile cookie.
|
||||
"""Build a Set-Cookie header value for the active-profile cookie.
|
||||
|
||||
Always persist the selected profile in the cookie, including 'default'.
|
||||
Clearing the cookie causes the backend to fall back to process-global
|
||||
@@ -287,8 +294,9 @@ def build_profile_cookie(name: str) -> str:
|
||||
"""
|
||||
import http.cookies as _hc
|
||||
cookie = _hc.SimpleCookie()
|
||||
cookie[PROFILE_COOKIE_NAME] = name
|
||||
cookie[PROFILE_COOKIE_NAME]['path'] = '/'
|
||||
cookie[PROFILE_COOKIE_NAME]['httponly'] = True
|
||||
cookie[PROFILE_COOKIE_NAME]['samesite'] = 'Lax'
|
||||
return cookie[PROFILE_COOKIE_NAME].OutputString()
|
||||
cookie_name = get_profile_cookie_name()
|
||||
cookie[cookie_name] = name
|
||||
cookie[cookie_name]['path'] = '/'
|
||||
cookie[cookie_name]['httponly'] = True
|
||||
cookie[cookie_name]['samesite'] = 'Lax'
|
||||
return cookie[cookie_name].OutputString()
|
||||
@@ -73,6 +73,38 @@ class TestProfileCookieHelpers:
|
||||
result = get_profile_cookie(handler)
|
||||
assert result is None
|
||||
|
||||
def test_profile_cookie_name_defaults_to_hermes_profile(self, monkeypatch):
|
||||
from api.helpers import build_profile_cookie
|
||||
|
||||
monkeypatch.delenv('WEBUI_PROFILE_COOKIE_NAME', raising=False)
|
||||
|
||||
s = build_profile_cookie('alice')
|
||||
assert 'hermes_profile=alice' in s
|
||||
|
||||
def test_profile_cookie_name_can_be_isolated_per_webui_instance(self, monkeypatch):
|
||||
from api.helpers import build_profile_cookie, get_profile_cookie
|
||||
|
||||
monkeypatch.setenv('WEBUI_PROFILE_COOKIE_NAME', 'hermes_profile_social')
|
||||
|
||||
s = build_profile_cookie('writer')
|
||||
assert 'hermes_profile_social=writer' in s
|
||||
assert 'hermes_profile=writer' not in s
|
||||
|
||||
handler = MagicMock()
|
||||
handler.headers.get = lambda k, d='': (
|
||||
'hermes_profile=wrong; hermes_profile_social=writer' if k == 'Cookie' else d
|
||||
)
|
||||
assert get_profile_cookie(handler) == 'writer'
|
||||
|
||||
def test_configured_profile_cookie_ignores_default_cookie_name(self, monkeypatch):
|
||||
from api.helpers import get_profile_cookie
|
||||
|
||||
monkeypatch.setenv('WEBUI_PROFILE_COOKIE_NAME', 'hermes_profile_main')
|
||||
|
||||
handler = MagicMock()
|
||||
handler.headers.get = lambda k, d='': 'hermes_profile=social_profile' if k == 'Cookie' else d
|
||||
assert get_profile_cookie(handler) is None
|
||||
|
||||
|
||||
# ── 2. Thread-local request context ──────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in new issue
Block a user